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, ...).