From 0f2e2b82aac1de6d2eb6a17947385c08796f1432 Mon Sep 17 00:00:00 2001 From: Brian Read Date: Wed, 25 Jun 2025 10:35:16 +0100 Subject: [PATCH] * Tue Jun 24 2025 Brian Read 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] --- root/usr/share/smanager/lib/SrvMngr.pm | 3 +- .../lib/SrvMngr/Controller/Datetime-Custom.pm | 76 ++++++----------- .../lib/SrvMngr/Controller/Datetime.pm | 56 ++++++++++++- .../I18N/Modules/Datetime/datetime_en.lex | 5 +- .../themes/default/public/css/datetime.css | 28 ++++++- .../themes/default/public/js/datetime.js | 83 +++++++++++++++++++ .../themes/default/templates/datetime.html.ep | 14 +++- smeserver-manager.spec | 7 +- 8 files changed, 210 insertions(+), 62 deletions(-) diff --git a/root/usr/share/smanager/lib/SrvMngr.pm b/root/usr/share/smanager/lib/SrvMngr.pm index f012d62..0fb80e2 100644 --- a/root/usr/share/smanager/lib/SrvMngr.pm +++ b/root/usr/share/smanager/lib/SrvMngr.pm @@ -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'); diff --git a/root/usr/share/smanager/lib/SrvMngr/Controller/Datetime-Custom.pm b/root/usr/share/smanager/lib/SrvMngr/Controller/Datetime-Custom.pm index 4551848..2cd2d2b 100644 --- a/root/usr/share/smanager/lib/SrvMngr/Controller/Datetime-Custom.pm +++ b/root/usr/share/smanager/lib/SrvMngr/Controller/Datetime-Custom.pm @@ -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; \ No newline at end of file diff --git a/root/usr/share/smanager/lib/SrvMngr/Controller/Datetime.pm b/root/usr/share/smanager/lib/SrvMngr/Controller/Datetime.pm index ce6dffd..4bdb676 100644 --- a/root/usr/share/smanager/lib/SrvMngr/Controller/Datetime.pm +++ b/root/usr/share/smanager/lib/SrvMngr/Controller/Datetime.pm @@ -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')); @@ -279,5 +282,52 @@ sub do_display { dat_data => \%dat_data ); $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; \ No newline at end of file diff --git a/root/usr/share/smanager/lib/SrvMngr/I18N/Modules/Datetime/datetime_en.lex b/root/usr/share/smanager/lib/SrvMngr/I18N/Modules/Datetime/datetime_en.lex index f0c0582..5fecd5e 100644 --- a/root/usr/share/smanager/lib/SrvMngr/I18N/Modules/Datetime/datetime_en.lex +++ b/root/usr/share/smanager/lib/SrvMngr/I18N/Modules/Datetime/datetime_en.lex @@ -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', @@ -58,4 +58,5 @@ clock, and will not try to synchronize from a time server.', 'dat_ntp_server' => 'NTP server', 'dat_manually_set' => 'Set manually', 'dat_NTP_Server_URL' =>'NTP Server URL:', -'dat_set_manually' =>'Set Date and Time:', \ No newline at end of file +'dat_set_manually' =>'Set Date and Time:', +'dat_Invalid_date' => 'Invalid date', \ No newline at end of file diff --git a/root/usr/share/smanager/themes/default/public/css/datetime.css b/root/usr/share/smanager/themes/default/public/css/datetime.css index b0289a7..766f86d 100644 --- a/root/usr/share/smanager/themes/default/public/css/datetime.css +++ b/root/usr/share/smanager/themes/default/public/css/datetime.css @@ -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 { @@ -26,4 +26,26 @@ border: 1px solid #ccc; border-left: none; border-radius: 0 4px 4px 0; -} \ No newline at end of file +} + +.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; } \ No newline at end of file diff --git a/root/usr/share/smanager/themes/default/public/js/datetime.js b/root/usr/share/smanager/themes/default/public/js/datetime.js index 0f75a5b..6641923 100644 --- a/root/usr/share/smanager/themes/default/public/js/datetime.js +++ b/root/usr/share/smanager/themes/default/public/js/datetime.js @@ -15,4 +15,87 @@ 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}`; + }); + }); }); \ No newline at end of file diff --git a/root/usr/share/smanager/themes/default/templates/datetime.html.ep b/root/usr/share/smanager/themes/default/templates/datetime.html.ep index c983e12..c795e13 100644 --- a/root/usr/share/smanager/themes/default/templates/datetime.html.ep +++ b/root/usr/share/smanager/themes/default/templates/datetime.html.ep @@ -27,7 +27,15 @@

<%= $title %>


%= $modul <% my $btn = l('SAVE'); %> -

+

+ + %= label_for 'real-time-clock' => $c->l('dat_The_time_is_currently'), class => 'datetime-clock-label' + + + %= text_field 'clock', id => 'real-time-clock', readonly => 'readonly', class => 'datetime-clock' , value => $dat_data->{currentdatetime} + % if ($dat_data->{ntpstatus} eq 'disabled') {

%=l 'dat_NTP_ENABLE_DESC' @@ -68,7 +76,9 @@
% 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' + +
diff --git a/smeserver-manager.spec b/smeserver-manager.spec index cfaed9c..7fbea0b 100644 --- a/smeserver-manager.spec +++ b/smeserver-manager.spec @@ -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 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 11.0.0-94.sme - re-instate datetime routes in SrvMngr.pm - removed by mistake [SME: 13053]