How to add pages to Web Admin using events and hooks

Since ejabberd SVN r884, the Web Administration has a modularization feature that allows developers of ejabberd modules to include menu items and pages in that web.

This feature uses ejabberd events and hooks: the Web Administration runs a specific event in certain circumstances. Any module that added a hook to this event will have a chance to contribute content to the Web Admin.

Summary of available events

The events ran by the Web Admin are grouped in three types:

  • Menu events: they are called when constructing the left side menu of the Web Admin. Allows to add one or several menu items to the menu.
  • Page events: they are called when a user requests a page which URL is not recognized by the Web Admin. Allows to add new pages to the Web Admin.
  • Content events: they are called when contructing the content of some specific pages. Allows to contribute HTML code to an already existing page.

The Menu and Page events are grouped in four subtypes:

  • Main: called in the main page, not in a node or host section of the web.
  • Node: called in a node section.
  • Host: called in a section of a virtual host.
  • HostNode: called in a node section of a host section.

Currently there is only one content event:

  • User: called in the page fpr a user account.

Each event may provide different arguments, so make sure the function that hooks to an event has the correct arity. Additionally, each event expects results in a different format.

This is the specification of the existing events, the type of arguments and the type of return value that must provide:

  • webadmin_menu_main(Acc, Lang) -> [menuitem(), string()]
  • webadmin_menu_node(Acc, Node, Lang) -> [menuitem(), string()]
  • webadmin_menu_host(Acc, Host, Lang) -> [menuitem(), string()]
  • webadmin_menu_hostnode(Acc, Host, Node, Lang) -> [menuitem(), string()]
  • webadmin_page_main(Acc, Request) -> {stop, [xml()]}
  • webadmin_page_node(Acc, Node, Path, Query, Lang) -> {stop, [xml()]}
  • webadmin_page_host(Acc, Host, Request) -> {stop, [xml()]}
  • webadmin_page_hostnode(Acc, Host, Node, Path, Query, Lang) -> {stop, [xml()]}
  • webadmin_user(Acc, User, Server, Lang) -> [xml()]

Type definitions:

  • menuitem() = {path(), item_name()}
  • path() = item_name() = string()
  • xml() = {xmlelement, ..., ..., ...}

How to use this in a module

Imagine that a module wants to provide a new page in the vhost section and its corresponding menu item in the Web Admin.

The module must export two functions, one to serve the page, and the other to serve the menu item. Let's call the functions wa_page and wa_menu. The arity of each function and the format of the result depends of the event type.

When the module starts, it calls ejabberd_hooks:add to add a hook to a specific event. The arguments are:

  1. the event name
  2. the virtual host that hosts this module (optional)
  3. the name of this module
  4. the function that will try to serve this hook
  5. the priority (lower value has more priority, so it is )

When the module stops, it must also delete his hook from the event. Use ejabberd_hooks:delete for that purpose.

How to generate HTML content in Erlang's XML format

There are many macro definitions in ejabberd_web_admin.hrl that help to build HTML code. To use them, put an include declaration in your module. You may need to fix the path to the header file:


For example, this erlang code:

  ?XC("h1", "Some examples of HTML"),
  ?C("The result of 7*7 is: 49"),
  ?AC("/admin", "Go to main page")
generates this HTML:
<h1>Some examples of HTML</h1>
The result of 7*7 is: 49
<a href="/admin">Go to main page</a>

Look in ejabberd source code for more examples.

Complete example

This example module provides a complete overview, since it adds hooks to all possible events that Web Admin runs. To test it, save the content to a file called mod_example.erl. Place that file in ejabbed source code directory, compile ejabberd, enable the module and go to the Web Admin.


-export([web_menu_main/2, web_page_main/2,
        web_menu_node/3, web_page_node/5,
        web_menu_host/3, web_page_host/3,
        web_menu_hostnode/4, web_page_hostnode/6,
        start/2, stop/1]).


%% gen_mod functions

start(Host, _Opts) ->
    ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
    ejabberd_hooks:add(webadmin_menu_node, ?MODULE, web_menu_node, 50),
    ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
    ejabberd_hooks:add(webadmin_menu_hostnode, Host, ?MODULE, web_menu_hostnode, 50),
    ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
    ejabberd_hooks:add(webadmin_page_node, ?MODULE, web_page_node, 50),
    ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
    ejabberd_hooks:add(webadmin_page_hostnode, Host, ?MODULE, web_page_hostnode, 50),
    ejabberd_hooks:add(webadmin_user, Host, ?MODULE, web_user, 50),

stop(Host) ->
    ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
    ejabberd_hooks:delete(webadmin_menu_node, ?MODULE, web_menu_node, 50),
    ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
    ejabberd_hooks:delete(webadmin_menu_hostnode, Host, ?MODULE, web_menu_hostnode, 50),
    ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
    ejabberd_hooks:delete(webadmin_page_node, ?MODULE, web_page_node, 50),
    ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
    ejabberd_hooks:delete(webadmin_page_hostnode, Host, ?MODULE, web_page_hostnode, 50),
    ejabberd_hooks:delete(webadmin_user, Host, ?MODULE, web_user, 50),

%% Web Admin Menu

web_menu_main(Acc,Lang) ->
        Acc ++ [{"example", ?T("Example (Main)")}].

web_menu_node(Acc, _Node, Lang) ->
        Acc ++ [{"example", ?T("Example (Node)")}].

web_menu_host(Acc, _Host, Lang) ->
        Acc ++ [{"example", ?T("Example (Host)")}].

web_menu_hostnode(Acc, _Host, _Node, Lang) ->
        Acc ++ [{"example", ?T("Example (Host+Node)")}].

%% Web Admin Page

web_page_main(_, #request{path=["example"]} = _Request) ->
    Res = [?XC("h1", "Example page in Main section")],
    {stop, Res};
web_page_main(Acc, _) -> Acc.

web_page_node(_, _Node, ["example"], _Query, Lang) ->
    Res = [?XC("h1", "Example page in Node section")],
    {stop, Res};
web_page_node(Acc, _, _, _, _) -> Acc.

web_page_host(_, _Host,
             #request{path = ["example"],
                      lang = _Lang} = _Request) ->
    Res = [?XC("h1", "Example page in Host section")],
    {stop, Res};
web_page_host(Acc, _, _) -> Acc.

web_page_hostnode(_, _Host, _Node, ["example"], _Query, Lang ) ->
    Res = [?XC("h1", "Example page in Host+Node section")],
    {stop, Res};
web_page_hostnode(Acc, _, _, _, _, _) -> Acc.

%% Web Admin Content

web_user(Acc, User, Server, Lang) ->
        JID = jlib:make_jid(User, Server, ""),
        JID_string = jlib:jid_to_string(JID),
        Res = [?XCT("C", "The JID of this user is: " ++ JID_string)],
        Acc ++ Res.

Syndicate content