Server Manager2 create panel for contrib

From SME Server
Jump to navigationJump to search

Introduction

Server Manager 2 is based on the perl library 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.

Initially the default theme mimics the current Server Manager pages (which is based on formMagick), however a new theme has also been developed which is based on AdminLTE.

If you follow the rules in this document, then your page should work in both themes without any trouble!

I am using the example of the DHCP Manager contrib which has a simple initial panel and 3 subsidiary panels.

Directory Structure

/usr/share/smanager
├── conf - contains preferences file.
├── data
├── lib
│   └── SrvMngr
│       ├── Controller - contains .pm files one for each module
│       ├── I18N
│       │   └── Modules
│       │       ├── Backup
...snip ....one for each module - contains translation strings
│       │       ├── Xt_geoip
│       │       └── Yum
│       ├── Model
│       └── Plugin
├── log
├── script
├── t
└── themes
    └── default
        ├── public
        │   ├── css
        │   ├── images 
        │   └── js
        └── templates - top level .html.ep file (one per theme)
            ├── layouts - contains one .html.ep file for each module
            └── partials - contains partial files - as many as required for modules 
                           (generally corresponds to subsidiary panels)

So each module (contrib in this case), consists of at least 3 files:

  1. .pm file of perl in the controller directory to gather up the content into a data structure (generally a hash or array)
  2. .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).
  3. .html.ep file in the themes/default/templates/layout directory being the top level panel plus includes to subsidiary panels.
  4. 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.

Configuration/Preferences File

This is found in /usr/share/smanager/conf/srvmngr.conf. Initial contents are:

#------------------------------------------------------------
#              !!DO NOT MODIFY THIS FILE!!
# 
# Manual changes will be lost when this file is regenerated.
#
# Please read the developer's guide, which is available
# at http://www.contribs.org/development/
#
# Copyright (C) 1999-2006 Mitel Networks Corporation
#------------------------------------------------------------
{
# configuration file for Mojolicious Server-Manager2 application
#
    secrets => ['blah blah'],
    theme => 'default',
    # session timeout
    timeout => '300',
    hypnotoad => {
    ## adresses and ports listened
        listen => ['http://127.0.0.1:982'],
        proxy  => 1,
        pid_file => '/var/run/smanager.pid',

    ## process number based on CPU number [x 2]
        workers => (`grep processor /proc/cpuinfo | wc -l` * 2),

    ## connections queue size, per worker
        accepts => 100,

    ## propriétaire et groupe du serveur
        user => 'admin',
        group => 'admin'
    },
    # is js-jquery available
    hasJquery => 1,
    modules_dir => 'lib/SrvMngr/Controller',
    webapp => 'smanager',
    mode => 'production',
    debug => 0,
}

If you change the "mode" => "development", then when you get a runtime error it will show you the context. Otherwise it just gives a generic error message.

The file is templated so every time you reload the rpm then it will get set to "production", unless you add a custom template to:

"/etc/e-smith/templates/usr/share/smanager/conf/srvmngr.conf" to override it.

Later you will see how to access the "debug" property in your perl code which can be used to switch on and off diagnostic information.

Here's the contents of my "/etc/e-smith/templates-custom/usr/share/smanager/conf/srvmngr.conf/30other":

    modules_dir => 'lib/SrvMngr/Controller',
    webapp => 'smanager',
    mode => 'development',
    debug => 1,
\}

The files we need

So, we'll create the following files for the new SM2 panel(s) for the DHCPManager contrib.

  1. /usr/share/smanager/lib/SrvMngr/Controller/Dhcpd.pm
  2. /usr/share/smanager/lib/SrvMngr/I18N/Modules/Dhcpd/dhcpd_en.lex
  3. /usr/share/smanager/themes/default/templates/dhcpd.html.ep
  4. /usr/share/smanager/themes/default/templates/partials/_dhcpd_scan.html.ep
  5. /usr/share/smanager/themes/default/templates/partials/_dhcpd_winpopup.html.ep
  6. /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.

Converting from formMagick

if you are converting an existing contrib with a formMagick panel, then you will want to use the current files as a basis for the re-write.

For the DHCP manager contrib, these are:

  1. /etc/e-smith/web/functions/dhcpd
  2. and sometimes a file in here: /usr/share/perl5/vendor_perl/esmith/FormMagick/Panel Although in this case there is not. (and I find that directory structure impossible to remember - "esmith" sans "-" throws me).
  3. and translatable strings in here: /etc/e-smith/locale/en-us/etc/e-smith/web/functions/<contrib name> the format is XML- like whereas SM2 requires a more perl l- ike structure.

I have not really understood how formMagick works, but mostly the subroutine names and code shows what is needed.

Coding Environment

I work using a "test" SME10 VM accessed through an SSH tunnel (setup in the fstab) from my Fedora/Cinnamon desktop using the "geany" editor.

Here is the fstab entry:

root@sme10.thereadclan.me.uk:/			/home/brianr/SME10 	fuse.sshfs 	rw,auto 	0	 0

I'll setup a geany project with all the above files open in tabs. Opening the editor project will open all the files in one go, assuming that the mapping to SME10 is live (sometime I have to manually mount it in Nemo, especially after the desktop has been suspended for a while).

Your mileage may of course vary!! Other editors etc are available.

I can then run the Server Manager in a browser against the SME10 VM, and make changes directly into the VM and see the results quickly.

Sometimes the changes require a:

signal-event smanager-refresh

and sometimes I need to use CTL-F5 to refresh the browser cache before seeing the result of a change (especially CSS changes)

Remember to set Mode = "development" in the mojolicious config file so that Perl run time errors are shown usefully. See above.

The Controller File (Dhcpd.pm)

Navigation Menu and Routing Tables

Here is the heading for "untested" skeleton for the controller file.:

package SrvMngr::Controller::Dhcpd;

#----------------------------------------------------------------------
# heading     : Configuration
# description : DHCP manager
# navigation  : 2000 2500
#
# name   : dhcpd,    method : get,  url : /dhcpd,     ctlact : Dhcpd#main
# name   : dhcpd1,   method : get,  url : /dhcpd1,    ctlact : Dhcpd#do_leases
# name   : dhcpd2,   method : get,  url : /dhcpd2,    ctlact : Dhcpd#do_winpopup
# name   : dhcpd3,   method : get,  url : /dhcpd3,    ctlact : Dhcpd#do_scan
# name   : dhcpd4,   method : get,  url : /dhcpd4,    ctlact : Dhcpd#do_delete_all_leases
# name   : dhcpd5,   method : post, url : /dhcpd5,    ctlact : Dhcpd#do_update_config
# name   : dhcpd6,   method : get,  url : /dhcpd6,    ctlact : Dhcpd#do_delete_one_lease
# name   : dhcpd7,   method : get,  url : /dhcpd7,    ctlact : Dhcpd#do_refresh_leases
# name   : dhcpd8,   method : post, url : /dhcpd8,    ctlact : Dhcpd#winpopup
#
# routes : end
#
#
# Documentation: https://wiki.koozali.org/Dhcpmanager
#

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.

The Navigation menu controls are described in here (search for "Navigation metadata").

The best link I can find for a description of Mojolicious routes is here.

Perl Main and sub-routines

use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller';

use constant FALSE => 0;
use constant TRUE  => 1;

use Locale::gettext;
use SrvMngr::I18N;
use SrvMngr qw(theme_list init_session);

use Data::Dumper;
use esmith::util;
use esmith::AccountsDB;

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;
our $adb = esmith::AccountsDB->open() or die("Unable to open accounts DB");

my %dhcp_data = ();

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.

In simplified terms these are the routines (as before currently untested):

sub main {
    #
    # Initial page - full summary of parameters etc
    # Initial para from the Wiki.
    #
    my $c = shift;
    $dhcp_data{"status"} = [[$c->l('dhcpd_ENABLED'),'enabled'],
                            [$c->l('dhcpd_DISABLED'),'disabled']
                            ];

	$dhcp_data{"check"}  = [[$c->l('dhcpd_ENABLED'),'enabled'],
                            [$c->l('dhcpd_DISABLED'),'disabled']
                            ];
	if (! $sme_conf{'winscustom'} ) {
		$sme_conf{'winscustom'} = 'disabled' ;
	}
	if (! $sme_conf{'dnscustom'} ) {
		$sme_conf{'dnscustom'} = 'disabled' ;
	}
	if ( ! $sme_conf{'leasetime'} )
	{ $sme_conf{'leasetime'} =  "86400" ; 
	}
	if (! $sme_conf{'gatewaycustom'} ) {
		$sme_conf{'gatewaycustom'} = 'disabled' ;
	}                           
    #$dhcp_data{"first"} = 'dhcpd_DESCRIPTION';
    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{'smbparams'} = \%smb_conf;  
    $c->stash( 	title => $title, 
				modul => $modul, 
				dhcp_data => \%dhcp_data 
				);
    $c->render( template => 'dhcpd' );

sub do_leases {
	#
	# Show a table of the leases
	#
	my $c = shift;
    my $title = $c->l("dhcp_MANAGING_DHCP_CLIENT");
    my $modul = '';
    my $trt   = "LEASES";
    $dhcp_data{trt} = $trt;
    $dhcp_data{"first"} = '';
    $dhcp_data{"leases"} = get_leases_in_array();
    $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data );
    $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 {
	#
	# call to show scan results
	#
	my $c = shift;
    my $title = $c->l("dhcp_SCANNING_NETWORK_TITLE");
    my $modul = '';
    my $trt   = "NETSCAN";
    $dhcp_data{trt} = $trt;
    $dhcp_data{"first"} = '';
	# ..... get scan results into dhcp_data
	dhcp_data{"scanresults"} = get_scan_results($c);
    $c->stash( title => $title, modul => $modul, dhcp_data => \%dhcp_data );
    $c->render( template => 'dhcpd' );
}
 
sub do_update_config {
	#
	# Update config dhcp parameters 
	# called through form submit.
	#
	my $c = shift;
	# Input results are in $c->param(<fieldname>).
	# If parameters do not validate, then return error message.
	# else write into config DB, and...
	# signal-event and ...return ok
	my $ret = update_config($c);
	if ($ret == 'ok') { 
		dhcp_data{"success"}="dhcp_CONFIG_SAVED_OK";
		do_leases($c);
	}	
	else {dhcp_data{"error"}=$ret;}
    return ;
}

sub do_delete_all_leases {
	#
	# Delete all the specified lease
	# Called from button at top of leases list panel
	#
	my $c = shift;
    my $ret = delete_all_leases($c);
	if ($ret == 'ok') { 
		dhcp_data{"success"}="dhcp_CONFIG_SAVED_OK";
		do_leases($c);
	}	
	else {dhcp_data{"error"}=$ret;}
    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 {
	my $c = shift;
	#...do it
	return "ok";
}

sub delete_one_lease {
	my $c = shift;	
    # ...do it
    return "ok";
}

sub delete_all_leases {
	my $c = shift;	
    # ...do it
    return "ok";
}

sub get_leases_in_array {
	my $c = shift;
	my @leases = [];
    # ...do it
    return @leases;
}

sub winpopup{
	my $c = shift;
	# Message in $c->param("winpopupmsg")
    # .... do it
    return "ok";
}	 

1;

The Template Files (dhcpd.html.ep and partials)

The top level .ep file starts like this:

% layout 'default', title => "Sme server 2 - DHCP Manager", share_dir => './';

% content_for 'module' => begin
<div id="module" class="module dhcpman-panel">

    % if ($config->{debug} == 1) {
	<p>
		%= dumper $c->current_route
	</p>
    % }
    
    <h1><%=$title%></h1>
     %= $modul
     
   	%if ($dhcp_data->{first}) {
	    <br><p>
		%=$c->render_to_string(inline =>$c->l($dhcp_data->{first}))
		</p>

	%}

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.

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 here and here.

Please note the initial "div" which has a class of "module" and "<modulename>-panel". This allows specific formatting in the AdminLTE theme.

Also note the use of "debug" config field. This can be used to show debug information.

The next part comprises that common success and error panels, and then the branch according to the $trt parameter which controls which panel details are shown.

	%} elsif ($dhcp_data->{success}) {
		<div class='sme-border'>
	       <h2> Operation Status Report</h2><p>
			%= $c->l($dhcp_data->{success});
			</p>
		</div>

	   %} elsif ($dhcp_data->{error}) { 
	   <div class='sme-error'>
	       <h2> Operation Status Report - error</h2><p>
			%= $c->l($dhcp_data->{error});
			</p>
    	</div>
	%}

    % if ($dhcp_data->{trt} eq 'LEASES') {
		%= include 'partials/_dhcpd_leases'
    %} elsif ($dhcp_data->{trt} eq 'WINPOPUP') {
	    %= include 'partials/_dhcpd_winpopup'
    %} elsif ($dhcp_data->{trt} eq 'SCAN') {
	    %= include 'partials/_dhcpd_scan'
	%}

and finally the front panel details is defined:

else {  					#PARAMS
	<table><tr><td>
	%= button_to $c->l('dhcpd_CONNECTED_IP') => '/dhcpd1'
	</td><td>
	%= button_to $c->l('dhcpd_SCAN_YOUR_NETWORK') => '/dhcpd3'
	</td><td>
	%= button_to $c->l('dhcpd_GLOBAL_WINPOPUP') => '/dhcpd2'
	<td>
	</tr>
    </table>     
	<hr /> 
	<h2> 
	%= $c->l("dhcpd_DHCPD_SETTINGS_TITLE")
	</h2>
	% my $btn = l('dhcpd_SAVE/RESTART');
 	%= form_for '/dhcpd5' => (method => 'POST') => begin
	<span class=label>
		%=l 'dhcpd_CHECK_CLIENT_STATUS'
	</span><span class=data>
		% param checkclientstatus=>$dhcp_data->{"params"}->{"check"};
		%=select_field  checkclientstatus=>$dhcp_data->{"check"}
	</span><br>
    <br />
	<span class=label>
		%=l 'dhcpd_STATUS_DHCP_SERVER'
	</span><span class=data>
		% param dhcpstatus=>$dhcp_data->{"params"}->{"status"};
		%=select_field  dhcpstatus=>$dhcp_data->{"status"}
	</span><br>
	
	<span class=label>
		%=l 'dhcpd_DHCP_START'
	</span><span class=data>
		% param dhcpstart=>$dhcp_data->{"params"}->{"start"};
		%=text_field  'dhcpstart'
	</span><br>
	
	<span class=label>
		%=l 'dhcpd_DHCP_END'
	</span><span class=data>
		% param dhcpend=>$dhcp_data->{"params"}->{"end"};
		%=text_field  'dhcpend'
	</span><br><br />
	====== snip =============
	%= submit_button "$btn", class => 'action'
    % end
	%}
</div>
%end

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 <span> tags and the <br> 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.

The Partials files can be used to keep the structure in mulitple files.

We will look at just one to display the lease table.

Translation Strings files

Here is a sed script and extra commands to take a formMagick strings file and make it into a lex file suitable for SM2.

Create a sed script file (cnv_lexi.vi):

/lexicon/ d
/entry/ d
s/'/\\'/g
s/<base>/'ddc_/
s/<\/base>/' => /
s/<trans>/'/
s/<\/trans>/',/
s/\t//g
s/<!\[CDATA\[//g
s/]]>//g
/^$/d
s/        '/'/g
s/         //g
s/=> \n/=> /g

Then the shell script to run it: (extractlex.sh):

#!/bin/sh
cp /etc/e-smith/locale/en-us/etc/e-smith/web/functions/$1 $1.res
sed -f cnv_lexi.vi /etc/e-smith/locale/en-us/etc/e-smith/web/functions/$1 $1.res | tr "\n" "~" | sed 's/\s+~/~/g' | sed 's/~,/,/g' | sed "s/~'~/'/g" | sed s/"~',~'/',~/g" | tr "~" "\n" > $1.lex

Run it by:

./extractlexi.sh dhcpd

You will need to edit the "ddc_" prefix in the .lex file to conform to whatever you want to use.

Then run:

/etc/e-smith/events/actions/locales2-conf

Which will loop through all the lex files and create corresponding .pm files where they do not exist. This could go in the (rpm) install inside the createlinks file (remember to delete the .pm file(s) first).

The .lex File

The .pm File

(Re)Building the RPM

smeserver-dhcpmanager.spec file

In order for the lex file to be processed, and the header on the Controller file to be processed (navigation and routing), we need to call

signal-event smanager-refresh

during the installation process. This has to be in such a way that it only happens if the new server manager has been installed (i.e. the rpm smeserver-manager installed).

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 ]
then
       /usr/sbin/e-smith/signal-event smanager-refresh
fi
true

In the fullness of time it might be better to put this in the smeserver-dhcpmanager-update event.

The Final Panels