转自:/initphp/article/details/17527639
LUCI这个在百度上搜索除了一篇我的百度文库luci的介绍文章之外,前三页都是些不知所云的名词(足见百度在专业领域的搜索之烂),我却在大学毕业的大半年的大部分时间里与它纠结,由于开始的发懵到后来逐渐感觉到这家伙还很好玩的,现在就把我对luci的浅显认识介绍给大家。
官网:/
有关luci的各个方面,你几乎都可以从这里获得,当然,只是浅显的获得,luci的文档写的还算比较全,但是写的稍显简略,开始看的时候会有一点不知所措。
UCI熟悉openwrt的人都会有所了解,就是UnifiedConfigurationInterface的简称,而luci这个openwrt上的默认web系统,是一个独立的由严谨的德国人开发的web框架,是LuaConfigurationInterface的简称,如果在您的应用里,luci是对openwrt的服务,我们就有必要做一下uci的简介,我这里就不说了,见链接:
.hk/url?sa=t&source=web&cd=5&ved=0CEMQFjAE&url=http://nbd.name/openwrt-fosdem-09.pdf&ei=h52iTcXvOcrMcJ-xxOwD&usg=AFQjCNGFhumCIgS5tK_mDJ2dDFU4qsskfQ
有的时候,我们开发的luci是在自己的LinuxPC上开发,在普通的linux上,一般是没有uci命令的,为了开发方便,可以手动编译一下,方法见链接:
/viewtopic.php?id=15243
OK,之前罗里罗嗦的说了很多,现在就进入正题,进入正题的前提是你已经makeinstall正确的安装了lua,luci,以及编译好链接了相关的so(如果你需要,比如uci.sonixio.so),以及makeinstall正确webserver,(我用的webserver是thttpd,也编译过mongoose,lighttpd,在这三个之中,lighttpd是功能最完善的,mongoose是最小巧的)。
进入正题:
一:luci的启动
在webserver中的cgi-bin目录下,运行luci文件(权限一般是755),luci的代码如下:
#!/usr/bin/lua--cgi的执行命令的路径require"luci.cacheloader"--导入cacheloader包require"luci.sgi.cgi"--导入sgi.cgi包luci.dispatcher.indexcache="/tmp/luci-indexcache"--cache缓存路径地址luci.sgi.cgi.run()--执行run方法,此方法位于*/luci/sgi/cgi.lua中
run方法的主要任务就是在安全的环境中打开开始页面(登录页面),在run中,最主要的功能还是在dispatch.lua中完成。
运行luci之后,就会出现登录界面:
-bash-4.0#pwd/var/www/cgi-bin-bash-4.0#./luciStatus:200OKContent-Type:text/html;charset=utf-8Cache-Control:no-cacheExpires:0<!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.01//EN""/TR/html4/strict.dtd"><htmlclass="ext-strict"></html>
二:LUCI的MVC
1:用户管理:
在luci的官方网站说明了luci是一个MVC架构的框架,这个MVC做的可扩展性很好,可以完全的统一的写自己的html网页,而且他对shell的支持相当的到位,(因为luci是lua写的,lua是C的儿子嘛,与shell是兄弟)。在登录界面用户名的选择很重要,luci是一个单用户框架,公用的模块放置在*/luci/controller/下面,各个用户的模块放置在*/luci/controller/下面对应的文件夹里面,比如admin登录,最终的页面只显示/luci/controller/admin下面的菜单。这样既有效的管理了不同管理员的权限。
2:controller文件夹下的lua文件说明:(以mini用户为例)
在mini目录下面,会有一个index.lua文件,简略的代码如下:
module("luci.controller.mini.index",package.seeall)1718functionindex()19luci.i18n.loadc("admin-core")20locali18n=luci.i18n.translate2122localroot=node()23ifnotroot.lockthen24root.target=alias("mini")25root.index=true26end2728entry({"about"},template("about")).i18n="admin-core"2930localpage=entry({"mini"},alias("mini","index"),i18n("essentials","Essentials"),10)31page.i18n="admin-core"32page.sysauth="root"33page.sysauth_authenticator="htmlauth"34page.index=true3536entry({"mini","index"},alias("mini","index","index"),i18n("overview"),10).index=true37entry({"mini","index","index"},form("mini/index"),i18n("general"),1).ignoreindex=true38entry({"mini","index","luci"},cbi("mini/luci",{autoapply=true}),i18n("settings"),10)39entry({"mini","index","logout"},call("action_logout"),i18n("logout"))40end4142functionaction_logout()43luci.http.header("Set-Cookie","sysauth=;path=/")44luci.http.redirect(luci.dispatcher.build_url())45end
这个文件定义了node,最外面的节点,最上层菜单的显示等等。在其他的lua文件里,定义了其他菜单的显示和html以及业务处理路径。每个文件对应一个菜单相。
例如system.lua文件
functionindex()19luci.i18n.loadc("admin-core")20locali18n=luci.i18n.translate2122entry({"mini","system"},alias("mini","system","index"),i18n("system"),40).index=true23entry({"mini","system","index"},cbi("mini/system",{autoapply=true}),i18n("general"),1)24entry({"mini","system","passwd"},form("mini/passwd"),i18n("a_s_changepw"),10)25entry({"mini","system","backup"},call("action_backup"),i18n("a_s_backup"),80)26entry({"mini","system","upgrade"},call("action_upgrade"),i18n("a_s_flash"),90)27entry({"mini","system","reboot"},call("action_reboot"),i18n("reboot"),100)28end
mudel是对应文件的,functionindex定义了菜单,比如这一句entry({"mini","system","reboot"},call("action_reboot"),i18n("reboot"),100)
第1项为菜单入口:
{"mini","system","reboot"},mini是最上层的菜单,即为用户项,system为一个具体的菜单,reboot为这个菜单的子菜单,如果reboot还需要加上子菜单的话,可以这样写:
entry({"mini","system","reboot","chreboot"},call("action_chreboot"),i18n("chreboot"),1),这样就会在reboot上产生一个新的子菜单,以此类推,可以产生N层菜单。
第二项为菜单对应的页面,可以是lua的源代码文件,也可以是html页面。
aliascgiformcall等定义了此菜单相应的处理方式,form和cgi对应到model/cbi相应的目录下面,那里面是对应的定制好的html和lua业务处理。
alias是等同于别的链接,call调用了相应的action_function。还有一种调用,是template,是直接链接到view相应目录下面的htm页面。(说明:luci框架下面的htm都是可以嵌入lua语句的,做业务处理,相当于jsp页面的内部的Java语句)。
问价查找对应简介:
entry({"mini","system","reboot"},call("action_reboot"),i18n("reboot"),100):对应此文件的action_rebootfunction
entry({"mini","system","index"},cbi("mini/system",{autoapply=true}),i18n("general"),1):对应*/model/cbi/mini/system.lua{autoapply=true}这个失传的参数。
。。。。。
第三项为i18n显示,比如entry({"mini","system","reboot"},call("action_reboot"),i18n("reboot"),100),菜单的名字为admin-core文件内的对应显示。此处也可以这样写,i18n("reboot","重启"),即直接做了国际化。菜单上显示的就是“重启”。
第四项为现实的顺序,这个数字越小,显示越靠前,靠上。
现在说一下这些文件的解析是怎么解析的呢?你当然是说dispatch.lua中,你说对了,但是真正解析成菜单的递归算法确实在header.htm中位置:*/view/themes/openwrt/ 代码如下: <%require("luci.sys")localload1,load5,load15=luci.sys.loadavg()localrequest=require("luci.dispatcher").context.pathlocalcategory=request[1]localtree=luci.dispatcher.node()localcattree=categoryandluci.dispatcher.node(category)localnode=luci.dispatcher.context.dispatchedlocalhostname=luci.sys.hostname()localc=treefori,rinipairs(request)doifc.nodesandc.nodes[r]thenc=c.nodes[r]c._menu_selected=trueendendrequire("luci.i18n").loadc("default")require("luci.http").prepare_content("application/xhtml+xml")-%><?xmlversion="1.0"encoding="utf-8"?><!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Strict//EN""/TR/xhtml1/DTD/xhtml1-strict.dtd"><htmlxmlns="/1999/xhtml"xml:lang="<%=luci.i18n.context.lang%>"lang="<%=luci.i18n.context.lang%>"><head><metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/><metahttp-equiv="Content-Script-Type"content="text/javascript"/><linkrel="stylesheet"type="text/css"media="screen"href="<%=media%>/cascade.css"/><!--[ifltIE7]><linkrel="stylesheet"type="text/css"media="screen"href="<%=media%>/ie6.css"/><![endif]--><!--[ifIE7]><linkrel="stylesheet"type="text/css"media="screen"href="<%=media%>/ie7.css"/><![endif]--><%ifnodeandnode.cssthen%><linkrel="stylesheet"type="text/css"media="screen"href="<%=resource%>/<%=node.css%>"/><%end-%><mce:scripttype="text/javascript"src="<%=resource%><!--/VarType.js">//--></mce:script><mce:scripttype="text/javascript"src="<%=resource%><!--/XHTML1.js">//--></mce:script><mce:scripttype="text/javascript"src="<%=resource%><!--/Dropdowns.js">//--></mce:script><title><%=striptags(hostname..((nodeandnode.title)and'-'..node.titleor''))%>-LuCI</title></head><bodyclass="lang_<%=luci.i18n.context.lang%>"><pclass="skiplink"><spanid="skiplink1"><ahref="#navigation"mce_href="#navigation"><%:skiplink1Skiptonavigation%></a></span><spanid="skiplink2"><ahref="#content"mce_href="#content"><%:skiplink2Skiptocontent%></a></span></p><divid="header"><h1><%=luci.version.distname%></h1><p><%=luci.version.distversion%><br/><%:load%>:<%="%.2f"%load1%><%="%.2f"%load5%><%="%.2f"%load15%><br/><%:hostname%>:<%=hostname%></p></div><divid="menubar"><h2class="navigation"><aid="navigation"name="navigation"><%:navigationNavigation%></a></h2><ulid="mainmenu"class="dropdowns"><%-localfunctionsubmenu(prefix,node)ifnotnode.nodesornode.hiddenthenreturnfalseendlocalindex={}localcount=0fork,ninpairs(node.nodes)doifn.titleandn.targetthentable.insert(index,{name=k,order=n.orderor100})count=count+1endendtable.sort(index,function(a,b)returna.order<b.orderend)ifcount>0then%><ulid="submenu_<%=string.gsub(string.gsub(prefix,"/","_"),"^_(.-)_$","%1")%>"><%-forj,vinpairs(index)doif#v.name>0thenlocalnnode=node.nodes[v.name]localhref=controller..prefix..v.name.."/"href=(nnode.query)andhref..luci.http.build_querystring(nnode.query)orhrefifnnode.nodesthenfork1,n1inpairs(nnode.nodes)dohref="#"endend%><li><a<%ifnnode._menu_selectedthen%>class="active"<%end%>href="<%=luci.util.pcdata(href)%>"><%=nnode.title%></a><%-submenu(prefix..v.name.."/",nnode)%></li><%-endend%></ul><%endend
3:model业务处理和页面生成简介
我认为model的业务处理和html生成,是luci框架的精华,但是想用好它,最终扩展定义自己的页面也是最难的,但是一旦定义好了,后面的工作就会轻松高效简介统一,不失为一种好的解决方案。但是它又有缺点,就是写页面虽然统一,但是不够灵活。
下面以SimpleForm为例,讲解一下。
具体文件*/luci/model/cbi/passwd.lua
f=SimpleForm("password",translate("a_s_changepw"),translate("a_s_changepw1"))--调用SimpleForm页面当然还是I18N从中捣乱,看上去没那么直观,不理他
pw1=f:field(Value,"pw1",translate("password"))--
SimpleForm里面加一个field至于SimpleForm和fiemd是什么,一会去看SimpleForm页面去
pw1.rmempty=false--把SimpleForm的rmempty为不显示后面就不做注释了应该看得懂了
pw2=f:field(Value,"pw2",translate("confirmation"))pw2.rmempty=falsefunctionpw2.validate(self,value,section)returnpw1:formvalue(section)==valueandvalueendfunctionf.handle(self,state,data)ifstate==FORM_VALIDthen--这个就是业务处理了你懂得呵呵localstat=luci.sys.user.setpasswd("admin",data.pw1)==0--root-->adminifstatthenf.message=translate("a_s_changepw_changed")elsef.errmessage=translate("unknownerror")enddata.pw1=nildata.pw2=nilendreturntrueendreturnf
说明:(simpleForm位于view/cbi下面,可以研究一下,各个元素是如何定义的)
现在在给一个小例子:
以.*/luci/model/cbi/admin_system/version_manage.lua为例,介绍一下luci中web页面lua代码
6localh=loadfile("/usr/local/luci/help.lua")7ifhthen8h()9end10localhelp_txt=help_infoandhelp_info.version
加载帮助帮助文件help.lua,关于loadfile()的用法可以查看lua的手册(我还没完全弄明白,先用了)
help_txt是一个全局变量
12appadmin_path="/usr/local/appadmin/bin/"
定义一个全局变量,其实跟功能跟宏一样,定义appadmin的绝对路径
14versionlist={}1516functiongetline(s).........32end3334functionget_versionlist().........68end6970versionlist=get_versionlist()
定义一个全局变量和两个函数,并初始化此变量
接下来就是和最终展现的Web页面直接相关的代码了,大部分都是对luci封装好的一些html控件(代码)的使用和扩展。luci封装好的html控件
类可以在以下文件查看:./host/usr/lib/lua/luci/cbi.lua
71m=SimpleForm("version",translate("版本管理"))72m.submit=false73m.reset=false74m.help=help_txtandtrueorfalse75m.helptxt=help_txtor""
使用了一个SimpleForm的控件,SimpleForm实际上对应一个html表单,是整个页面最大的"容器",本页面内的绝大部分控件都处于SimpleForm内
,是它的子控件。我知道的可以做>页面最大"容器"的控件还有一个Map,但它需要./host/etc/config/目录下的一个配置文件,我没有使用。
submitreset是luci默认就封装好的一个属性,分别控制html表单的"提交""重置"按钮;helphelptxt是我扩充的表单属性,分别控制web页面的
"帮助"功能和帮助内容。关于控件属
性的意义、实现和扩充可以按以下步骤进行:
在文件./host/usr/lib/lua/luci/cbi.lua中查找控件名SimpleForm,然后可以找到以下行664self.template="cbi/simpleform"这
表明SimpleForm的html模版文件为./host/usr/lib/lua/luci/view/cbi/simpleform.htm,通过研究simpleform.htm文件内容可以知道各属性的
功能以及模版中使用lua代码的方法,然后可以按类似的方法添加自定义的
属性。
77s=m:section(Table,versionlist)
新建了一个section,section内定义了一个表格类,versionlist是与其相关的变量(lua的所有变量都可归类于table类型)
与Table关联的table变量应该是这种结构的:
t={row1={column1="xxx",column2="xxx",....},row2={column1="xxx",column2="xxx",....},row3={column1="xxx",column2="xxx",....},row4={column1="xxx",column2="xxx",....},}
然后定义Table的列控件
79enable=s:option(DummyValue,"_enabled",translate("软件状态"))83appid=s:option(DummyValue,"_appid",translate("软件版本"))84appname=s:option(DummyValue,"_appname",translate("软件名称"))
DummyValue是只读的文本框,只输出不输入。Value是单行文本框,可输出也可输入。Flag是一个checkbox,值为"1"时被选中,为"0"时未选中。
ListValue是列表框...具体的用法可
以看./host/usr/lib/lua/luci/model/cbi/下的文件(find./host/usr/lib/lua/luci/model/cbi/-name"*.lua"|xargsgrep
"ListValue")
对于table内普通的字符串类的值,只需要把列控件的id(括号内第二个值,如"_appid")定义为table内对应的变量名(比如column1)
对于非变通字符串类的值,或者为字符串但需要进行一定的处理然后再显示的值,可以按以下方法显示:定义该控件的cfgvalue函数属性
127newinfo=up_s:option(TextValue,"_newifo",translate("新版本信息"))128newinfo.readonly=true129newinfo.rows=11130newinfo.cfgvalue=function(self,section)131localt=string.gsub(info,"Archive:[^/n]*","")132returnt133end
定义cfgvalue后,luci的处理函数会调用此函数为此控件赋值,(传入的section参数值为row1/row2/row3等,当处理到row几时值就为row几)
对于DummyValue等只输出不输入的类,还有一种赋值方法:控件实例名(如enable).value=xxx
对于有输入的控件Value等,.value方法赋值在处理输入里会有一些问题,有什么问题以及如何解决可以做实验试试,也许是我使用方法不对造
成的
对有输入控件的处理有两种方法:
1定义控件的.write属性
这种方法对处理比较独立的输入(与其它控件输入关系不大)比较适用
88up_s=m:section(SimpleSection)89up_version=up_s:option(Button,"_up_version",translate("上传新版本"))90up_version.onlybutton=true91up_version.align="right"92up_version.inputstyle="save"93up_version.write=function(self,section)94luci.http.redirect(luci.dispatcher.build_url("admin","system","version_manage","upload"))95end
ps:只有当Value的rmempty==false时,Value输入为空也会触发write函数,需要对rmemtpy显示赋值为false(xx.rmempty=false)
4:view下面的html简介
这个是最好理解的例:passwd.htm
<%+header%><h2><aid="content"name="content"><%:system%></a></h2><h3><%:reboot%></h3><p><%:a_s_reboot1%></p><%-localc=require("luci.model.uci").cursor():changes()ifcandnext(c)then-%><pclass="warning"><%:a_s_reboot_u%></p><%-endifnotrebootthen-%><p><ahref="<%=controller%>/admin/system/reboot?reboot=1"><%:a_s_reboot_do%></a></p><%-else-%><p><%:a_s_reboot_running%></p><scripttype="text/javascript">setTimeout("location='<%=controller%>/admin'",60000)</script><%-end-%><%+footer%><%+header%><%+footer%>加载公用的头部和尾部<%luacode%><%:i18n%><%luacode%><%=lua变量%>