Changes

Jump to navigation Jump to search
no edit summary
Line 1: Line 1:  
__TOC__
 
__TOC__
   −
[[Category:Howto]] [[Category:Server Manager 2]] [[Category:SM2]] [[Category:Mojolicious]]
   
== Introduction ==
 
== Introduction ==
   −
Server Manager 2 is based on the perl library [https://mojolicious.org/ Mojolicious] and has as its central tenet that the html structure is kept separate from the content that is displayed, giving a lot of flexibility. It has a structure so that the web pages can have a theme applied independant of the content. Behind the scenes a non blocking web server does the actual work, and comes with lots of additional plugins.<br>
+
Server Manager 2 is based on the perl library [https://mojolicious.org/ Mojolicious] and has as its central tenet that the html structure is kept separate from the content that is displayed, giving a lot of flexibility. It has a structure so that the web pages can have a theme applied independant of the content. Behind the scenes a non blocking web server does the actual work, and comes with lots of additional plugins.
 +
 
 +
Some details about the implementation and installation of Server manager 2 can be found [[Server Manager2|here]]. <br>
    
Initially the default theme mimics the current Server Manager pages (which is based on [[Esmith::FormMagick|formMagick]]), however a new theme has also been developed which is  based on [https://adminlte.io/themes/dev/AdminLTE/index3.html AdminLTE].<br>
 
Initially the default theme mimics the current Server Manager pages (which is based on [[Esmith::FormMagick|formMagick]]), however a new theme has also been developed which is  based on [https://adminlte.io/themes/dev/AdminLTE/index3.html AdminLTE].<br>
Line 16: Line 17:  
File:DHCP Clients.png|alt=DHCP Leases| DHCP Leases  
 
File:DHCP Clients.png|alt=DHCP Leases| DHCP Leases  
 
</gallery>
 
</gallery>
 +
 +
=== Installing Server Manager2 ===
 +
Ultimately Server Manager 2 will be part of the base release, and installed automatically as part of the installation.
 +
 +
However (Feb 2022) until it is deemed stable enough it needs to be installed specifically from the development/testing repos:<syntaxhighlight lang="shell">
 +
yum install smeserver-manager smeserver-manager-AdminLTE js-jquery --enablerepo=smedev,smetest,smecontribs
 +
</syntaxhighlight>you can access the new server manager as follows:<syntaxhighlight lang="shell">
 +
<your server FQDN or Ip address>/smanager
 +
</syntaxhighlight>
    
=== Directory Structure ===
 
=== Directory Structure ===
Line 51: Line 61:     
# .pm file of perl in the controller directory to gather up the content into a data structure (generally a hash or array)
 
# .pm file of perl in the controller directory to gather up the content into a data structure (generally a hash or array)
# .lex or .pm in the I18n/modules directory consisting of translation strings. I think that the system will generate the .pm file from the .lex file, but will use a .pm file if it is there (need to  check this with @michel).
+
# .lex or .pm in the I18n/modules directory consisting of translation strings. The system will generate the .pm file from the .lex file, but will use a .pm file if it is there.
# .html.ep file in the themes/default/templates/layout directory being the top level panel plus includes to subsidiary panels.
+
# .html.ep file in the themes/default/templates/layout directory being the top level panel plus includes to subsidiary panels.  ep stands for "Extended Perl".  
# In addition there may be "extra" .html.ep files in the themes/default/templates/layout/partials directory, which are conditionally included in the top level panel code.
+
# In addition there may be "extra" .html.ep files in the themes/default/templates/layout/partials directory, which are conditionally included in the top level panel code.    By convention the partial files have a specific filename structure "_<module id>_<function>.html.ep". I don't think this is enforced anywhere. It looks like a convention to "underline" that fact that they are only called internally.
    
=== Configuration/Preferences File ===
 
=== Configuration/Preferences File ===
Line 120: Line 130:  
#/usr/share/smanager/lib/SrvMngr/I18N/Modules/Dhcpd/dhcpd_en.lex
 
#/usr/share/smanager/lib/SrvMngr/I18N/Modules/Dhcpd/dhcpd_en.lex
 
#/usr/share/smanager/themes/default/templates/dhcpd.html.ep
 
#/usr/share/smanager/themes/default/templates/dhcpd.html.ep
#/usr/share/smanager/themes/default/templates/partials/_dhcpd_scan.html.ep
  −
#/usr/share/smanager/themes/default/templates/partials/_dhcpd_winpopup.html.ep
   
#/usr/share/smanager/themes/default/templates/partials/_dhcpd_leases.html.ep
 
#/usr/share/smanager/themes/default/templates/partials/_dhcpd_leases.html.ep
Quite why the partials have to start with the "_" I am not sure  - looks like a convention to "underline" that fact that they are only called internally.
+
Note the initial capital letter on the file name for the controller file, and also the Translation directory.  I believe that the "automatic" navigation menu creation requires this.
 
  −
Note the initial capital letter on the file name for the controller file, and also the Translation directory.
      
== Converting from formMagick ==
 
== Converting from formMagick ==
Line 160: Line 166:     
=== Navigation Menu and Routing Tables ===
 
=== Navigation Menu and Routing Tables ===
Here is the heading for "untested" skeleton for the controller file.:<syntaxhighlight lang="perl">
+
Here is the heading for the controller file.:<syntaxhighlight lang="perl">
 
package SrvMngr::Controller::Dhcpd;
 
package SrvMngr::Controller::Dhcpd;
    
#----------------------------------------------------------------------
 
#----------------------------------------------------------------------
# heading    : Configuration
+
# heading    : Network
 
# description : DHCP manager
 
# description : DHCP manager
 
# navigation  : 2000 2500
 
# navigation  : 2000 2500
Line 176: Line 182:  
# name  : dhcpd6,  method : get,  url : /dhcpd6,    ctlact : Dhcpd#do_delete_one_lease
 
# name  : dhcpd6,  method : get,  url : /dhcpd6,    ctlact : Dhcpd#do_delete_one_lease
 
# name  : dhcpd7,  method : get,  url : /dhcpd7,    ctlact : Dhcpd#do_refresh_leases
 
# name  : dhcpd7,  method : get,  url : /dhcpd7,    ctlact : Dhcpd#do_refresh_leases
# name  : dhcpd8,  method : post, url : /dhcpd8,    ctlact : Dhcpd#winpopup
+
# name  : dhcpd8,  method : get, url : /dhcpd8,    ctlact : Dhcpd#do_winpopup
 +
# name  : dhcpd9,  method : get,  url : /dhcpd9,    ctlact : Dhcpd#do_wol
 +
# name  : dhcpd10,  method : post, url : /dhcpd10,  ctlact : Dhcpd#do_update_check
 
#
 
#
 
# routes : end
 
# routes : end
Line 183: Line 191:  
# Documentation: https://wiki.koozali.org/Dhcpmanager
 
# Documentation: https://wiki.koozali.org/Dhcpmanager
 
#
 
#
  −
   
</syntaxhighlight>The Server Manager 2 system extracts these "comments" from the header of the controller file and builds the navigation menu and the routing tables accordingly.
 
</syntaxhighlight>The Server Manager 2 system extracts these "comments" from the header of the controller file and builds the navigation menu and the routing tables accordingly.
   Line 206: Line 212:  
use Data::Dumper;
 
use Data::Dumper;
 
use esmith::util;
 
use esmith::util;
 +
use esmith::HostsDB;
 
use esmith::AccountsDB;
 
use esmith::AccountsDB;
 
+
use Net::Ping;
our $db  = esmith::ConfigDB->open() or die("Unable to open Configuration DB");
+
use esmith::util::network qw(:all);
our %sme_conf = $db->get('dhcpd')->props;
+
use Socket qw( inet_aton );
our %smb_conf = $db->get('smb')->props;
  −
our $adb = esmith::AccountsDB->open() or die("Unable to open accounts DB");
      
my %dhcp_data = ();
 
my %dhcp_data = ();
Line 217: Line 222:  
</syntaxhighlight>Necessary library units are identified and Configuration databases opened and the hash to be used to communicate with the template files is initialised.
 
</syntaxhighlight>Necessary library units are identified and Configuration databases opened and the hash to be used to communicate with the template files is initialised.
   −
As can be seen from the routing tables the following routines are specified to be the receipt of control depending on the web page user.
+
As can be seen from the routing tables the following routines are specified to be the receipt of control depending on the web page user. I
 +
 
 +
In simplified terms these are the routines.
 +
 
 +
Updated in Feb 2024 - this code is the current at this time. <syntaxhighlight lang="perl">
 +
my %dhcp_data = ();
   −
In simplified terms these are the routines (as before currently untested):<syntaxhighlight lang="perl">
   
sub main {
 
sub main {
 
     #
 
     #
Line 226: Line 235:  
     #
 
     #
 
     my $c = shift;
 
     my $c = shift;
 +
    %dhcp_data = ();
 +
do_display($c);
 +
}
 +
 +
sub do_display {
 +
    #
 +
    # Front parameters page
 +
    #
 +
    my $c = shift;
 +
    $c->app->log->info( $c->log_req );
 +
    my $title = $c->l("dhcpd_DHCP manager");
 +
    my $modul = '';
 +
    my $trt  = "SETTINGS";
 +
our $db  = esmith::ConfigDB->open() or die("Unable to open Configuration DB");
 +
our %sme_conf = $db->get('dhcpd')->props;
 +
our %smb_conf = $db->get('smb')->props;
 +
    $dhcp_data{trt} = $trt;
 
     $dhcp_data{"status"} = [[$c->l('dhcpd_ENABLED'),'enabled'],
 
     $dhcp_data{"status"} = [[$c->l('dhcpd_ENABLED'),'enabled'],
 
                             [$c->l('dhcpd_DISABLED'),'disabled']
 
                             [$c->l('dhcpd_DISABLED'),'disabled']
Line 245: Line 271:  
$sme_conf{'gatewaycustom'} = 'disabled' ;
 
$sme_conf{'gatewaycustom'} = 'disabled' ;
 
}                           
 
}                           
    #$dhcp_data{"first"} = 'dhcpd_DESCRIPTION';
+
     # Accumulate parameters for Configuration DB
    do_display( $c, %dhcp_data );
  −
}
  −
 
  −
sub do_display {
  −
    #
  −
    # Front parameters page
  −
    #
  −
    my $c = shift;
  −
    $c->app->log->info( $c->log_req );
  −
    my $title = $c->l("dhcpd_DHCP manager");
  −
    my $modul = '';
  −
    my $trt  = "SETTINGS";
  −
    $dhcp_data{trt} = $trt;
  −
     # Accumulate parameters for Configuration DB
   
     $dhcp_data{'params'} = \%sme_conf;
 
     $dhcp_data{'params'} = \%sme_conf;
 
     $dhcp_data{'smbparams'} = \%smb_conf;   
 
     $dhcp_data{'smbparams'} = \%smb_conf;   
Line 266: Line 278:  
dhcp_data => \%dhcp_data  
 
dhcp_data => \%dhcp_data  
 
);
 
);
 +
#die("here");
 
     $c->render( template => 'dhcpd' );
 
     $c->render( template => 'dhcpd' );
 +
}
    
sub do_leases {
 
sub do_leases {
Line 273: Line 287:  
#
 
#
 
my $c = shift;
 
my $c = shift;
     my $title = $c->l("dhcp_MANAGING_DHCP_CLIENT");
+
     my $title = $c->l("dhcpd_MANAGING_DHCP_CLIENT");
 
     my $modul = '';
 
     my $modul = '';
 
     my $trt  = "LEASES";
 
     my $trt  = "LEASES";
 +
  $dhcp_data{"check"}  = [[$c->l('dhcpd_ENABLED'),'enabled'],
 +
[$c->l('dhcpd_DISABLED'),'disabled']
 +
                            ];
 
     $dhcp_data{trt} = $trt;
 
     $dhcp_data{trt} = $trt;
 
     $dhcp_data{"first"} = '';
 
     $dhcp_data{"first"} = '';
     $dhcp_data{"leases"} = get_leases_in_array();
+
     my @leases = get_leases_in_array($c);
     $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data );
+
     $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data, leases=> \@leases );
 
     $c->render( template => 'dhcpd' );
 
     $c->render( template => 'dhcpd' );
 
}
 
}
     −
sub do_winpopup {
  −
#
  −
# call to win pop up
  −
#
  −
my $c = shift;
  −
    my $title = $c->l("dhcp_GLOBAL_WINPOPUP");
  −
    my $modul = '';
  −
    my $trt  = "WINPOPUP";
  −
    $dhcp_data{trt} = $trt;
  −
    $dhcp_data{"first"} = '';
  −
#..... get winpopup details
  −
    $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data );
  −
    $c->render( template => 'dhcpd' );
  −
}
      
sub do_scan {
 
sub do_scan {
Line 304: Line 306:  
#
 
#
 
my $c = shift;
 
my $c = shift;
     my $title = $c->l("dhcp_SCANNING_NETWORK_TITLE");
+
     my $title = $c->l("dhcpd_SCANNING_NETWORK_TITLE");
 
     my $modul = '';
 
     my $modul = '';
     my $trt  = "NETSCAN";
+
     my $trt  = "SCAN";
 
     $dhcp_data{trt} = $trt;
 
     $dhcp_data{trt} = $trt;
 
     $dhcp_data{"first"} = '';
 
     $dhcp_data{"first"} = '';
# ..... get scan results into dhcp_data
+
# ..... get scan results into stash for passing to html template
dhcp_data{"scanresults"} = get_scan_results($c);
+
my $dhcp_scanresults = get_scan_results($c);
     $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data );
+
     $c->stash( title => $title, modul => $modul, "dhcp_data" => \%dhcp_data, "scanresults" => $dhcp_scanresults);
 
     $c->render( template => 'dhcpd' );
 
     $c->render( template => 'dhcpd' );
 
}
 
}
Line 325: Line 327:  
# else write into config DB, and...
 
# else write into config DB, and...
 
# signal-event and ...return ok
 
# signal-event and ...return ok
my $ret = update_config($c);
+
$dhcp_data{"success"}  ="";
if ($ret == 'ok') {  
+
my $ret = Main_Save($c);
dhcp_data{"success"}="dhcp_CONFIG_SAVED_OK";
+
if ($ret eq 'ok') {  
do_leases($c);
+
$dhcp_data{"success"}="dhcpd_SUCCESSFULLY_SAVED_SETTINGS";
}
+
} else {
else {dhcp_data{"error"}=$ret;}
+
$dhcp_data{"error"}=$ret;
     return ;
+
}
 +
do_display($c);
 +
     return;
 
}
 
}
   −
sub do_delete_all_leases {
+
sub do_update_check {
#
+
#Just update the check parameter
# Delete all the specified lease
  −
# Called from button at top of leases list panel
  −
#
   
my $c = shift;
 
my $c = shift;
     my $ret = delete_all_leases($c);
+
     my $dhcpd_check = $c->param ('dhcp_check');
if ($ret == 'ok') {
+
    ###Update SME configuration dbase
dhcp_data{"success"}="dhcp_CONFIG_SAVED_OK";
+
    my $dbh_sme = esmith::ConfigDB->open('/home/e-smith/db/configuration');
do_leases($c);
+
    ##Initiate get method --> create record object
}
+
    my $sme_record  = $dbh_sme->get('dhcpd');
else {dhcp_data{"error"}=$ret;}
+
    $sme_record->set_prop('check' , $dhcpd_check);
    return ;
+
$dhcp_data{"success"}="dhcpd_SUCCESSFULLY_SAVED_SETTINGS";
 +
    do_display($c);
 +
return;
 
}
 
}
   −
sub do_delete_one_lease {
  −
#
  −
# Delete the specified lease
  −
# Called from link in table of leases
  −
#
  −
my $c = shift;
  −
    # Lease in $c->param("lease")
  −
    # Validate  - if not return error message
  −
    # delete it
  −
    # If deletion not ok return message
  −
    # else return "ok"
  −
    my $ret = delete_lease($c);
  −
if ($ret == 'ok') {
  −
dhcp_data{"success"}="dhcp_CONFIG_SAVED_OK";
  −
do_leases($c);
  −
}
  −
else {dhcp_data{"error"}=$ret;}
  −
    return ;
  −
}
     −
sub update_config {
+
</syntaxhighlight>
my $c = shift;
+
Things to notice are the use of the "Stash" to communicate between the controller and the .ep file (mainly by pointers to data structures) and the need to pass the $c data structure through the routines as a parameter.
#...do it
+
 
return "ok";
+
The Main_Save procedure is stolen almost entirely from the original formMagick code:<syntaxhighlight lang="perl">
}
+
sub Main_Save ($){
 +
 
 +
    ##Pull CGI object from parameters array
 +
    my $q = shift;
 +
 
 +
    ###Build Hash of config parameters to update from cgi submit
 +
    my $dhcpd_status    = $q->param ('dhcp_enable');
 +
    my $dhcpd_winscustom    = $q->param ('dhcp_winscustom');
 +
    my $dhcpd_check    = $q->param ('dhcp_check');
 +
    my $dhcpd_start    = $q->param ('dhcp_start');
 +
    my $dhcpd_end        = $q->param ('dhcp_end');
 +
    my $dhcpd_winsserver    = $q->param ('dhcp_winsserver');
 +
    my $dhcpd_leasetime    = $q->param ('dhcp_leasetime');
 +
    my $dhcpd_dnscustom    = $q->param ('dhcp_dnscustom');
 +
    my $dhcpd_dns1server    = $q->param ('dhcp_dns1server');
 +
    my $dhcpd_dns2server    = $q->param ('dhcp_dns2server');
 +
    my $dhcpd_dns3server    = $q->param ('dhcp_dns3server');
 +
    my $dhcpd_gatewaycustom = $q->param ('dhcp_gatewaycustom');
 +
    my $dhcpd_gateway    = $q->param ('dhcp_gateway');
 +
     
 +
    ###Update SME configuration dbase
 +
    my $dbh_sme = esmith::ConfigDB->open('/home/e-smith/db/configuration');
 +
 
 +
    ##Initiate get method --> create record object
 +
    my $sme_record  = $dbh_sme->get('dhcpd');
 +
    #get localip of server
 +
    my $local_ip    = $dbh_sme->get_value('LocalIP');
 +
 
 +
    ##Set status of service
 +
    $sme_record->set_prop('status', $dhcpd_status);
 +
    $sme_record->set_prop('check' , $dhcpd_check);
 +
    $sme_record->set_prop('winscustom', $dhcpd_winscustom);
 +
    $sme_record->set_prop('leasetime' , $dhcpd_leasetime);
 +
    $sme_record->set_prop('dnscustom' , $dhcpd_dnscustom);
 +
    $sme_record->set_prop('gatewaycustom' , $dhcpd_gatewaycustom);
 +
 
 +
    #checkip to the dhcpserver, perform the save in DB configuration or display an error if value != of a valid ip or if dhcp_start is greater than dhcp_end
 +
   
 +
   
 +
    if ($dhcpd_status eq "enabled")
 +
    {
 +
        if ( isValidIP ($dhcpd_start) && isValidIP ($dhcpd_end))
 +
        {
 +
            #check if $dhcpd_start is greater than $dhcpd_end and if yes, display an error message.
 +
         
 +
            if (inet_aton($dhcpd_start) ge inet_aton($dhcpd_end))
 +
            {
 +
            return $q->l('dhcpd_DHCP_START_GREATER_DHCP_END_ERRORS') . ' (' . $dhcpd_start . '/' . $dhcpd_end .')';
 +
            }
 +
            elsif ( ( (inet_aton($dhcpd_start) le inet_aton($local_ip) ) && ( inet_aton($dhcpd_end)) ge inet_aton($local_ip) ) )
 +
            {
 +
            #display an error if the range of dhcp server include the ip of the server address
 +
            return $q->l('dhcpd_DHCP_RANGE_MUST_NOT_INCLUDE_SERVER_IP') . ' (' . $local_ip . ')';
 +
            }
 +
            else
 +
            {
 +
            #set value
 +
            my $dhcpd_start = cleanIP($dhcpd_start);
 +
            my $dhcpd_end = cleanIP($dhcpd_end);
 +
 
 +
            $sme_record->set_prop('end', $dhcpd_end);
 +
            $sme_record->set_prop('start', $dhcpd_start);
 +
            }
 +
        } 
 +
        #if $dhcpd_start or $dhcpd_end are not valid ip then display an error
 +
        else
 +
        {
 +
        return $q->l('dhcpd_DHCP_RANGE_WITH_BAD_IP') . ' (' . $dhcpd_start . '/' . $dhcpd_end .')';
 +
        } 
 +
    } 
 +
     
 +
    #checkip to the winserver perform the save in DB configuration or display an error if value != of a valid ip
 +
    if ($dhcpd_winscustom eq "enabled")
 +
    {
 +
 
 +
        if ( isValidIP ($dhcpd_winsserver) )
 +
        {
 +
        #set value
 +
        my $dhcpd_winsserver = cleanIP($dhcpd_winsserver);
 +
        $dbh_sme->set_prop('smb','WINSServer', $dhcpd_winsserver);
 +
        } 
 +
        else
 +
        {
 +
        #if $dhcpd_winsserver is not valid ip then display an error
 +
        return $q->l('dhcpd_WINSSERVER_BAD_IP') . ' (' . $dhcpd_winsserver .')';
 +
        }
 +
    }
 +
    elsif ($dhcpd_winscustom eq "disabled")
 +
    {
 +
        my $delws = $dbh_sme->get('smb');
 +
        $delws->delete_prop('WINSServer');
 +
    }
 +
 
 +
    #checkip to the dnsserver custom, perform the save in DB configuration or display an error if value != of a valid ip
 +
    if ($dhcpd_dnscustom eq "enabled")
 +
    {
 +
        #check if $dhcpd_dns1server and ( $dhcpd_dns2server are valid ip or $dhcpd_dns2server = null )
 +
        if ( isValidIP ($dhcpd_dns1server) &&  (isValidIP($dhcpd_dns2server) || ( $dhcpd_dns2server eq "")  ) &&  (isValidIP($dhcpd_dns3server) || ( $dhcpd_dns3server eq "")  )  )
 +
        {
 +
        #set value
 +
        my $dhcpd_dns1server = cleanIP($dhcpd_dns1server);
 +
        $sme_record->set_prop('dns1server' , $dhcpd_dns1server);
 +
        my $dhcpd_dns2server = cleanIP($dhcpd_dns2server);
 +
        $sme_record->set_prop('dns2server' , $dhcpd_dns2server);
 +
        my $dhcpd_dns3server = cleanIP($dhcpd_dns3server);
 +
        $sme_record->set_prop('dns3server' , $dhcpd_dns3server);
 +
        }
 +
        else
 +
        {
 +
        ##if $dhcpd_dns1server or $dhcpd_dns2server or $dhcpd_dns3server are not valid ip then display an error
 +
        return $q->l('dhcpd_DNS_SERVER_WITH_BAD_IP') . ' (' . $dhcpd_dns1server . '/' . $dhcpd_dns2server . '/' . $dhcpd_dns3server .')';
 +
        }
 +
    }
   −
sub delete_one_lease {
+
    #checkip to the gateway_custom perform the save in DB configuration or display an error if value != of a valid ip
my $c = shift;
+
    if ($dhcpd_gatewaycustom eq "enabled")
    # ...do it
+
    {
     return "ok";
+
        if ( isValidIP ($dhcpd_gateway) )
}
+
        {
 +
        #set value
 +
        my $dhcpd_gateway = cleanIP($dhcpd_gateway);
 +
        $sme_record->set_prop('gateway' , $dhcpd_gateway);
 +
        } 
 +
        else
 +
        {       
 +
        #if $dhcpd_gateway is not valid ip then display an error
 +
        return $q->l('dhcpd_GATEWAY_BAD_IP') . ' (' . $dhcpd_gateway .')';
 +
        }
 +
     }   
 +
   
   −
sub delete_all_leases {
+
    # - 4 expand templates
my $c = shift;
+
     # changed to new sme standard signal-event
     # ...do it
+
     system ("/sbin/e-smith/signal-event","workgroup-update") == 0
     return "ok";
+
                or die "Error while saving settings: $!";
 +
  return 'ok';
 +
    exit;
 
}
 
}
   −
sub get_leases_in_array {
  −
my $c = shift;
  −
my @leases = [];
  −
    # ...do it
  −
    return @leases;
  −
}
     −
sub winpopup{
+
</syntaxhighlight>The routine extracts the input field values through the $c-<param routine and validates them, returning an error message if the validation fails and otherwise writing the results back to the DB.  
my $c = shift;
  −
# Message in $c->param("winpopupmsg")
  −
    # .... do it
  −
    return "ok";
  −
}
     −
1;
+
The Message names had to have "dhcpd_" added to them, the localise routine name needed editing. $q replaces $c in other routines for legacy reasons.  Note the return string is either an error message OR the "ok" string.
</syntaxhighlight>
      
==The Template Files (dhcpd.html.ep and partials)==
 
==The Template Files (dhcpd.html.ep and partials)==
Line 409: Line 506:     
% content_for 'module' => begin
 
% content_for 'module' => begin
 +
 
<div id="module" class="module dhcpman-panel">
 
<div id="module" class="module dhcpman-panel">
   Line 425: Line 523:  
</p>
 
</p>
   −
%}
+
</syntaxhighlight>All Mojolicious commands are indicated by a "%" in the first non space character. If the next character is an equals sign then the result of the expression is output.
+
</syntaxhighlight>All Mojolicious commands are indicated by a "%" in the first non space character. If the next character is an equals sign then the result of the expression is output. Inline commands are inside pseudo-tags "<%" and "%>".
 +
 
   −
Lines which do not start with a "%" are output anyway (and are usually htrml tags).
+
This is the best list I have found of the ep syntax:<syntaxhighlight lang="text">
 +
<% Perl code %>
 +
<%= Perl expression, replaced with XML escaped result %> Useful to be embedded in a line of html
 +
<%== Perl expression, replaced with result %> (useful if the perl code emitts html tags)
 +
<%# Comment, useful for debugging %>
 +
<%% Replaced with "<%", useful for generating templates %>
 +
% Perl code line, treated as "<% line =%>" (a full line of perl)
 +
%= Perl expression line, treated as "<%= line %>" (a full line of perl replaced by the result)
 +
%== Perl expression line, treated as "<%== line %>" (ditto, but allows html tags)
 +
%# Comment line, useful for debugging
 +
%% Replaced with "%", useful for generating templates
 +
</syntaxhighlight>Lines which do not start with a "%" are output anyway (and are usually htrml tags).
    
More details about the content of the .ep files are available [https://docs.mojolicious.org/Mojolicious/Guides/Rendering#toc here] and [https://docs.mojolicious.org/Mojolicious/Plugin/DefaultHelpers here].
 
More details about the content of the .ep files are available [https://docs.mojolicious.org/Mojolicious/Guides/Rendering#toc here] and [https://docs.mojolicious.org/Mojolicious/Plugin/DefaultHelpers here].
Line 439: Line 549:  
%} elsif ($dhcp_data->{success}) {
 
%} elsif ($dhcp_data->{success}) {
 
<div class='sme-border'>
 
<div class='sme-border'>
      <h2> Operation Status Report</h2><p>
+
      <h2> Operation Status Report - success</h2><p>
 +
<font color=green> 
 
%= $c->l($dhcp_data->{success});
 
%= $c->l($dhcp_data->{success});
 +
</font>
 
</p>
 
</p>
 
</div>
 
</div>
Line 447: Line 559:  
  <div class='sme-error'>
 
  <div class='sme-error'>
 
      <h2> Operation Status Report - error</h2><p>
 
      <h2> Operation Status Report - error</h2><p>
 +
<font color=red> 
 
%= $c->l($dhcp_data->{error});
 
%= $c->l($dhcp_data->{error});
 +
</font>
 
</p>
 
</p>
 
     </div>
 
     </div>
 +
   
 
%}
 
%}
   −
    % if ($dhcp_data->{trt} eq 'LEASES') {
+
% if ($dhcp_data->{trt} eq 'LEASES') {
 
%= include 'partials/_dhcpd_leases'
 
%= include 'partials/_dhcpd_leases'
    %} elsif ($dhcp_data->{trt} eq 'WINPOPUP') {
+
%} elsif ($dhcp_data->{trt} eq 'SCAN') {
    %= include 'partials/_dhcpd_winpopup'
+
%= include 'partials/_dhcpd_scan'
    %} elsif ($dhcp_data->{trt} eq 'SCAN') {
  −
    %= include 'partials/_dhcpd_scan'
   
%}
 
%}
 
</syntaxhighlight>and finally the front panel details is defined:
 
</syntaxhighlight>and finally the front panel details is defined:
Line 463: Line 576:  
%} else {  #PARAMS
 
%} else {  #PARAMS
 
% my $ip_regex = '^((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$';
 
% my $ip_regex = '^((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$';
<table><tr><td>
+
<table>
%= button_to $c->l('dhcpd_CONNECTED_IP') => '/dhcpd1'
+
<tr>
</td><td>
+
<td>
%= button_to $c->l('dhcpd_SCAN_YOUR_NETWORK') => '/dhcpd3'
+
%= button_to $c->l('dhcpd_CONNECTED_IP') => '/dhcpd1', onclick=>"showSpinnerLeases()", id=>"scanLeases"
</td><td>
+
<button class ="btn btn-primary spinnerButtonOverlay"  type = "submit" id="load" style="display:none">
%= button_to $c->l('dhcpd_GLOBAL_WINPOPUP') => '/dhcpd2'
+
  Scanning  <!--%= $c->l('dhcpd_CONNECTED_IP')-->
<td>
+
  <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</tr>
+
</button>
</table>    
+
</td><td>
 +
%= button_to $c->l('dhcpd_SCAN_YOUR_NETWORK') => '/dhcpd3', onclick=>"showSpinnerNetwork()", id=>"scanNetwork"
 +
<button class ="btn btn-primary spinnerButtonOverlay"  type = "submit" id="loadingNetwork" style="display:none">
 +
  Scanning  <!--%= $c->l('dhcpd_CONNECTED_IP')-->
 +
  <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
 +
</button>
 +
</td>
 +
</tr>
 +
</table>  
 
<hr />  
 
<hr />  
 
<h2>  
 
<h2>  
Line 578: Line 699:  
%= submit_button "$btn", class => 'action'
 
%= submit_button "$btn", class => 'action'
 
% end
 
% end
%}
     −
</div>
  −
%end
   
</syntaxhighlight>This shows the top few controls for the panel.  Note the use of the table to keep the buttons in a row and also the structure of each parameter row involving the <nowiki><span> tags and the <br></nowiki> to create newlines.  Use of this structure will keep your panel in line with both the default and the AdminLTE themes. You need not do this of course!  
 
</syntaxhighlight>This shows the top few controls for the panel.  Note the use of the table to keep the buttons in a row and also the structure of each parameter row involving the <nowiki><span> tags and the <br></nowiki> to create newlines.  Use of this structure will keep your panel in line with both the default and the AdminLTE themes. You need not do this of course!  
    
From the form command at the top it can be seen that clicking the "Save/Restart"  button will lead to a routing through dhcpd5 which will result in the perl sub "do_update_config" being executed, which will save all the parameters back to the  DB.
 
From the form command at the top it can be seen that clicking the "Save/Restart"  button will lead to a routing through dhcpd5 which will result in the perl sub "do_update_config" being executed, which will save all the parameters back to the  DB.
   −
=== The Partials files can be used to keep the structure in mulitple files. ===
+
=== The Partials files can be used to keep the structure in multiple files. ===
 
We will look at just one to display the lease table. <syntaxhighlight lang="perl">
 
We will look at just one to display the lease table. <syntaxhighlight lang="perl">
 
<div id='dhcpd-leases'>
 
<div id='dhcpd-leases'>
 
<table><tr><td>
 
<table><tr><td>
%= button_to $c->l('dhcpd_REFRESH') => '/dhcpd1'
+
%= button_to $c->l('dhcpd_REFRESH') => '/dhcpd1', onclick=>"showSpinnerLeases()", id=>"scanLeases"
</td><td>
+
<button class ="btn btn-primary spinnerButtonOverlay"  type = "submit" id="load" style="display:true">
%= button_to $c->l('dhcpd_REMOVE_ALL_LEASES') => '/dhcpd4'
+
  Scanning  <!--%= $c->l('dhcpd_CONNECTED_IP')-->
</td>
+
  <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</tr>
+
</button>
 +
 
 +
</td><td>
 +
%= button_to $c->l('dhcpd_REMOVE_ALL_LEASES') => '/dhcpd4'
 +
</td>
 +
</tr>
 
     </table>     
 
     </table>     
<hr />
+
 
 
% my $btn = l('dhcpd_SAVE/RESTART');
 
% my $btn = l('dhcpd_SAVE/RESTART');
%= form_for '/dhcpd5' => (method => 'POST') => begin
+
%= form_for '/dhcpd10' => (method => 'POST') => begin
 
<span class=label>
 
<span class=label>
 
%=l 'dhcpd_CHECK_CLIENT_STATUS'
 
%=l 'dhcpd_CHECK_CLIENT_STATUS'
Line 608: Line 731:  
   %= $c->l("dhcpd_SAVE_TITLE");
 
   %= $c->l("dhcpd_SAVE_TITLE");
 
<br />
 
<br />
   
%= submit_button "$btn", class => 'action'
 
%= submit_button "$btn", class => 'action'
 
     % end
 
     % end
 
     <br>
 
     <br>
 
+
<table class="sme-border TableSort"><thead>
<table class="sme-border"><tbody>
   
<tr>
 
<tr>
 
<th class='sme-border'>
 
<th class='sme-border'>
Line 637: Line 758:  
</th>
 
</th>
 
</tr>
 
</tr>
 +
</thead>
 +
<tbody>
 
         % foreach my $ip (@$leases) {
 
         % foreach my $ip (@$leases) {
 
         <tr>
 
         <tr>
Line 642: Line 765:  
%= t td => (class => 'sme-border') => $ip->{name}
 
%= t td => (class => 'sme-border') => $ip->{name}
 
<td class='sme-border'>
 
<td class='sme-border'>
<a href="/smanager/dhcpd9??state=wake_up&MAC=<%= $ip->{mac}%>&name=<%= $ip->{name}%>" onclick="Wol_confirm(event,'<%=$c->l('dhcpd_WAKING_A_REMOTE_COMPUTER')%>',this);"><%=l $ip->{wol}%></a>
+
% if ($ip->{wol} =~ /ACTIVE/) {
 +
<center><%==l $ip->{wol}%></center>
 +
%} else {
 +
<a href="/smanager/dhcpd9??state=wake_up&MAC=<%= $ip->{mac}%>&name=<%= $ip->{name}%>" onclick="Wol_confirm(event,'<%=$c->l('dhcpd_WAKING_A_REMOTE_COMPUTER')%>',this);"><center><%==l $ip->{wol}%></center></a>
 +
%}
 
</td>
 
</td>
 
%= t td => (class => 'sme-border') => $ip->{start}
 
%= t td => (class => 'sme-border') => $ip->{start}
Line 648: Line 775:  
%= t td => (class => 'sme-border') => $ip->{mac}
 
%= t td => (class => 'sme-border') => $ip->{mac}
 
<td class = 'sme-border'>
 
<td class = 'sme-border'>
<a href="/smanager/dhcpd6?trt=DEL&ip=<%= $ip->{ip}%>" onclick="Remove_lease_confirm(event,'<%=$c->l('dhcpd_REMOVE_A_DHCP_LEASE_ACTION')%>',this);"><%=l 'dhcpd_REMOVE'%></a>
+
<a href="/smanager/dhcpd6?trt=DEL&ip=<%= $ip->{ip}%>&name=<%= $ip->{name}%>" onclick="Remove_lease_confirm(event,'<%=$c->l('dhcpd_REMOVE_A_DHCP_LEASE_ACTION')%>',this);"><center><%=l 'dhcpd_REMOVE'%></center></a>
<a href="/smanager/dhcpd8?trt=WIN&ip=<%= $ip->{ip}%>" onclick="Winpop_confirm(event,'<%=$c->l('dhcpd_SENDING_A_WINPOPUP')%>',this);"><%=l 'dhcpd_WINPOPUP_ACTION'%></a>
   
</td>
 
</td>
 
</tr>
 
</tr>
Line 658: Line 784:  
   <br />
 
   <br />
 
%= button_to $c->l('dhcpd_CLICK_HERE_TO_MAIN_PANEL') => '/dhcpd'
 
%= button_to $c->l('dhcpd_CLICK_HERE_TO_MAIN_PANEL') => '/dhcpd'
<script>
+
 
//var form = document.getElementById("dhcpd-leases").getElementsByTagName('a');
+
%= javascript begin
//confirm("Form elements:"+form.length);
  −
//for (let i = 0; i<form.length; i++) {
  −
//form[i].onclick = function() {
  −
// return confirm("html:"+i);
  −
//}
  −
//}
  −
//function Wol_confirm(){
  −
// if confirm("Confirm Wake on Lan to be sent to:"+this.href) then window.location.href=''
  −
//}
   
function Wol_confirm(event,msg,current){
 
function Wol_confirm(event,msg,current){
 
const getMAC = /.*MAC\=(.*)\&name.*/;
 
const getMAC = /.*MAC\=(.*)\&name.*/;
Line 693: Line 810:  
const getIP = /.*ip\=(.*)/;
 
const getIP = /.*ip\=(.*)/;
 
var IP = current.href.match(getIP)[1];
 
var IP = current.href.match(getIP)[1];
if (prompt(msg+" IP: "+IP))
+
if (confirm(msg+" IP: "+IP))
 
{ return true;}
 
{ return true;}
 
else {event.preventDefault();return false;}
 
else {event.preventDefault();return false;}
 
}
 
}
 
 
</script>
+
%end
       
</div>
 
</div>
 +
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Note the use of Javascript to present to user with dialogs for confirmation and the WinPopup msg. This was done in the original using extra panels. Although some do not like Javascript, I feel it is here to stay and presents opportunities in terms of interfaces that are best taken advantage of. The main challenge is to work out how to pass the various perl based data to and from the JS routines.  Note I use a hidden field for this in one case. And the html request parameters for the others.  It can be a challenge to get the mixture of double and single quotes correct!
 
Note the use of Javascript to present to user with dialogs for confirmation and the WinPopup msg. This was done in the original using extra panels. Although some do not like Javascript, I feel it is here to stay and presents opportunities in terms of interfaces that are best taken advantage of. The main challenge is to work out how to pass the various perl based data to and from the JS routines.  Note I use a hidden field for this in one case. And the html request parameters for the others.  It can be a challenge to get the mixture of double and single quotes correct!
 +
 +
==== Adding Javascript and CSS to the .ep file ====
 +
You can use the tag helpers "Javascript" and "Stylesheet"to add inline content to a .ep file, here is an example from the DHCPManager to add notification that the scan is being performed.  Under the AdminLTE theme, the button is changed to show a "Scanning" text and also a spinner. In the default theme thje text is shown, but the AdminLTE/Bootstrap spinner classes are ignored.
 +
 +
This code fits in between the final  html tags and the "%end" command:<syntaxhighlight lang="css">
 +
%= javascript begin
 +
  document.getElementById("load").style.display="none";
 +
  function showSpinner(){
 +
  document.getElementById("scanLeases").style.display="none";
 +
  document.getElementById("load").style.display="inline";
 +
  }
 +
%end
 +
 +
%= stylesheet begin
 +
.spinnerButtonOverlay,
 +
.spinnerButtonOverlay:hover,
 +
.spinnerButtonOverlay:any-link ,
 +
.spinnerButtonOverlay:focus ,
 +
.spinnerButtonOverlay:active {
 +
appearance: auto;
 +
    user-select: none;
 +
    align-items: flex-start;
 +
    cursor: default;
 +
    box-sizing: border-box;
 +
    background-color: #efefef;
 +
    color: black;
 +
    padding: 1px 6px;
 +
    border-width: 2px;
 +
    border-style: outset;
 +
    border-color: darkgrey;
 +
    border-image: initial;
 +
}
 +
%end
 +
</syntaxhighlight>For completion sake here is the replacement button (with AdminLTE spinner) html/mojolicious helper tags:<syntaxhighlight lang="html">
 +
%= button_to $c->l('dhcpd_CONNECTED_IP') => '/dhcpd1', onclick=>"showSpinner()", id=>"scanLeases"
 +
<button class ="btn btn-primary spinnerButtonOverlay"  type = "submit" id="load" style="display:true">
 +
  Scanning  <!--%= $c->l('dhcpd_CONNECTED_IP')-->
 +
  <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
 +
</button>
 +
 +
</syntaxhighlight>Note the Id codes used to allow the JS to find the controls.
    
===Translation Strings files===
 
===Translation Strings files===
Line 739: Line 898:     
=== The .lex File ===
 
=== The .lex File ===
 +
The .lex file (dhcpd_en.lex) contains just the mappings between the language independant messages and the actual messages (in this case in English):<syntaxhighlight lang="text">
 +
'dhcpd_DHCP manager' =>
 +
'DHCP Manager',
 +
'dhcpd_DHCPD_TITLE' =>
 +
'DHCP Manager',
 +
'dhcpd_DHCPD_SETTINGS_TITLE' =>
 +
'Settings of the DHCP server',
 +
'dhcpd_CONNECTED_IP' =>
 +
'Show DHCP Clients',
 +
'dhcpd_SCAN_YOUR_NETWORK' =>
 +
'Scan your network',
 +
'dhcpd_GLOBAL_WINPOPUP' =>
 +
'Send Global Netsend WinPopup',
 +
'dhcpd_CHECK_CLIENT_STATUS' =>
 +
'Always check the status of computers (Disabled is much faster)',
 +
 +
</syntaxhighlight>
    
=== The .pm File ===
 
=== The .pm File ===
 +
The corresponding .pm file is created by the installation procedure (see below).<syntaxhighlight lang="perl">
 +
package SrvMngr::I18N::Modules::Dhcpd::en;
 +
use strict;
 +
use warnings;
 +
use utf8;
 +
use Mojo::Base 'SrvMngr::I18N';
 +
 +
use SrvMngr::I18N::Modules::General::en;
 +
 +
my %lexicon = (
 +
'dhcpd_DHCP manager' =>
 +
'DHCP Manager',
 +
'dhcpd_DHCPD_TITLE' =>
 +
'DHCP Manager',
 +
'dhcpd_DHCPD_SETTINGS_TITLE' =>
 +
'Settings of the DHCP server',
 +
'dhcpd_CONNECTED_IP' =>
 +
'Show DHCP Clients',
 +
'dhcpd_SCAN_YOUR_NETWORK' =>
 +
'Scan your network',
 +
'dhcpd_GLOBAL_WINPOPUP' =>
 +
'Send Global Netsend WinPopup',
 +
'dhcpd_CHECK_CLIENT_STATUS' =>
 +
'Always check the status of computers (Disabled is much faster)',
 +
 +
</syntaxhighlight>The file is genuine perl and forms part of the program code for the package.
    
== (Re)Building the RPM==
 
== (Re)Building the RPM==
Line 752: Line 954:  
My current way to do this is to put the following code in the "%post" section of the .spec file:
 
My current way to do this is to put the following code in the "%post" section of the .spec file:
   −
  if [ -f /usr/share/smanager/lib/SrvMngr.pm ]
+
  if (systemctl list-unit-files |grep smanager) then
then
+
    echo "Smanager restart in spec file"  /sbin/e-smith/signal-event smanager-refresh
        /usr/sbin/e-smith/signal-event smanager-refresh
   
  fi
 
  fi
true
      
In the fullness of time it might be better to put this in the smeserver-dhcpmanager-update event.
 
In the fullness of time it might be better to put this in the smeserver-dhcpmanager-update event.
Line 766: Line 966:  
File:Screenshot from 2022-02-03 11-03-55.png
 
File:Screenshot from 2022-02-03 11-03-55.png
 
</gallery>
 
</gallery>
[[Category:Developer]]
+
 
 +
=== What needs to be done ===
 +
As part of the initial development all the base panels have been converted. Some of the "most" popular contribs have also been done (although we do not have any way of really knowing which is the most popular).
 +
 
 +
So many of the well known contribs have not been done. 
 +
 
 +
Ones that have been done (Feb 2024) are:
 +
 
 +
*smeserver-BackupPC (but not entirely - bug 11701)
 +
*smeserver-awstats
 +
*smeserver-certificate
 +
*smeserver-ddclient
 +
*smeserver-dhcpmanager
 +
*smeserver-domains (this one may not be finished)
 +
*smeserver-durep
 +
*smeserver-fail2ban
 +
*smeserver-geneweb
 +
*smeserver-qmHandle
 +
*smeserver-vacation
 +
*smeserver-wbl
 +
*smeserver-webhosting
 +
*smeserver-wireguard
 +
*smeserver-xt_geoip
 +
 
 +
 
 +
I suggest you take one of the contribs you currently use and give it a go!! Check with the dev team and on the [https://forums.koozali.org/index.php/board,36.0.html forum] in case someone else is working on your choice though.
 +
 
 +
We can provide you with support through the [https://chat.reetspetit.info/ rocket chat] hosted by John Crisp and/or the forum - ask on the forum to be added if you do not already have an account .  Start by opening a [http://bugs.koozali.org/enter_bug.cgi?product=SME%20Contribs&component=&short_desc=Add&#x20;code&#x20;for&#x20;SM2&#x20;panels&comment= bug] for the changes.
 +
 
 +
This [[:Category:Contrib#In%20production|link]] will give you a list of contribs that have been  released for SME10.
 +
 
 +
===Bugs===
 +
Please raise bugs under the SME-Server section in {{BugzillaFileBug|product=SME%20Server%2010.x|component=server-manager|title= bugzilla}}
 +
and select the {{#var:smecontribname}} component or use {{BugzillaFileBug|product=SME%20Server%2010.x|component=server-manager|title=this link}}
 +
 
 +
Below is an overview of the current issues for this package:{{#bugzilla:columns=id,product,version,status,summary|sort=id|order=desc|component=server-manager|noresultsmessage=No open bugs found.}}
 +
 
 +
 
 +
===Changelog===
 +
Only released version in smeserver are listed here.
 +
 
 +
{{#smechangelog: {{#var:smecontribname}} }}
 +
 
 +
 
 +
<!-- list of category you want to see this page in -->
 +
[[Category:SME Server]][[Category:Administration]][[Category:Server Manager 2]] [[Category:SM2]] [[Category:Mojolicious]] [[Category:Developer]]

Navigation menu