openwrt LuCI

openwrt LuCI

LuCI 介绍

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

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 应用模块

大多数 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'

MVC文件

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/

controller控制器

在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: 要求用户使用给定的系统用户帐户进行身份验证
view 视图文件

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一样。

model 模型文件

编写 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以禁用代码缓存以用于开发目的。

新建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模块分为几个类别目录,即:

  • applications(其他模块或应用程序的单个应用程序或插件)
  • i18n(翻译文件)
  • libs(独立库)
  • modules(应用程序的集合)
  • themes(前端主题)

模块目录

  • Makefile : 如果模块只包含 Lua 源代码或资源,那么下面的 Makefile 就足够了。
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 ,compileinstall 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 文档。

feed 整合

如果想将模块添加到 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))

其他参考链接

  • LuCI 0.10.x 的重要变化
  • HowTo: 使用 JSON-RPC API
  • 参考:LuCI API文档(Lua)
  • 参考:LuCI JavaScript API 文档(客户端)
  • 教程:i18n
  • 参考:LMO文件格式

你可能感兴趣的:(lua,openwrt,luci)