LuCI 成立于 2008 年 3 月,名称为“FFLuCI”,为 OpenWrt 固件从 Whiterussian 到 Kamikaze 实现快速配置接口。用于嵌入式设备的免费、干净、可扩展且易于维护的 Web 用户界面。 LuCI 可以实现路由的网页配置界面,是 LUA 与 UCI的合体。
UCI 是 OpenWrt 中为实现 所有系统配置的一个统一接口,英文名 Unified Configuration Interface ,即统一配置接 口。
LUA(入门) 是一个小巧的脚本语言, 轻量级的官方版本只包括一个精简的核心和最基本的库。这使 得 LUA 体积小、启动速度快,从而适合嵌入在别的程序里。我们主要工作是基于 LuCI 框架编写LUA 脚本、在 html 页面中嵌入 LUA 脚本。
LuCI使用 Lua 编程语言并将界面拆分为模型和视图等逻辑部分,使用面向对象的库和模板。这确保了更高的性能、更小的安装尺寸、更快的运行时间以及更重要的是:更好的可维护性。与此同时,
LuCI 从一个 MVC-Webframework 演变为一个包含了很多库、应用程序和用户界面的集合,而重点仍然放在 Web 用户界面上,这也成为了 OpenWrt Kamikaze 的官方部分。LuCI 是一个开放源码的独立项目。参考LuCI wiki。
LuCI --->
Collections --->
<*> luci
<*> luci-ssl
Modules --->
<*> luci-compat //可以帮助解决一些兼容性问题,推荐一同安装
luci 包含依赖 +uhttpd +luci-mod-admin-full +luci-theme-bootstrap +luci-app-firewall +luci-app-opkg +luci-proto-ppp +libiwinfo-lua +IPV6:luci-proto-ipv6 +rpcd-mod-rrdns
太多,可以精简一些:
LuCI --->
Collections --->
< > luci #不需要选
Modules --->
<*> luci-compat #可以帮助解决一些兼容性问题,推荐一同安装
<*> luci-mod-admin-full #LUCI_DEPENDS:=+luci-base +luci-mod-status +luci-mod-system +luci-mod-network
Translations--->
<*> luci-i18n-chinese
<*> luci-i18n-english
Themes --->
<*> luci-theme-bootstrap
#luci Makefile LUCI_DEPENDS 包含 @BROKEN 表示此模块是不稳定的,有性能问题或存在缺陷。且不会在menuconfig中加载。
大多数 LuCI 代码位于目标系统的目录中
/usr/lib/lua/luci
,而 Web 资源(JavaScript、CSS 等)位于/www
目标系统的目录中。
我们要做的主要工作就是基于 LuCI 框架编写LUA 脚本、在 html 页面中嵌入 LUA 脚本。
参考 Adding new elements to LuCI 在 feeds/luci/applications/
创建luci-app-myapplication
应用。
# tree luci-app-myapplication
├── Makefile
└── luasrc
├── controller
│ └── myapp
│ └── new_tab.lua
├── model
│ └── cbi
│ └── myapp-module
│ └── cbi_tab.lua
└── view
└── myapp-module
└── view_tab.htm
# luci-app-myapplication 对应的 uci 配置文件
package/base-files/files/etc/config/cbi_file
Makefile
在 luci-app-myapplication 目录中添加 Makefile 文件:
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Support for Test
LUCI_DEPENDS:=
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature
cbi_file
config 'info' 'A'
option 'name' 'OpenWRT'
LuCI采用了MVC (模型/视图/控制)三层架构,在系统的
/usr/lib/lua/luci/
下有三个目录model、 view、 controller
。参考model 指的是 cbi 模型,经过高度封装直接映射 /etc/config/下的UCI文件生成页面表单,简单易用但无法自定义。
view 是可以嵌入 lua 的 HTML 文件。可以自由定义页面样式,需要前端知识。
controller 生成一些url路径,并指定这些url对应 model中的cbi生成的页面 或者 view中的html页面 或者是 call执行函数(定义在文件下方即可) 等。
可参考源码 feeds/luci/applications/luci-app-xx/luasrc/
在Luci web中注册名为"New tab"的菜单项,包含三个子项"CBI Tab",“View Tab"和"Call Tab”,分别展示CBI配置页面,视图页面和调用action页面。
注册名为 "New tab"的菜单项,设置访问权限级别为60,不依赖其他菜单项。
注册名为 "CBI Tab"的子菜单项,访问排序为1。点击时系统将调用 cbi 返回 cbi_tab.lua 模型生成的CBI配置表单页面。
注册名为 "View Tab"的子菜单项,访问排序为2。点击时系统将调用 template 返回渲染后的 view_tab.htm 视图页面。注册 set_hostname 函数,可嵌入View页面调用。
注册名为 "Call Tab"的子菜单项,访问排序为3。点击时系统将调用 action_reboot 函数,无页面。
luci-app-myapplication/luasrc/controller/myapp/new_tab.lua
文件:
module("luci.controller.myapp.new_tab", package.seeall) --路径对应
function index() --菜单项的入口函数。当用户访问这个菜单项时,系统将执行这个函数。
entry({"admin", "new_tab"}, firstchild(), "New tab", 60).dependent=false
entry({"admin", "new_tab", "tab_from_cbi"}, cbi("cbi_tab"), "CBI Tab", 1)
entry({"admin", "new_tab", "tab_from_view"}, template("view_tab"), "View Tab", 2)
entry({"admin", "new_tab", "call_act_reboot"}, call("action_reboot"), "Call Tab", 3).dependent=false
entry({"admin", "new_tab", "call_act_sethost"}, call("set_hostname"))
end
function action_reboot()
luci.http.prepare_content("text/plain")
luci.http.write("Rebooting now...")
luci.sys.reboot()
end
function set_hostname()
local hostname = luci.http.formvalue("hostname")
local executeString = "uci set system.@system[0].hostname="..hostname
luci.sys.exec(executeString)
end
luci.dispatcher
中的 entry 函数可以在dispatching tree中完成模块节点的注册。
entry(path, target, title=nil, order=nil)
- path: 虚拟路径。例如:
{"admin", "new_tab", "tab_from_cbi"}
对应/cgi-bin/luci/admin/new_tab/tab_from_cbi
。- target: 分派时要调用的目标函数。3个最重要的函数(call, template, cbi)。
- call 执行指定方法(Action)。如上述代码:
call("action_reboot")
以及定义action_reboot
函数。- template用来调用已有的htm模版,模版目录在
lua\luci\view
目录下。- cbi语句使用cbi模块,这是使用非常频繁也非常方便的模块,在cbi模块中定义各种控件,Luci系统会自动执行大部分处理工作。其链接目录在
lua\luci\model\cbi
下。- alias 指向另一菜单入口。
- arcombine 有参与无参组合调度。
- luci.dispatcher 其他函数
- title: 定义在菜单中对用户可见的标题(可选)。
- order: 菜单栏中的排序数值(可选),由上至下,由左至右,依次递增。
- 可以通过操作入口函数返回的节点表来分配更多属性。一些示例属性:
- i18n: 定义了当页面被请求时应该自动加载哪个翻译文件
- dependent: 保护插件在父节点缺失时脱离上下文调用
- leaf: 在这个节点上停止解析请求,在调度树中不再继续
- sysauth: 要求用户使用给定的系统用户帐户进行身份验证
LuCI是一个简单的基于正则表达式的模板处理器,可以将HTML文件解析为Lua函数,并允许存储预编译的模板文件。最简单的形式只是一个普通的HTML文件,将原样显示给用户。
在LuCI中每个模板都是一个具有自己作用域的对象。因此,它可以被实例化,每个实例都可以具有不同的作用域。LuCI支持几个特殊的标记。这些标记被包含在<% %>标签中。
通过在开头的
<%
后面添加-
可以去除标记之前的所有空格。在闭合%>
前面加上-
将相应地去除标记后面的所有空格。为了提升用户体验,form页应该使用ajax来传输数据。
luci-app-myapplication/luasrc/view/myapp-module/view_tab.htm
文件
<%+header%>
<p>Setting the IP addressp>
<form method="get" action="<%=luci.dispatcher.build_url("admin", "new_tab", "call_act_sethost")%>">
<input type="text" name="hostname">
<button id="submit">提交button>
<%+footer%>
语法说明
1、 包含Lua代码:
<% code %>
2、 输出变量和函数值:
<% write(value) %>
或<%=value%>
3、 包含模板:
<% include(templatesName) %>
或<%+templatesName%>
4、 翻译:
<%=translate(“Text to translate”) %>
或<%:Text to translate%>
5、 注释:
<%# comment %>
内置常量
REQUEST_URI
: 当前 URL (没有服务器部分)controller
: Luci 主调度器的路径resource
: 资源目录路径media
:活动主题目录的路径其他语法跟html和JavaScript一样。
编写 LuCI CBI 模型
luci-app-myapplication/luasrc/model/cbi/myapp-module/cbi_tab.lua
文件:
m = Map("cbi_file", translate("First Tab Form"), translate("Please fill out the form below")) -- 映射/etc/config/中的uci文件
d = m:section(TypedSection, "info", "Part A of the form")
a = d:option(Value, "name", "Name"); a.optional=false; a.rmempty = false;
return m
Map() 函数创建了一个名为 m 的表单映射对象,它将映射到 /etc/config/ 目录下的 cbi_file 文件。
m:section() 函数创建一个名为 d 的区域对象(TypedSection),将包含表单的第一个选项卡 Part A of the form。
d:option() 函数在该区域对象上创建一个名为 a 的选项(Value),它将对应表单中的一个文本输入框,用于输入名称。a.optional=false 表示选项必填,a.rmempty=false 表示必须输入值才能提交表单。
return 语句返回整个表单映射对象 m,以便在 OpenWrt LuCI 界面上显示该表单。
./scripts/feeds update luci
./scripts/feeds install -a -p luci
make menuconfig
通常可以直接在系统上编辑所有文件。但要注意,LuCI 框架在/tmp目录中缓存其 Lua 库的预编译变体,建议使用uci set luci.ccache.enable=0; uci commit luci
以禁用代码缓存以用于开发目的。
themes/luci-theme-mytheme
示例。
# tree luci-theme-mytheme
├── Makefile
├── htdocs
│ └── luci-static
│ └── mytheme
├── luasrc
│ └── view
│ └── themes
│ └── mytheme
│ ├── footer.htm
│ └── header.htm
└── root
└── etc
└── uci-defaults
└── 30_luci-theme-mytheme
Makefile
在 luci-theme-mytheme 目录中添加 Makefile 文件:
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Title of mytheme
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature
在 luasrc/view/themes/mytheme
中的 header.htm
将包含在每个呈现页面的开头,footer.htm
将包含在末尾。
所以header.htm
可能会包含DOCTYPE描述,页眉,菜单和页面布局,footer.htm
将关闭所有剩余的打开标签,并可能添加一个页脚栏。
要确保header.htm
从以下几行开始:
<%
require("luci.http").prepare_content("text/html")
-%>
这将确保内容将以正确的内容类型发送到客户端。也可以根据自己的需要调整text/html。
将任何stylesheets、Javascript、images等放入htdocs/luci-static/mytheme
。需要在页眉和页脚模板中将此目录称为:<%=media%>
。
例如:对于 htdocs/luci-static/mytheme/cascade.css
需编写:
<link rel="stylesheet" type="text/css" href="<%=media%>/cascade.css" />
为了使主题在 OpenWrt 设置页面上可选择,root/etc/uci-defaults/luci-theme-mytheme
包含以下内容的文件:
#!/bin/sh
if [ "$PKG_UPGRADE" != 1 ]; then
uci get luci.themes.Mytheme >/dev/null 2>&1 || \
uci batch <<-EOF
set luci.themes.Mytheme=/luci-static/mytheme
set luci.main.mediaurlbase=/luci-static/mytheme
commit luci
EOF
fi
exit 0
参考教程:LuCI Modules ,LuCI模块分为几个类别目录,即:
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Title of my example applications
LUCI_DEPENDS:=+some-package +libsome-library +luci-app-anotherthing
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature
如果模块中有 C(++) 代码,应该包含一个 src/
子目录,其中包含另一个支持clean
,compile
和 install target
的 Makefile。install target
应该相对于预定义的$(DESTDIR)
变量部署其文件,例如
mkdir -p $(DESTDIR)/usr/bin; cp myexecutable $(DESTDIR)/usr/bin/myexecutable
src: 该src
目录是为 C 源代码保留的。
luasrc: luasrc
包含所有 Lua 源代码文件。将根据 Make 目标自动剥离或编译,并安装在 LuCI 安装目录中。
lua: lua
相当于luasrc
但是包含lua的文件会被安装到lua文件根目录下。
htdocs: htdocs
下的所有文件将被复制到目标网络服务器的文档根目录。
root:root
下所有目录和文件将原样复制到安装目标。
dist: dist
保留给构建器来创建一个工作安装树,它将代表目标机器上的文件系统。不要将任何文件放在那里,因为它们会被删除。
ipkg: ipkg
包含 IPKG 包控制文件,如preinst
, posinst
, prerm
, postrm
. conffiles
. 有关详细信息,请参阅 IPKG 文档。
如果想将模块添加到 LuCI OpenWRT feed 中,必须将这几个部分添加到contrib/package/luci/Makefile
.
对于 Web UI 应用程序:
# A package description:
define Package/luci-app-YOURMODULE
$(call Package/luci/webtemplate)
DEPENDS+=+some-package +some-other-package
TITLE:=SHORT DESCRIPTION OF YOURMODULE
endef
# A package installation target:
define Package/luci-app-YOURMODULE/install
$(call Package/luci/install/template,$(1),applications/YOURMODULE)
endef
# A module build instruction:
ifneq ($(CONFIG_PACKAGE_luci-app-YOURMODULE),)
PKG_SELECTED_MODULES+=applications/YOURMODULE
endif
# A build package call:
$(eval $(call BuildPackage,luci-app-YOURMODULE))