【OpenWrt】(Luci)ModulesHowTo



HowTo: Write Modules

Note: If you plan to integrate your module into LuCI, you should read the Module Reference before.

This tutorial describes how to write your own modules for the LuCI WebUI.
For this tutorial we refer to your LuCI installation directory as lucidir (/usr/lib/lua/luci if you are working with an installed version) and assume your LuCI installation is reachable through your webserver via /cgi-bin/luci.

If you are working with the development environment replace lucidir with /path/to/your/luci/checkout/applications/myapplication/luasrc (this is a default empty module you can use for your experiments) and your LuCI installation can probably be reached via http://localhost:8080/luci/ after you ran make runhttpd.


Show me the way (The dispatching process)

To write a module you need to understand the basics of the dispatching process in LuCI.
LuCI uses a dispatching tree that will be built by executing the index-Function of every available controller.
The CGI-environment variable PATH_INFO will be used as the path in this dispatching tree, e.g.: /cgi-bin/luci/foo/bar/baz
will be resolved to foo.bar.baz

To register a function in the dispatching tree, you can use the entry-function of luci.dispatcher. It takes 4 arguments (2 are optional):

entry(path, target, title=nil, order=nil)
  • path is a table that describes the position in the dispatching tree: For example a path of {"foo", "bar", "baz"} would insert your node in foo.bar.baz.
  • target describes the action that will be taken when a user requests the node. There are several predefined ones of which the 3 most important (call, template, cbi) are described later on on this page
  • title defines the title that will be visible to the user in the menu (optional)
  • order is a number with which nodes on the same level will be sorted in the menu (optional)

You can assign more attributes by manipulating the node table returned by the entry-function. A few example attributes:

  • i18n defines which translation file should be automatically loaded when the page gets requested
  • dependent protects plugins to be called out of their context if a parent node is missing
  • leaf stops parsing the request at this node and goes no further in the dispatching tree
  • sysauth requires the user to authenticate with a given system user account


It’s all about names (Naming and the module file)

Now that you know the basics about dispatching, we can start writing modules. But before you have to choose the category and name of your new digital child.

We assume you want to create a new application myapp with a module mymodule.

So you have to create a new subdirectory lucidir/controller/myapp with a file mymodule.lua with the following content:

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

function index()

end

The first line is required for Lua to correctly identify the module and create its scope.
The index-Function will be used to register actions in the dispatching tree.


Teaching your new child (Actions)

So it is there and has a name but it has no actions.

We assume you want to reuse your module myapp.mymodule that you begun in the last step.


Actions

Reopen lucidir/controller/myapp/mymodule.lua and just add a function to it so that its content looks like this example:

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

function index()
    entry({"click", "here", "now"}, call("action_tryme"), "Click here", 10).dependent=false
end
 
function action_tryme()
    luci.http.prepare_content("text/plain")
    luci.http.write("Haha, rebooting now...")
    luci.sys.reboot()
end

And now type /cgi-bin/luci/click/here/now (http://localhost:8080/luci/click/here/now if you are using the development environment) in your browser.

You see these action functions simple have to be added to a dispatching entry.

As you might or might not know: CGI specification requires you to send a Content-Type header before you can send your content. You will find several shortcuts (like the one used above) as well as redirecting functions in the module luci.http


Views

If you only want to show the user a text or some interesting familiy photos it may be enough to use a HTML-template. These templates can also include some Lua code but be aware that writing whole office suites by only using these templates might be called “dirty” by other developers.

Now let’s create a little template lucidir/view/myapp-mymodule/helloworld.htm with the content:

<%+header%>
<h1><%:Hello World%></h1> 
<%+footer%>

and add the following line to the index-Function of your module file.

entry({"my", "new", "template"}, template("myapp-mymodule/helloworld"), "Hello world", 20).dependent=false

Now type /cgi-bin/luci/my/new/template (http://localhost:8080/luci/my/new/template if you are using the development environment) in your browser.

You may notice those fancy <% %>-Tags, these are template markups used by the LuCI template processor.
It is always good to include header and footer at the beginning and end of a template as those create the default design and menu.


CBI models

CBI1 是 LuCI 最酷的功能之一。它创建了一个 formular 基于 user interface 并且将其内容保存到特定的 UCI 配置文件。你仅需在 CBI 模型文件中描述该配置文件的结构(structure),然后 Luci 会做剩下的工作——包括了生成、解析和验证 XHTML 表单和读取/写入该 UCI 文件。

So let’s be serious at least for this paragraph and create a real pratical example lucidir/model/cbi/myapp-mymodule/netifaces.lua with the following contents:

现在,我们认真的对待这一段路,并且创建一个实例:lucidir/model/cbi/myapp-mymodule/netifaces.lua,这个文件内容:

--我们想要编辑的 UCI 配置文件:/etc/config/network
m = Map("network", "Network")

-- 特别地:"interface" 单元
s = m:section(TypedSection, "interface", "Interfaces")
-- 允许用户删除或创建单元
s.addremove = true
function s:filter(value)
   return value ~= "loopback" and value -- Don't touch loopback
end 

-- 只显示 "static"
s:depends("proto", "static")
-- 或者 "dhcp" 作为协议和 leave PPPoE and PPTP alone
s:depends("proto", "dhcp")

-- Creates an element list (select box)
p = s:option(ListValue, "proto", "Protocol")
p:value("static", "static") -- Key and value pairs
p:value("dhcp", "DHCP")
p.default = "static"

 -- This will give a simple textbox
s:option(Value, "ifname", "interface", "the physical interface to be used")

-- Yes, 这是一个 i18n(多国语言)功能 ;-)
s:option(Value, "ipaddr", translate("ip", "IP Address"))

-- You may remember this "depends" function from above
s:option(Value, "netmask", "Netmask"):depends("proto", "static")
mtu = s:option(Value, "mtu", "MTU")
mtu.optional = true -- This one is very optional

dns = s:option(Value, "dns", "DNS-Server")
dns:depends("proto", "static")
dns.optional = true
function dns:validate(value) -- Now, that's nifty, eh?
    -- Returns nil if it doesn't match otherwise returns match
    return value:match("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
end

gw = s:option(Value, "gateway", "Gateway")
gw:depends("proto", "static")
gw.rmempty = true -- Remove entry if it is empty

return m -- Returns the map

当然不要忘记在模块的 index-Function 中添加这样的东西:

entry({"admin", "network", "interfaces"}, cbi("myapp-mymodule/netifaces"), "Network interfaces", 30).dependent=false

关于 CBI 更多的功能,参阅 the CBI reference 和 LuCI 一起提供的模块。



Reference

n/a




  1. CBI – 【OpenWrt】(Luci) CBI ↩︎

发布了164 篇原创文章 · 获赞 76 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_29757283/article/details/100322195
今日推荐