Compare commits

..

8 Commits

Author SHA1 Message Date
1708fbe3fa * Wed Jul 16 2025 Brian Read <brianr@koozali.org> 11.0.0-108.sme
- Password change panel - add extra descriptive text [SME: 13081]
2025-07-16 06:19:47 +01:00
9f6e45d98c * Tue Jul 15 2025 Brian Read <brianr@koozali.org> 11.0.0-107.sme
- Email Settings panel: Fix lex strings with long names and no prefix [SME: 13080]
2025-07-15 15:43:30 +01:00
5ba77e4526 * Mon Jul 14 2025 Brian Read <brianr@koozali.org> 11.0.0-106.sme
- Datetime panel: Typo in error message - missing _ [SME: 13078]
- ditto user accounts panel                         [SME: 13078]
- ditto groups panel                                [SME: 13078]
2025-07-14 11:44:49 +01:00
1d00f2aeb2 * Mon Jul 14 2025 Brian Read <brianr@koozali.org> 11.0.0-105.sme
- Adjust db opening in Software install panel [SME: 13077]
2025-07-14 06:38:10 +01:00
b0c6bea0a2 * Mon Jul 07 2025 Brian Read <brianr@koozali.org> 11.0.0-104.sme
- Fix DB not open in Workgrouyp panel sometimes. [SME: 13070]
2025-07-07 06:22:13 +01:00
5a57ffaa34 * Thu Jul 03 2025 Brian Read <brianr@koozali.org> 11.0.0-103.sme
- Fix a couple of typos [SME: 13065]
2025-07-03 11:11:12 +01:00
717d34a53d * Thu Jul 03 2025 Brian Read <brianr@koozali.org> 11.0.0-102.sme
- Local Network panel: Make DEL and ADD robust even if called more than once by browser [SME: 13065]
2025-07-03 10:18:28 +01:00
bec39af49c * Wed Jul 02 2025 Brian Read <brianr@koozali.org> 11.0.0-101.sme
- Fix busy cursor, csp intervened. [SME: 13064]
2025-07-02 14:10:06 +01:00
18 changed files with 190 additions and 94 deletions

View File

@@ -255,7 +255,7 @@ sub validate_change_datetime {
}
if (($minute < 0) || ($minute > 59)) {
return $c->l('datINVALID_MINUTE') . " $minute. " . $c->l('dat_BETWEEN_0_AND_59');
return $c->l('dat_INVALID_MINUTE') . " $minute. " . $c->l('dat_BETWEEN_0_AND_59');
}
my $second = $c->param('second');

View File

@@ -132,7 +132,7 @@ sub do_update {
# Untaint groupName before use in system()
($groupName) = ($groupName =~ /^([a-z][\-\_\.a-z0-9]*)$/);
system("/sbin/e-smith/signal-event", "group-create", "$groupName") == 0
or $result .= $c->l('qgp_CREATE_ERROR') . "\n";
or $result .= $c->l('grp_CREATE_ERROR') . "\n";
} ## end if (!$result)
if (!$result) {
@@ -161,7 +161,7 @@ sub do_update {
# Untaint groupName before use in system()
($groupName) = ($groupName =~ /^([a-z][\-\_\.a-z0-9]*)$/);
system("/sbin/e-smith/signal-event", "group-modify", "$groupName") == 0
or $result .= $c->l('qgp_MODIFY_ERROR') . "\n";
or $result .= $c->l('grp_MODIFY_ERROR') . "\n";
} ## end if (!$result)
if (!$result) {
@@ -414,4 +414,4 @@ sub validate_description {
return ($c->l('FM_ERR_UNEXPECTED_DESC'));
}
} ## end sub validate_description
1
1

View File

@@ -5,6 +5,12 @@ package SrvMngr::Controller::Localnetworks;
# description : Local networks
# navigation : 6000 500
#
#$if_admin->get('/localnetworks')->to('localnetworks#main')->name('localnetworks');
#$if_admin->post('/localnetworks')->to('localnetworks#do_display')->name('localnetworks');
#$if_admin->post('/localnetworksa')->to('localnetworks#do_display')->name('localnetworksadd');
#$if_admin->post('/localnetworksb')->to('localnetworks#do_display')->name('localnetworksadd1');
#$if_admin->get('/localnetworksd')->to('localnetworks#do_display')->name('localnetworksdel');
#$if_admin->post('/localnetworkse')->to('localnetworks#do_display')->name('localnetworksdel1');
# routes : end
#----------------------------------------------------------------------
use strict;
@@ -53,6 +59,7 @@ sub do_display {
$trt = 'ADD' if ($rt eq 'localnetworksadd');
$trt = 'ADD1' if ($rt eq 'localnetworksadd1');
$trt = 'DEL1' if ($rt eq 'localnetworksdel1');
$c->app->log->info("Localnetworks:trt:$trt");
my %ln_datas = ();
my $title = $c->l('ln_LOCAL NETWORKS');
my $modul = '';
@@ -83,16 +90,21 @@ sub do_display {
if ($trt eq 'DEL1') {
#After Remove clicked on Delete network panel
#$network_db = esmith::NetworksDB::UTF8->open() || die("Failed to open Networkdb-1");
#$network_db = esmith::NetworksDB::UTF8->open() || die("Failed to open Networkdb-1");
my $localnetwork = $c->param("localnetwork");
my $delete_hosts = $c->param("deletehost") || "1"; #default to deleting them.
my $rec = $network_db->get($localnetwork) || die("Failed to find network on db:$localnetwork");
if ($rec and $rec->prop('type') eq 'localnetwork') {
$ln_datas{localnetwork} = $localnetwork;
}
my %ret = remove_network($localnetwork, $delete_hosts);
#$network_db = esmith::NetworksDB::UTF8->open() || die("Failed to open Networkdb-2"); #Refresh the network DB
my $delete_hosts = $c->param("deletehost") || "1"; #default to deleting them.
$c->app->log->info("Localnetworks:deleting $localnetwork");
my ($rec,%ret);
if ($rec = $network_db->get($localnetwork)){ #|| die("Failed to find network on db:$localnetwork");
if ($rec and $rec->prop('type') eq 'localnetwork') {
$ln_datas{localnetwork} = $localnetwork;
}
%ret = $c->remove_network($localnetwork, $delete_hosts);
} else {
$c->app->log->info("Local network: delete failed to find network in db: $localnetwork");
%ret = (ret => "");
}
#$network_db = esmith::NetworksDB::UTF8->open() || die("Failed to open Networkdb-2"); #Refresh the network DB
my @localnetworks;
if ($network_db) {
@@ -100,8 +112,10 @@ sub do_display {
}
# Load up ln_datas with values need by template
$ln_datas{subnet} = $rec->prop('Mask');
$ln_datas{router} = $rec->prop('Router');
if ($rec){
$ln_datas{subnet} = $rec->prop('Mask');
$ln_datas{router} = $rec->prop('Router');
}
$c->stash(ln_datas => \%ln_datas, localnetworks => \@localnetworks, ret => \%ret);
} ## end if ($trt eq 'DEL1')
@@ -109,14 +123,20 @@ sub do_display {
#Initial delete panel requiring confirmation
my $localnetwork = $c->param("localnetwork") || '';
my ($rec,%ret);
$c->stash(localnetwork => $localnetwork);
my $rec = $network_db->get($localnetwork) || die("Failed to get local network in db::$localnetwork");
my $subnet = $rec->prop('Mask');
$ln_datas{subnet} = $subnet;
$ln_datas{router} = $rec->prop('Router');
my $numhosts = hosts_on_network($localnetwork, $subnet);
$ln_datas{localnetwork} = $localnetwork;
$ln_datas{deletehosts} = $numhosts > 0 ? 1 : 0;
if ($rec = $network_db->get($localnetwork)){
my $subnet = $rec->prop('Mask');
$ln_datas{subnet} = $subnet;
$ln_datas{router} = $rec->prop('Router');
my $numhosts =$c->hosts_on_network($localnetwork, $subnet);
$ln_datas{localnetwork} = $localnetwork;
$ln_datas{deletehosts} = $numhosts > 0 ? 1 : 0;
} else {
$c->app->log->info("Local network:Initial Failed to find network in Db: $localnetwork");
$c->flash('error',$c->l('ln_Failed to find network in Db'));
$trt = 'LIST';
}
} ## end if ($trt eq 'DEL')
if ($trt eq 'LIST') {
@@ -136,6 +156,7 @@ sub do_display {
} ## end sub do_display
sub remove_network {
my $c = shift;
my $network = shift;
$network_db = esmith::NetworksDB::UTF8->open();
my $record = $network_db->get($network);
@@ -152,7 +173,7 @@ sub remove_network {
if (system("/sbin/e-smith/signal-event", "network-delete", $network) == 0) {
if ($delete_hosts) {
my @hosts_to_delete = hosts_on_network($network, $subnet);
my @hosts_to_delete = $c->hosts_on_network($network, $subnet);
foreach my $host (@hosts_to_delete) {
$host->delete;
@@ -169,6 +190,7 @@ sub remove_network {
} ## end sub remove_network
sub hosts_on_network {
my $c = shift;
my $network = shift;
my $netmask = shift;
die if not $network and $netmask;
@@ -191,17 +213,20 @@ sub hosts_on_network {
} ## end sub hosts_on_network
sub add_network {
my ($fm) = @_;
my $networkAddress = $fm->param('networkAddress');
my $networkMask = $fm->param('networkMask');
my $networkRouter = $fm->param('networkRouter');
my ($c) = @_;
my $networkAddress = $c->param('networkAddress');
my $networkMask = $c->param('networkMask');
my $networkRouter = $c->param('networkRouter');
#Start by checking that the network does not already exist
#Validate Ips and subnet mask
my $res = ip_number($fm, $networkAddress);
my $res = ip_number($c, $networkAddress);
return (ret => 'ln_INVALID_IP_ADDRESS', vars => "Network Address $res") unless $res eq 'OK';
$res = subnet_mask($networkMask);
return (ret => 'ln_INVALID_SUBNET_MASK', vars => "$networkMask") unless $res eq 'OK';
$res = ip_number($fm, $networkRouter);
$res = ip_number($c, $networkRouter);
return (ret => 'ln_INVALID_IP_ADDRESS', vars => "Routeur Address $res") unless $res eq 'OK';
# we transform bit mask to regular mask
@@ -234,19 +259,25 @@ sub add_network {
if ($network_db->get($network)) {
return (ret => 'ln_NETWORK_ALREADY_ADDED', vars => "$network,$networkMask,$networkRouter");
}
$network_db->new_record(
$res = $network_db->new_record(
$network,
{ Mask => $networkMask,
Router => $networkRouter,
type => 'network',
}
);
# Untaint $network before use in system()
$network =~ /(.+)/;
$network = $1;
system("/sbin/e-smith/signal-event", "network-create", $network) == 0
or (return (ret => 'ln_ERROR_CREATING_NETWORK', vars => "$network,$networkMask,$networkRouter"));
if (! $res) {
#Record already existed
$c->app->log->info("Local Network:Network already exists:$network");
#return success message
} else {
#Only call underlying batch if new record created
# Untaint $network before use in system()
$network =~ /(.+)/;
$network = $1;
system("/sbin/e-smith/signal-event", "network-create", $network) == 0
or (return (ret => 'ln_ERROR_CREATING_NETWORK', vars => "$network,$networkMask,$networkRouter"));
}
my ($totalHosts, $firstAddr, $lastAddr) = esmith::util::computeHostRange($network, $networkMask);
my $msg;

View File

@@ -176,7 +176,7 @@ sub do_update {
$result .= $res unless $res eq 'OK';
if (!$result) {
$result = $c->l('usrUSER_CREATED') . ' ' . $user;
$result = $c->l('usr_USER_CREATED') . ' ' . $user;
$usr_datas{trt} = 'SUC';
}
} ## end if (!$result)
@@ -791,4 +791,4 @@ sub system_change_password {
return $c->l("Error occurred while modifying password for admin.", 'First');
}
} ## end sub system_change_password
1
1

View File

@@ -34,6 +34,7 @@ sub main {
sub do_update {
my $c = shift;
$c->app->log->info($c->log_req);
$db = esmith::ConfigDB::UTF8->open || die "Couldn't open config db";
my $result = "";
my $workgroup = ($c->param('Workgroup') || 'no');
my $servername = ($c->param('ServerName') || 'WS');
@@ -78,4 +79,4 @@ sub validate2_workgroup {
return $c->l('INVALID_WORKGROUP_MATCHES_SERVERNAME') if ($servername eq $workgroup);
return ('OK');
} ## end sub validate2_workgroup
1;
1;

View File

@@ -4,6 +4,12 @@ package SrvMngr::Controller::Yum;
# heading : System
# description : Software installer
# navigation : 4000 500
#$if_admin->get('/yum')->to('yum#main')->name('yum');
#$if_admin->post('/yum')->to('yum#do_display')->name('yumd1');
#$if_admin->get('/yumd')->to('yum#do_display')->name('yumd');
#$if_admin->post('/yumd')->to('yum#do_update')->name('yumu');
#
# routes : end
#----------------------------------------------------------------------
@@ -23,20 +29,20 @@ my $dnf_status_file = '/var/cache/dnf/dnf.status';
#use File::stat;
our %dbs;
for (qw(available installed updates)) {
$dbs{$_} = esmith::ConfigDB::UTF8->open_ro("dnf_$_")
or die "Couldn't open dnf_$_ DB\n";
}
for (qw(repositories)) {
$dbs{$_} = esmith::ConfigDB::UTF8->open("yum_$_")
or die "Couldn't open yum_$_ DB\n";
}
sub main {
my $c = shift;
$c->app->log->info($c->log_req);
$cdb = esmith::ConfigDB::UTF8->open || die "Couldn't open config db";
for (qw(available installed updates)) {
$dbs{$_} = esmith::ConfigDB::UTF8->open_ro("dnf_$_")
or die "Couldn't open dnf_$_ DB\n";
}
for (qw(repositories)) {
$dbs{$_} = esmith::ConfigDB::UTF8->open("yum_$_")
or die "Couldn't open yum_$_ DB\n";
}
my %yum_datas = ();
my $title = $c->l('yum_FORM_TITLE');
my $dest = 'yum';
@@ -63,6 +69,15 @@ sub do_display {
my $rt = $c->current_route;
my $trt = ($c->param('trt') || 'STAT');
$cdb = esmith::ConfigDB::UTF8->open || die "Couldn't open config db";
for (qw(available installed updates)) {
$dbs{$_} = esmith::ConfigDB::UTF8->open_ro("dnf_$_")
or die "Couldn't open dnf_$_ DB\n";
}
for (qw(repositories)) {
$dbs{$_} = esmith::ConfigDB::UTF8->open("yum_$_")
or die "Couldn't open yum_$_ DB\n";
}
my %yum_datas = ();
my $title = $c->l('yum_FORM_TITLE');
my ($notif, $dest) = '';
@@ -113,6 +128,16 @@ sub do_update {
$c->app->log->info($c->log_req);
my $rt = $c->current_route;
my $trt = $c->param('trt');
$cdb = esmith::ConfigDB::UTF8->open || die "Couldn't open config db";
for (qw(available installed updates)) {
$dbs{$_} = esmith::ConfigDB::UTF8->open_ro("dnf_$_")
or die "Couldn't open dnf_$_ DB\n";
}
for (qw(repositories)) {
$dbs{$_} = esmith::ConfigDB::UTF8->open("yum_$_")
or die "Couldn't open yum_$_ DB\n";
}
my %yum_datas = ();
$yum_datas{trt} = $trt;
my $title = $c->l('yum_FORM_TITLE');
@@ -456,4 +481,4 @@ sub show_yum_log {
my $yum_log = $cdb->get_prop_and_delete('dnf', 'LogFile');
return $out;
} ## end sub show_yum_log
1;
1;

View File

@@ -153,4 +153,11 @@ enabled.',
'mai_ACC_panel_action_was_successful' => 'Email access settings saved sucessfully',
'mai_DEL_panel_action_was_successful' => 'Email delivery settings saved sucessfully',
'mai_FIL_panel_action_was_successful' => 'Email filtering settings saved sucessfully',
'mai_REC_panel_action_was_successful' => 'Email receive settings saved sucessfully',
'mai_REC_panel_action_was_successful' => 'Email receive settings saved sucessfully',
'mai_SMTP_port_authenticate' => 'Smtp port %u allow client to authenticate:',
'mai_SMTP_port_(25)' => 'Smtp port (25) main purpose is to receive emails from another server, starttls is always offered, but auth if offered is only if starttls is also used by client.',
'mai_SMTP_Submission' => 'Smtp submission port (587) will require starttls, then authenticate to send.',
'mai_SMTPS_port_(465)' => 'Smtps port (465) will require implicit ssl/tls then authenticate to send.',
'mai_SMTPS_SSL/TLS' => 'Smtps ssl/tls auth: port %u status:',
'mai_Submission_port' => 'Submission port %u status:'

View File

@@ -27,4 +27,5 @@
'ln_REMOVE_HOSTS_DESC' => 'Local hosts configured on the network you are about to remove have been detected. By default, they will also be removed. Uncheck this box if, for some reason, you do not wish this to happen. Note that they will not be treated as local, and may not even be reachable, after this network is removed. ',
'ln_REMOVE_HOSTS_LABEL' => 'Remove hosts on network',
'ln_extra' => '[_1]/[_2] via router $networkRouter}.',
'ln_SUCCESS_NONSTANDARD_RANGE' =>'<p>Successfully added network [_1]/[_2] via router [_3].</p><p> Your server will grant local access privileges to [_4] IP addresses in the range [_5] to [_6].</p><p> Warning: the ProFTPd FTP server cannot handle this nonstandard subnet mask. The simpler specification <b>[_7]</b> will be used instead.</p>',
'ln_SUCCESS_NONSTANDARD_RANGE' =>'<p>Successfully added network [_1]/[_2] via router [_3].</p><p> Your server will grant local access privileges to [_4] IP addresses in the range [_5] to [_6].</p><p> Warning: the ProFTPd FTP server cannot handle this nonstandard subnet mask. The simpler specification <b>[_7]</b> will be used instead.</p>',
'ln_Failed to find network in Db' => 'Failed to find network in Db',

View File

@@ -19,3 +19,5 @@
'pwd_PASSWORD_CHANGE_SUCCESS' => 'Your password has been successfully changed.',
'pwd_DESCRIPTION' => '<P>To change your account password, please fill out the following form. You will need to provide the name of your account, your old password, and your desired new password. (You must type the new password twice.)</P><P>If you cannot change your password because you have forgotten the old one, your local system administrator can reset your password using the <EM>server manager</EM>.</P>',
'pwd_DESCRIPTION_RESET' => '<P>To reset your account password, please fill out the following form. You will need to provide your desired new password. (You must type it twice.)</P><P>In cas of problem, your local system administrator can reset your password using the <EM>server manager</EM>.</P>',
'pwd_PASSWORD_DESCRIPTION' => 'The password must be composed of at least twelve characters with uppercase and lowercase letters, numbers and non-alphanumeric characters.',
'pwd_PASSWORD_WIKI' => "If you want to know more about passwords, then please refer to the <a href='https://wiki.koozali.org/Useful_Commands#Password_strength'>Wiki</a>",

View File

@@ -280,4 +280,14 @@ background-color: #e8f3e1;
/* Or specify the desired color */
text-decoration: none;
/* Or any other style you want to reset */
}
#busy-indicator {
display: none;
margin-top: 10px;
font-weight: bold;
color: red;
}
.busy {
cursor: wait; /* Change the cursor to a 'wait' cursor */
}

View File

@@ -28,4 +28,16 @@ $(document).ready(function() {
var iconSrc = inputType === 'password' ? 'images/visible.png' : 'images/visible-slash.png';
$(this).attr('src', iconSrc);
});
});
// and busy cursor
$(document).ready(function() {
// Handle form submission for any form
$('form').on('submit', function(event) {
// Disable all submit buttons and update their labels
$(this).find('button[type="submit"]').prop('disabled', true).text('Please wait...');
$(this).find('input[type="submit"]').prop('disabled', true).val('Please wait...');
// Add busy cursor
$('body').addClass('busy');
});
});

View File

@@ -32,19 +32,7 @@
<link rel="stylesheet" href="/smanager/css/flag-icon.min.css">
%= stylesheet '/css/sme-jquery-overrides.css'
<style>
#busy-indicator {
display: none;
margin-top: 10px;
font-weight: bold;
color: red;
}
.busy {
cursor: wait; /* Change the cursor to a 'wait' cursor */
}
</style>
%# panel specific css file
% my $controller = stash('controller');
% if ($controller) {
@@ -112,19 +100,6 @@
%= javascript '/js/sme-dataTable-setup.js'
<script>
$(document).ready(function() {
// Handle form submission for any form
$('form').on('submit', function(event) {
// Disable the submit button
$(this).find('button[type="submit"], input[type="submit"]').prop('disabled', true);
// Show the busy indicator
// Change the cursor to "wait"
$('body').addClass('busy');
});
});
</script>
%# Specific panel js code
% $controller = stash('controller');
% if ($controller) {

View File

@@ -6,12 +6,12 @@
% my $router = $ln_datas->{router};
% if (config->{debug} == 1) {
<p>
%= dumper $c->current_route
%= dumper $c->stash("ret")
%= dumper %$ln_datas
%= dumper $deletehosts
</p>
<pre>
%= dumper $c->current_route
%= dumper $c->stash("ret")
%= dumper %$ln_datas
%= dumper $deletehosts
</pre>
% }
%= form_for '/localnetworkse' => (method => 'POST') => begin
@@ -56,4 +56,4 @@
%end
</div>
</div>

View File

@@ -8,9 +8,9 @@
% my ($var1, $var2, $var3, $var4, $var5, $var6, $var7) = @vars;
% if ($c->app->config->{debug}) {
<p>
<pre>
%= dumper { ret_data => \%ret }
</p>
</pre>
% }
% if ($ret{ret} eq "") {

View File

@@ -89,21 +89,21 @@
% my $smtp_mesg;
% $smtp_mesg=l('SMTP port %u allow client to authenticate:');
% $smtp_mesg=l('mai_SMTP_port_authenticate');
<p><span class=label>
%= sprintf($smtp_mesg,$c->get_db_prop('qpsmtpd','TCPPort',25));
</span><span class=data2>
%= $c->get_current_smtp_auth( TRUE )
</span></p>
% $smtp_mesg=l('SMTPS SSL/TLS auth: port %u status:');
% $smtp_mesg=l('mai_SMTPS_SSL/TLS');
<p><span class=label>
%= sprintf($smtp_mesg,$c->get_db_prop('sqpsmtpd','TCPPort',465));
</span><span class=data2>
%= $c->get_current_smtp_ssl_auth( TRUE, 's', FALSE )
</span></p>
% $smtp_mesg=l 'Submission port %u status:';
% $smtp_mesg=l 'mai_Submission_port';
<p><span class=label>
%= sprintf($smtp_mesg,$c->get_db_prop('uqpsmtpd','TCPPort',587));
</span><span class=data2>

View File

@@ -38,14 +38,14 @@
%#=l 'mai_DESC_SMTP_AUTH_CONTROL'
<br />
%= l "SMTP port (25) main purpose is to receive emails from another server, STARTTLS is always offered, but auth if offered is only if STARTTLS is also used by client."
%= l "mai_SMTP_port_(25)"
<br />
%= l "SMTPS port (465) will require implicit SSL/TLS then authenticate to send."
%= l "mai_SMTPS_port_(465)"
<br />
%= l "SMTP Submission port (587) will require STARTTLS, then authenticate to send."
%= l "mai_SMTP_Submission"
<br />
% my $smtp_mesg=l('SMTP port %u allow client to authenticate:');
% my $smtp_mesg=l('mai_SMTP_port_authenticate');
<p><span class=label>
%= sprintf($smtp_mesg,$c->get_db_prop('qpsmtpd','TCPPort',25));
</span><span class=input>

View File

@@ -25,6 +25,12 @@
% my $btn = l('pwd_PASSWORD_CHANGE');
% my $url = '/userpassword';
%= $c->render_to_string( inline => l('pwd_DESCRIPTION'));
<p>
<%= $c->render_to_string( inline => l('pwd_PASSWORD_DESCRIPTION')) %><br />
<%= $c->render_to_string( inline => l('pwd_PASSWORD_WIKI')) %>
</p>
% if ( $pwd_datas->{trt} eq 'RESET' ) {
% $btn = l('pwd_PASSWORD_RESET');
% $url = '/userpasswordr';

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 100
%define release 108
Version: %{version}
Release: %{release}%{?dist}
License: GPL
@@ -146,6 +146,32 @@ true
%defattr(-,root,root)
%changelog
* Wed Jul 16 2025 Brian Read <brianr@koozali.org> 11.0.0-108.sme
- Password change panel - add extra descriptive text [SME: 13081]
* Tue Jul 15 2025 Brian Read <brianr@koozali.org> 11.0.0-107.sme
- Email Settings panel: Fix lex strings with long names and no prefix [SME: 13080]
* Mon Jul 14 2025 Brian Read <brianr@koozali.org> 11.0.0-106.sme
- Datetime panel: Typo in error message - missing _ [SME: 13078]
- ditto user accounts panel [SME: 13078]
- ditto groups panel [SME: 13078]
* Mon Jul 14 2025 Brian Read <brianr@koozali.org> 11.0.0-105.sme
- Adjust db opening in Software install panel [SME: 13077]
* Mon Jul 07 2025 Brian Read <brianr@koozali.org> 11.0.0-104.sme
- Fix DB not open in Workgrouyp panel sometimes. [SME: 13070]
* Thu Jul 03 2025 Brian Read <brianr@koozali.org> 11.0.0-103.sme
- Fix a couple of typos [SME: 13065]
* Thu Jul 03 2025 Brian Read <brianr@koozali.org> 11.0.0-102.sme
- Local Network panel: Make DEL and ADD robust even if called more than once by browser [SME: 13065]
* Wed Jul 02 2025 Brian Read <brianr@koozali.org> 11.0.0-101.sme
- Fix busy cursor, csp intervened. [SME: 13064]
* Tue Jul 01 2025 Brian Read <brianr@koozali.org> 11.0.0-100.sme
- Add code in top template default.html.ep to incorporate any panel specific js and css [SME: 13062]