[luci] Adding a page to luci

Andrew Peebles peebles at cortina-systems.com
Wed Jan 9 03:34:53 CET 2013

On 01/08/2013 05:19 PM, Nguye^~n Ho^`ng Quân wrote:
> Hello,
> You can see my mod as a reference https://github.com/hongquan/KikiAuth
> I started from this doc: 
> http://luci.subsignal.org/trac/wiki/Documentation/ModulesHowTo
> then http://luci.subsignal.org/trac/wiki/Documentation/CBI
> ...
> Hope you see it helpful.
> ***********************************************
> * Nguye^~n Ho^`ng Quân                            *
> * Y!M: ng_hquan_vn                            *
> * Identi.ca: hongquan                         *
> ***********************************************
> 2013/1/9 Frank Parker <mr.frank.parker at gmail.com 
> <mailto:mr.frank.parker at gmail.com>>
>     If someone here is willing to walk me through the steps required
>     to customize my luci interface, I would be happy to document it.
>      Seems useful since this is a common newbie question.
>     My mod would be adding a single page with a single textbox that
>     gets saved to /etc/config/myapp
>     So for example, under System I would like to add a new tab called
>     "MyApp" that displays a page with a single textbox.  The value in
>     the textbox gets saved to /etc/config/myapp.  That's it.
>     For bonus points, I also need a single button on the same page
>     that will run a local script when clicked.
>     On OpenWrt Backfire 10.03.1, I am hunting around in
>     /usr/lib/lua/luci/* but not having much luck.
>     Anyone?
>     _______________________________________________
>     luci mailing list
>     luci at lists.subsignal.org <mailto:luci at lists.subsignal.org>
>     https://lists.subsignal.org/mailman/listinfo/luci
> _______________________________________________
> luci mailing list
> luci at lists.subsignal.org
> https://lists.subsignal.org/mailman/listinfo/luci
I find that it easier, and more portable, to create a new package. The 
instructions below do not include code/support for generating i18n 
language files, but that can be wedged in.

First create a directory for the new package.  Easiest is:

   mkdir -p package/luci-app-myapp/files/controller
   mkdir -p package/luci-app-myapp/files/model/cbi
   mkdir -p package/luci-app-myapp/files/uci-defaults

This example will assume there is already a package called "myapp", 
which installs its own /etc/init.d/myapp and /etc/config/myapp. That is 
the typical break-out, so that myapp can be used in a system even 
without a GUI.   The package/luci-app-myapp/Makefile would look like:

include $(TOPDIR)/rules.mk


include $(INCLUDE_DIR)/package.mk

define Package/luci-app-myapp
   SUBMENU:=3. Applications
   TITLE:=GUI for myapp package

define Build/Configure

define Build/Compile

define Package/luci-app-myapp/install
         $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller
         $(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi
         $(INSTALL_DIR) $(1)/etc/uci-defaults
         $(CP) ./files/controller/myapp.lua \
         $(CP) ./files/model/cbi/myapp.lua \
         $(CP) ./files/uci-defaults/luci-app-myapp \

define Package/luci-app-myapp/postinst
[ -n "$${IPKG_INSTROOT}" ] || {
         if [ -f /etc/uci-defaults/luci-app-myapp ]; then
             ( . /etc/uci-defaults/luci-app-myapp ) && rm -f 

$(eval $(call BuildPackage,luci-app-myapp))

Some interesting points above: DEPENDS makes sure that "mayapp" is 
installed and selected for build when this package is selected.  In the 
install macro, the three pertinent files (the controller, the model and 
the uci-defaults will get installed into the correct LuCI runtime 
hierarchy.  The postinst thing will run the luci-app-myapp script on the 
target at installation time.  That script should look like this:


uci -q batch <<-EOF >/dev/null
         delete ucitrack. at myapp[-1]
         add ucitrack myapp
         set ucitrack. at myapp[-1].init=myapp
         commit ucitrack

rm -f /tmp/luci-indexcache
exit 0

This is the magic that ends up calling /etc/init.d/myapp when you click 
on the Save&Apply button in LuCI.

Ok, so the controller (controller/myapp.lua) would look like this:

module("luci.controller.myapp", package.seeall)

function index()
    -- dont show unless myapp package is installed
    if not nixio.fs.access("/etc/config/myapp") then

    local page

    -- install this page into the hierarchy under admin -> services
    page = entry({"admin", "services", "myapp"},
                 _("My Application"))

    -- the i18n file's base name
    page.i18n = "myapp"
    page.dependent = true

And the model (model/cbi/myapp.lua) would look like:

-- "myapp" here refers to the name of the file in /etc/config
m = Map("myapp", translate("My Application"),
         translate("Some optional additional text..."))

-- this refers to a section in the config file.
s = m:section( NamedSection, "configuration", "myapp", 

-- and then the options in that section
o = s:option( Value, "field", translate( "Field" ))

-- we're done!
return m

The way this model is written expects a /etc/config/myapp file that 
looks like this:

config myapp configuration
   option field 'some_initial_value'

At this point you should be able to "make menuconfig", select your new 
package, and "make".  I typed this entirely in my email client without 
any actual verification, so there may be minor syntax errors, etc, and I 
apologize for that in advance.

As for the bonus question about the button; as I know how to do things, 
that is a fairly advanced topic.  Its a little rough mixing cbi models 
and gui stuff that is not part of the /etc/config file in question.  cbi 
models are fantastic if all you want to do is provide gui for config.  
But, it is possible.  It involves creating another option of type 
DummyValue and creating a custom template for it. The template goes into 
view/myapp/myview.htm.  This template can paint any picture it wants.  
You can place a button with an onclick javascript call or an href that 
calls a function defined in the controller.  You can even use ajax to 
get json back from the call. I'll outline this from memory, so it might 
also be a bit buggy, but maybe enough to set you down the right path:

In the controller, add a new entry that looks like this (in the index() 

    entry({"admin", "services", "myapp", "func"},
          call("action_func")).leaf = true

Now, in the same file, add a new function:

function action_func()
     -- obtain any arguments passed (form entries or ajax data)
     local field1 = luci.http.formvalue("field1")
     -- do something, more interesting ...
     -- then return json
     luci.http.write_json({return_value = field1})

You can define as many of these entry points as you want.  Now, in the 
model file, add another option, something like this:

     o = s:option(DummyValue, "_val", translate("Title"))
     o.template = "myapp/myview.htm"  -- is the html making up the gui

I *think* this is all you need.  You then need to create the template 
(and fix your Makefile to install it in the proper place). It might look 

<button onclick="doit();">My Button</button>
function doit() {
     url: '<%=luci.dispatcher.build_url("admin", "services", "myapp", 
     data: {field1: 'it works!'},
     dataType: 'json',
     success: function(json) {
         alert( json.return_value);
     error: function(e, m) {
         alert("Crap, spoke too soon: " + m);

Of course, that assumes you have jQuery installed and available (which 
you can do here in this template).  Or you can use the xhr.js that is 
already available in LuCI themes.  But you get the idea, I hope.

That has some chance of working.  If its horribly broken and you can't 
untangle it, lemme know and I'll actually try it and debug it for you.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.subsignal.org/pipermail/luci/attachments/20130108/a7d65140/attachment-0001.html>

More information about the luci mailing list