Chapter 5. Web frontend

Table of Contents

5.1. CGIs
5.2. Mason templates
5.2.1. Standard GUI elements
5.3. The menu
5.4. Summary

5.1. CGIs

The eBox administration interface is web-based, using mod_perl on top of apache. Information is fetched from the backends of the different modules by CGI scripts and it is displayed using the mason templating engine.

URLs are mapped from slash-separated paths to perl class names: the URL Network/Index is translated to the classname EBox::CGI::Network::Index. Actually, the root of the namespace is /ebox/, so the actual URL would be /ebox/Network/Index, but that part is handled automatically by the framework. So if you need to write a CGI to show some data or let the user configure something, just put your class in the EBox::CGI namespace and all matching requests will be passed on to it.

There are lots of common functionality shared by most of the CGIs in eBox: error handling, input data validation, calling mason templates, showing messages and warnings, printing the menu, etc.

For this reason there is a parent class from which all CGIs should be inherited. This class implements all those features with almost no intervention by its children, this is why many CGIs end up being around 20 lines long. All this functionality is actually split into two classes, because there is a small part of it that is specific to the client side of eBox. This part is kept in the client, and it is the class you should inherit from: EBox::CGI::ClientBase.

The other class, EBox::CGI::Base, has most of the code, and it resides in libebox so that it can be reused by the server side. EBox::CGI::ClientBase inherits from EBox::CGI::Base, so you can safely forget there are two classes and just remember the former.

Inheriting from EBox::CGI::ClientBase will give you a page with the usual title bar and menu, but an empty body. If you pass a title argument to the constructor of the parent class it will be shown too:

Example 5.1. Hello world CGI

package EBox::CGI::Hello::World;

use strict;
use warnings;

use base 'EBox::CGI::ClientBase';

use EBox::Gettext;

sub new 
{
	my $class = shift;
	my $self = $class->SUPER::new('title' => __('Hello World!'), @_);
	bless($self, $class);
	return $self;
}

In the vast majority of cases, you'll just need to implement the _process abstract method in your class to get the desired results. It is called by the parent class, which then performs different actions depending on the state of the object after the call to _process. There are certain attributes in the class you can set to change the parent's behavior. Your _process method will normally do one of these two things (maybe both):

  • Obtain information from module backend and feed it to a mason template that will display it.

  • Read input fields from the HTTP request and use them to perform some action on the module backend.

The first thing there is to know is how to make a CGI use a mason template to display stuff. It's really easy, you can just pass a template argument to the parent's constructor, or set the template attribute at any time before your _process returns. This code shows both approaches:

Example 5.2. Setting the mason template for a CGI

# Setting the template in the constructor
sub new 
{
	my $class = shift;
	my $self = $class->SUPER::new('title' => __('Hello World!'), 
					'template' => 'hello/world.mas',
					@_);
	bless($self, $class);
	return $self;
}

# Setting the template in _process
sub _process
{
	my $self = shift;
	$self->setTemplate('hello/world.mas');
}

Secondly, we need to be able to pass some data to the mason template, this is done using the params attribute. It should be an array reference holding all parameters and their names. This is the _process method for the general configuration page (the one with the port, password and language):

Example 5.3. Passing arguments to a mason template

sub _process
{
	my $self = shift;

	my $global = EBox::Global->getInstance();
	my $apache = $global->modInstance('apache');

	my @array = ();
	push(@array, 'port' => $apache->port());
	push(@array, 'lang' => EBox::locale());

	$self->{params} = \@array;
}

That's all there is to using templates from the CGIs. The details about writing them are outlined in Section 5.2.

There is another mechanism to show data to the user. You can show messages and errors by setting the msg and error attributes to whatever text you want to display. Both types of messages are displayed in a box on the top of the page. You need to be a bit careful with the error attribute, since it will be overwritten if if an exception is caught by the parent class.

Example 5.4. Setting the 'msg' attribute

sub _writeBackupToDiscAction
{
  my ($self) = @_;

  my $backup = new EBox::Backup;
  my $id     = $self->param('id');
  
  $backup->writeBackupToDisc($id);

  $self->setMsg(__('Backup was written to CD/DVD disk'));
}

Reading arguments sent in the HTTP request is very straight forward. The parent class uses the CGI perl module to access that information, and provides a wrapper around its param function that does some checks on the input data. This is the _process method in the CGI that changes the web interface tcp port:

Example 5.5. Using parameters from the HTTP request

sub _process
{
	my $self = shift;

	my $global = EBox::Global->getInstance();
	my $apache = $global->modInstance('apache');

	$apache->setPort($self->param('port'));
}

It is so simple because all checks on the data are performed by the backend (using the EBox::Validate perl module) and by the parent class. Another reason for the simplicity of the code above is that error handling is automatic.

While you do all that stuff, exceptions may be thrown. Unless you have a good reason to catch an exception (i.e. you expect it and do not want it to be displayed as an error) you'll probably be just fine with the standard error handling, so you can simply forget the fact that exceptions could be thrown and write your code as if they did not exist. The standard behavior is that exceptions that inherit from EBox::Exceptions::Internal cause a generic error message, pointing the user to the logs if they need the details. EBox::Exceptions::External subclasses are displayed verbatim, the most typical use of these exceptions is when user-provided data is validated.

The last bit you should learn is how to redirect requests to other CGIs. There are a few reasons why you could want to do that, e.g. you could write a CGI that takes some input parameters from the web browser, changes something in the module backend and then redirects the request to the CGI that displays the data. The CGI shown in Example 5.5 does not call any mason template at all, it just changes the apache port and does nothing else. We want the user to see the same page they were seeing before changing the port, with the new value. Example 5.6 shows the constructor for that CGI:

Example 5.6. Redirecting a request to a different CGI

sub new
	my $class = shift;
	my $self = $class->SUPER::new(@_);
	bless($self, $class);
	$self->setRedirect("EBox/General");
	return $self;
}

You can see that the constructor sets the redirect attribute to the URL of the page we want to redirect to. That's all there is to do, if redirect is set to some value, the parent class will do an HTTP redirect after the _process method returns.

An HTTP redirect makes the browser issue a new HTTP request, so all the status data in the old request gets lost, but there are cases when you want to keep that data for the new CGI. This could be done passing GET parameters in the redirect URL, but it is simpler to do an internal redirect, without going through the browser.

If you need to keep data, like a message or warning to the user, you can use the chain attribute. It works exactly the same way as redirect but instead of sending an HTTP response to the browser, the parent class parses the url, instantiates the matching CGI, copies all data into it and runs it. Messages and errors are copied automatically, the parameters in the HTTP request are not, since an error caused by one of them could propagate to the next CGI.

If you need to keep HTTP parameters you can use the keepParam method in the parent class. It takes the name of the parameter as an argument and adds it to the list of parameters that will be copied to the new CGI if a "chain" is performed.

Errors are a special case here. When an error happens you don't want redirects at all, as the error message would be lost. If an error happens and redirect has been set, then that value is used as if it was chain. However, sometimes you want to chain to a different CGI if there is an error, for example if the cause of the error is the absence of an input parameter necessary to show the page. If that's the case you can set the errorchain attribute, which will have a higher priority than chain and redirect if there's an error.

Finally, the last detail there is to know about CGIs is how to make i18n work properly, both in the CGIs themselves and in mason templates. Since your module will not have the same gettext domain as the eBox framework, the framework needs to know your domain so that it can set it before calling your CGI and your template. It is as simple as setting the domain attribute in your object, the "Hello world" constructor in Example 5.1 would look like this:

Example 5.7. Setting the gettext domain in a CGI

sub new 
{
	my $class = shift;
	my $self = $class->SUPER::new('title' => __('Hello World!'), @_);
	$self->{domain} = 'ebox-hello';
	bless($self, $class);
	return $self;
}

If your title is translatable you should set it again in the _process function, like:

$self->{title} = __('Hello world');
		

otherwise it will not be translated correctly.

On the other hand, note that you should never use __ with non translatable strings, like protocol names (i.e., DHCP, NTP, ...).