luci的框架和认识

109 篇文章 10 订阅
87 篇文章 30 订阅

官网:

有关luci的各个方面,你几乎都可以从这里获得,当然,只是浅显的获得,luci的文档写的还算比较全,但是写的稍显简略,开始看的时候会有一点不知所措。

UCI 熟悉openwrt的人都会有所了解,就是Uni?ed Con?guration Interface的简称,而luci这个openwrt上的默认web系统,是一个独立的由严谨的德国人开发的web框架,是Lua Con?guration Interface的简称,如果在您的应用里,luci是对openwrt的服务,我们就有必要做一下uci的简介,我这里就不说了,见链接:

有的时候,我们开发的luci是在自己的Linux PC上开发,在普通的linux上,一般是没有uci命令的,为了开发方便,可以手动编译一下,方法见链接:

OK ,之前罗里罗嗦的说了很多,现在就进入正题,进入正题的前提是你已经make install正确的安装了lua ,luci,以及编译好链接了相关的so(如果你需要,比如uci.so nixio.so),以及make install正确web server,(我用的web server是thttpd,也编译过mongoose,lighttpd,在这三个之中,lighttpd是功能最完善的,mongoose是最小巧的)。

进入正题:

一:luci的启动

在web server中的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# ./luci 
 Status: 200 OK   
 Content-Type: text/html;  
 charset=utf-8    
 Cache-Control: no-cache   
 Expires: 0 
 
 ""**>**   
 **<****html** class=" ext-strict"**>** 
 /*some html code*/   
 ***\*html\**\**>\**** 

如果你成功的运行了luci 就说明你的luci框架成功的跑了起来。

二: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) 
 
function index() 
	luci.i18n.loadc("admin-core") 
  local i18n = luci.i18n.translate 

  local root = node() 
       if not root.lock then 
          root.target = alias("mini") 
			    root.index = true 
	end 
    
	entry({"about"}, template("about")).i18n = "admin-core" 
    
   local page  = entry({"mini"}, alias("mini", "index"), i18n("essentials", "Essentials"), 10) 
   page.i18n  = "admin-core" 
   page.sysauth = "root" 
   page.sysauth_authenticator = "htmlauth" 
   page.index = true 
    
   entry({"mini", "index"}, alias("mini", "index", "index"), i18n("overview"), 10).index = true 
   entry({"mini", "index", "index"}, form("mini/index"), i18n("general"), 1).ignoreindex = true 
   entry({"mini", "index", "luci"}, cbi("mini/luci", {autoapply=true}), i18n("settings"), 10) 
   entry({"mini", "index", "logout"}, call("action_logout"), i18n("logout")) 
end 
 
function action_logout() 
   luci.http.header("Set-Cookie", "sysauth=; path=/") 
   luci.http.redirect(luci.dispatcher.build_url()) 
end 

这个文件定义了node,最外面的节点,最上层菜单的显示等等。在其他的lua文件里,定义了其他菜单的显示和html以及业务处理路径。每个文件对应一个菜单相。

例如 system.lua文件

function index() 
   luci.i18n.loadc("admin-core") 
   local i18n = luci.i18n.translate 
  
   entry({"mini", "system"}, alias("mini", "system", "index"), i18n("system"), 40).index = **true** 
   entry({"mini", "system", "index"}, cbi("mini/system", {autoapply=**true**}), i18n("general"), 1) 
   entry({"mini", "system", "passwd"}, form("mini/passwd"), i18n("a_s_changepw"), 10) 
   entry({"mini", "system", "backup"}, call("action_backup"), i18n("a_s_backup"), 80) 
   entry({"mini", "system", "upgrade"}, call("action_upgrade"), i18n("a_s_flash"), 90) 
   entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100) 
end 

mudel是对应文件的,function index定义了菜单,比如这一句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页面。

alias cgi form call 等定义了此菜单相应的处理方式,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_reboot function

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") 
local load1, load5, load15 = luci.sys.loadavg() 
local request = **require**("luci.dispatcher").context.path 
local category = request[1] 
local tree   = luci.dispatcher.node() 
local cattree = category **and** luci.dispatcher.node(category) 
local node   = luci.dispatcher.context.dispatched 
local hostname = luci.sys.hostname() 
local c = tree 
	for i,r in ipairs(request) do
    if c.nodes and c.nodes[r] then 
 ​     c = c.nodes[r] 
 ​     c._menu_selected = true 
    end 
 end 

require("luci.i18n").loadc("default") 
require("luci.http").prepare_content("application/xhtml+xml") 
-%> 


"1.0" encoding="utf-8"?> 
"-//W3C//DTD XHTML 1.0 Strict//EN" ""> 
"" xml:lang="<%=luci.i18n.context.lang%>" lang="<%=luci.i18n.context.lang%>"> 
 
"Content-Type" content="text/html; charset=utf-8" /> 
"Content-Script-Type" content="text/javascript" /> 
"stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" /> 
 
 
<% **if** node **and** node.css then %>"stylesheet" type="text/css" media="screen" href="<%=resource%>/<%=node.css%>" /> 
<% end -%> 
"text/javascript" src="<%=resource%> 
"text/javascript" src="<%=resource%> 
"text/javascript" src="<%=resource%>  
  
class="lang_<%=luci.i18n.context.lang%>"> 
class="skiplink"> 
"skiplink1">"#navigation" mce_href="#navigation"><%:skiplink1 Skip to navigation%> 
"skiplink2">"#content" mce_href="#content"><%:skiplink2 Skip to content%> 

"header"> 
<%=luci.version.distname%> 
 
<%=luci.version.distversion%> 
<%:load%>: <%="%.2f" % load1%> <%="%.2f" % load5%> <%="%.2f" % load15%> 
<%:hostname%>: <%=hostname%>  

"menubar"> 

class="navigation">"navigation" name="navigation"><%:navigation Navigation%> 

"mainmenu" **class**="dropdowns"> 

<%- 

local function submenu(prefix, node) 

   if not node.nodes or node.hidden then 
    return false 
   end 

   local index = {} 
   local count = 0 

   for k, n in pairs(node.nodes) do
     if n.title and n.target then 
        table.insert(index, {name=k, order=n.order or 100}) 
        count = count + 1 
     end 
   end 

   table.sort(index, function(a, b) return a.order < b.order end) 

   if count > 0  then 

%> 
"submenu_<%=string.gsub(string.gsub(prefix, "/", "_"), "^_(.-)_$", "%1")%>"> 
<%- 

	for j, v in pairs(index) do
    if #v.name > 0 then 
		local nnode = node.nodes[v.name] 
		local href = controller .. prefix .. v.name .. "/" 
		href = (nnode.query) and href .. luci.http.build_querystring(nnode.query) or href 
     
    if nnode.nodes then 
    		for k1, n1 in pairs(nnode.nodes) do
						href = "#"      
        end 
    end    

%> 


if nnode._menu_selected then %> **class**="active"<%end%> href="<%=luci.util.pcdata(href)%>"><%=nnode.title%><%- 
submenu(prefix .. v.name .. "/", nnode) 

%>
<%- 
        end 

    end 

%> 

<% 

   end 

end 

**if** cattree **and** cattree.nodes then 

   local index = {} 

   for k, node in pairs(cattree.nodes) do

      table.insert(index, {name=k, order=node.order or 100}) 

   end 

   table.sort(index, function(a, b) return a.order < b.order end) 

   for i, k in ipairs(index) do
      node = cattree.nodes[k.name] 
      if node.title and node.target and not node.hidden then 
        local href = controller.."/"..category.."/"..k.name.."/" 
        href = (k.query) **and** href .. luci.http.build_querystring(k.query) **or** href 

        for k1, n1 in pairs(node.nodes) do
          if n1.title and n1.target then 
            href = " #"           
        end 
      end    
%> 

if node._menu_selected then %> class="preactive"<%end%> href="<%=href%>"><%=node.title%><% 

submenu("/" .. category .. "/" .. k.name .. "/", node) 

%>
<% end 
   end 

end 

%> 

 

"modemenu"><% 

for k,node in pairs(tree.nodes) do

   if node.title **and** not node.hidden then %> 

if request[1] == k then %> class="active"<%end%> href="<%=controller%>/<%=k%>/"><%=node.title%>

<% 
   end 
end 
%>
<% 

if tree.nodes[category] and tree.nodes[category].ucidata then 
   local ucic = 0 
   for i, j in pairs(require("luci.model.uci").cursor():changes()) **do** 

     for k, l in pairs(j) do
       for m, n in pairs(l) do
         ucic = ucic + 1; 
      end 
     end 
   end 
-%> 

"savemenu" **class**="dropdowns"> 


<% if ucic > 0 then %>class="warning" href="<%=controller%>/<%=category%>/uci/changes/"><%:unsavedchanges%>: <%=ucic%><% 

submenu("/" .. category .. "/uci/", tree.nodes[category].nodes["uci"]) 

else -%> 
"#" mce_href="#"><%:changes%>: 0<% end -%> 
<% end %> 
class="clear">
"maincontent"> 

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 = **false** 
function pw2.validate(self, value, section) 
   return pw1:formvalue(section) == value and value 
end 

function f.handle(self, state, data) 
   if state == FORM_VALID then  --这个就是业务处理了 你懂得 呵呵 
       local stat = luci.sys.user.setpasswd("admin", data.pw1) == 0 -- root --> admin   
       if stat then 
          f.message = translate("a_s_changepw_changed") 
       else 
          f.errmessage = translate("unknownerror") 
       end 

       data.pw1 = nil 
       data.pw2 = nil 
  end 
  
	return true
end 

return f 

说明:( simpleForm 位于view/cbi 下面,可以研究一下,各个元素是如何定义的)

现在在给一个小例子:

以.*/luci/model /cbi/admin_system/version_manage.lua为例,介绍一下luci中web页面lua代码

local h = loadfile("/usr/local/luci/help.lua") 
if h then 
  h() 
end 
local help_txt = help_info and help_info.version 

加载帮助帮助文件help.lua,关于loadfile()的用法可以查看lua的手册(我还没完全弄明白,先用了) help_txt 是一个全局变量

12 appadmin_path = “/usr/local/appadmin/bin/” 定义一个全局变量,其实跟功能跟宏一样,定义appadmin的绝对路径

versionlist = {}  
function getline (s) 

end 

function get_versionlist() 

end 

versionlist = get_versionlist() 

定义一个全局变量和两个函数,并初始化此变量

接下来就是和最终展现的Web页面直接相关的代码了,大部分都是对luci封装好的一些html控件(代码)的使用和扩展。luci 封装好的html控件

类可以在以下文件查看:./host/usr/lib/lua/luci/cbi.lua

m = SimpleForm("version", translate("版本管理")) 
m.submit = **false** 
m.reset = **false** 
m.help = help_txt and **true** or **false** 
m.helptxt = help_txt or "" 

使用了一个SimpleForm的控件,SimpleForm实际上对应一个html表单,是整个页面最大的"容器",本页面内的绝大部分控件都处于SimpleForm内

,是它的子控件 。我知道的可以做>页面最大"容器"的控件还有一个Map,但它需要./host/etc/config/目录下的一个配置文件,我没有使用。 submit reset是luci默认就封装好的一个属性,分别控制html表单的"提交"“重置"按钮;help helptxt是我扩充的表单属性,分别控制web页面的

“帮助"功能和帮助内容。关于控件属 性的意义、实现和扩充可以按以下步骤进行: 在文件./host/usr/lib/lua/luci/cbi.lua中查找控件名SimpleForm, 然后可以找到以下行 664 self.template = “cbi/simpleform"这

表明SimpleForm的html模版文件为./host/usr/lib/lua/luci/view/cbi /simpleform.htm,通过研究simpleform.htm文件内容可以知道各属性的

功能以及模版中使用lua代码的方法,然后可以按类似的方法添加自定义的 属性。 77 s = 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的列控件

enable = s:option(DummyValue, "_enabled", translate("软件状态")) 
appid = s:option(DummyValue, "_appid", translate("软件版本")) 
appname = 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” |xargs grep

“ListValue”) 对于table内普通的字符串类的值,只需要把列控件的id(括号内第二个值,如”_appid”)定义为table内对应的变量名(比如column1 ) 对于非变通字符串类的值,或者为字符串但需要进行一定的处理然后再显示的值,可以按以下方法显示:定义该控件的cfgvalue函数属性

newinfo = up_s:option(TextValue, "_newifo", translate("新版本信息")) 
newinfo.readonly = true
newinfo.rows = 11 
newinfo.cfgvalue = function(self, section) 
                     local t = string.gsub(info, "Archive:[^/n]*", "") 
                     return t 
									end 

定义cfgvalue后,luci的处理函数会调用此函数为此控件赋值,(传入的section参数值为row1/row2/row3等,当处理到row几时值就为row几) 对于DummyValue等只输出不输入的类,还有一种赋值方法: 控件实例名(如enable).value = xxx 对于有输入的控件Value等, .value方法赋值在处理输入里会有一些问题,有什么问题以及如何解决可以做实验试试 ,也许是我使用方法不对造 成的 对有输入控件的处理有两种方法: 1 定义控件的.write属性 这种方法对处理比较独立的输入(与其它控件输入关系不大)比较适用

up_s = m:section(SimpleSection) 
up_version = up_s:option(Button, "_up_version", translate("上传新版本")) 
up_version.onlybutton = true 
up_version.align = "right" 
up_version.inputstyle = "save" 
up_version.write = function(self, section) 
  luci.http.redirect(luci.dispatcher.build_url("admin", "system", "version_manage", "upload")) 
end 

ps:只有当Value的rmempty == false时,Value输入为空也会触发write函数, 需要对rmemtpy显示赋值为false ( xx.rmempty = false)

4:view下面的html简介

这个是最好理解的 例:passwd.htm

<%+header%> 

 "content" name="content"><%:system%>   

 <%:reboot%>   

 <%:a_s_reboot1%>
  
 <%- 
  
 local c = require("luci.model.uci").cursor():changes() 


 if c and next(c) then  

 -%>

    class

    =

    "warning"

    \><%:a_s_reboot_u%>
 
 <%-  

end 
 

if not reboot then 
 
 -%> 
 
 "<%=controller%>/admin/system/reboot?reboot=1"><%:a_s_reboot_do%>
  
 <%- **else** -%> 

 <%:a_s_reboot_running%>

 "text/javascript">setTimeout("location='<%=controller%>/admin'", 60000) 

 <%- end -%> 


 <%+footer%> 
<%+header%> <%+footer%> 加载公用的头部和尾部 
  
<% lua code%> 
  
<%:i18n%> 
  
<%lua code%> 
  
<%=lua 变量%>  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值