HTML教程:https://www.w3schools.com/tags/tag_th.asp
一些LuCI教程(未整理):
- openwrt web管理luci界面修改:https://www.shuzhiduo.com/A/KE5QlAg4JL/
- OpenWrt路由器系统开发与网页设计:https://www.shuzhiduo.com/A/LPdokQmyd3/
- OpenWRT - WEB界面开发思路和基本方法:https://www.shuzhiduo.com/A/n2d9Y070dD/
- MT7628学习笔记(15)——修改LuCI界面左上角LOGO:https://blog.csdn.net/p1279030826/article/details/109099952
- openwrt luci管理的Web界面实例(佐大):https://www.openwrt.pro/post-494.html
- Openwrt Luci界面开发(佐大):https://www.openwrt.pro/post-349.html
- openwrt luci管理的Web界面实例(hello world, ip地址):https://juejin.cn/post/6844903638280699912
- OpenWrt Luci web开发-luci mvc架构讲解及实例:https://zhuanlan.zhihu.com/p/566238777
- openwrt web管理luci界面修改:https://blog.csdn.net/viewsky11/article/details/29838517
- OpenWRT(十一)LuCi开发(三)https://blog.51cto.com/u_11866419/5019229
- Lua教学:https://www.jianshu.com/u/8fad76e7e05c
- JS和HTML教学: https://www.w3schools.com/jsref/prop_html_innerhtml.asp
需要解决的问题:
- WinSCP怎么用vscode:white_check_mark:
- OpenWRT如何看到接入设备的ip:white_check_mark:
- UCI和CBI模块详细
- signal和noise计算部分代码review
- 每个设备加入wifi后自动从1开始添加序号:white_check_mark:
轻量级 LUA 语言的官方版本只包括一个精简的核心和最基本的库。这使得 LUA 体积小、启动速度快,从而适合嵌入在别的程序里。UCI 是 Openwrt 中为实现所有系统配置的一个统一接口
英文名 Unified Configuration Interface,即统一配置接口。LuCI 即是这两个项目的合体,可以实现路由的网页配置界面。
(最好先学习 LUA 脚本编程)
MVC(Model,View,Controller)模式是一种软件设计模式。视图(view)既软件与用户交互的界面,模型(model)表示数据以及业务规则,控制器(Controller)连接二者的桥梁,接受视图所提交的请求,调用模型去完成请求。
LuCI 使用 MVC(模型/视图/控制)模型,在/usr/lib/lua/luci/下有三个目录 model、view、controller,它们分别对应 M、 V、C。
LUCI从某些功能的实现来讲属于MVC框架。使用winSCP进入openwrt系统,可以发现在usr/lib/lua/luci文件夹下:
/usr/lib/lua/luci/controller/ --控制层: 负责转发请求,对请求进行处理
/usr/lib/lua/luci/view/ --视图层: 界面设计人员进行图形界面设计
/usr/lib/lua/luci/model/cbi/ --模型层:程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)\
view文件夹下有大量html文件,即luci的视图文件。controller文件夹下存着逻辑控制程序。model文件夹下的cbi文件夹存在着处理各种请求的程序 。
我们要做的主要工作就是基于 LuCI 框架编写 LUA 脚本、在 html 页面中嵌入 LUA 脚本。
控制层中的文件语法精简结构如下,主要为两个部分:模块声明 和路由定义。
文件定义时需要声明其模块名称,按照其路径定义即可,比如该文件位于/luci/controller/admin/network.lua
上面代码中我们可以看到使用了两种定义节点的方式:
- 一种是直接使用entry方法来定义一个节点,并传入以下4个参数:path, target, title, order。
- 第二种方式是使用node方法直接定义节点的路径,然后分开指定其他target, title等属性。
entry方法内部也是使用node方法,所以两种方式等效。
-
path: 规定了此节点的访问路径(调度树的位置),参数是一个字符串数组,比如 {“admin”, “test”, “test_view”} 这样的参数,在页面访问的时候,路径为 /admin/test/test_view ,"test" 是一级菜单。"test_view" 为二级菜单。例如:{“foo”, “bar”, “baz”}会插入foo.bar.baz节点(文件树形结构)。如果使用node方法直接创建,则需要散列传参,不需要传入table。
-
target:规定访问此路径时,Luci框架执行的调用目标,根据调用目标的不同,LuCI也有不同的调度行为-->用户请求(点击)节点时调用的动作(可以理解为监听器)。
所有的target类型,在源码文件 dispatcher.lua中可以找到。
target主要使用的有alias、firstchild、call、template、cbi、form、arcombine。
target主要有如下种类:
- firstchild() :自动指向该节点的第一个子节点(order最小的那个节点)
- call(func_name):调用该文件下的指定函数,比如 call(“test_function”),则会在访问当前路径时,调用该文件下的test_function函数。在这些函数中,我们也可以处理一些数据。
- template(tpl_name):根据给定的HTM模板名称访问指定的HTML页面,路径对应luci/view下面的文件。比如template(“admin_system/hello”),对应luci/view/admin_system/hello.htm 文件。
再比如template(“admin_system/helloworld”),admin_system/helloworld: 对应/view/admin_system目录下的helloworld.htm文件 上图中的_("Network")表示在web界面显示的节点名称为Network - cbi(model,config):根据给定的model文件名,渲染一个CBI模型。路径对应luci/model/cbi下面的文件。同时可以传入一些config信息,比如 {nofooter:不显示cbi_footer,noheader:不显示cbi_header} 等等。其他用法可以在源码 dispatcher.lua 的 _cbi方法中进行查看。
- form(model) : 与cbi()类似,不过渲染的是一个表单CBI模型。
- arcombine(trg1,trg2):当需要访问同一个路由,而这个路由在有参和无参时的调用目标需要不一致的时候,可以使用arcombine把两个target进行组合起来(比如某个列表和其详情页分开时)。当有参数时调用trg2,无参时调用trg1.
- alias(Path):重定向到另外一个节点上。
-
title:定义该节点在菜单上显示的名称(menu上面显示的字符串(可选)),非必填项,如果使用了国际化配置,可以使用_(“Router”)这样的表示方式,在中文环境下显示路由,在英文环境下显示Router。如果不填title,则不会在菜单栏上面显示。但是可以通过url进行访问。
-
order: 在相同层次的排列顺序(可选)--> 该节点在同级节点下的显示顺序。非必填项,从上至下,从左至右数值越小显示越靠前。
节点属性:使用变量接收node方法或者entry方法返回的节点后,可以选择添加的属性。
- i18n :定义了在请求页面时应自动加载哪个翻译文件
- dependent :保护插件在父节点丢失时从上下文中调用
- leaf :停止在此节点处解析请求,并且在调度树中不再进一步
- sysauth :要求用户使用指定的帐户进行身份验证
- sysauth_authenticator :验证时手动指定验证方法
- setgroup :设置当前进程的组ID
- setuser:设置当前进程的用户ID
上面的属性的使用,在dispatcher.lua文件中的dispatch方法中有体现
1)index.lua 这个文件定义了node ,最外面的节点,最上层菜单的显示等等。
2)其他的 lua 文件里,定义了其他菜单的显示和html 以及业务处理路径。每个文件对应一个菜单项。
例如system.lua:
1)module是对应文件的
2)function index 定义了菜单,
比如这一句entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100)
第1 项为菜单入口:{"mini", "system", "reboot"}中,mini 是最上层的菜单,即为用户项; system 为一个具体的菜单;reboot 为这个菜单的子菜单;
第2项为菜单对应的页面,可以是lua 的源代码文件,也可以是 html 页面。alias 、cbi 、form 、call 等定义了此菜单相应的处理方式。
- form 和 cbi 对应到 model/cbi 相应的目录下面,那里面是对应的定制好的 html 和 lua 业务处理。
- alias是等同于别的链接
- call 调用了相应的 action_function
- template直接链接到view 相应目录下面的 htm 页面
第3项为i18n 显示(即语言替换)
第4项为现实的顺序,这个数字越小,显示越靠前,靠上。
例如我们在视图(界面中)打卡wireless—>overview:
在控制器中对应处理函数即entry({"admin","network","wireless"},arcombine(template("admin_network/wifi_overview"), cbi("admin_network/wifi")), _("Wifi"), 15)
其中"admin", "network","wireless"表示视图所在位置,cbi("admin_network/wifi"))表示其处理模型为/usr/lib/lua/luci/model/cbi下的wifi.lua。
LUCI中某些功能的实现在model中并没有专门的模型程序,因为其在Controller文件中写了功能函数做了处理。例如中的add操作,在cbi下并没有对应的程序做处理,在Controller文件中定义了function wifi_add()函数,执行对应操作。
上面的/usr/lib/lua/luci/controller/admin目录下存放着各个入口文件,
network.lua、status.lua、system.lua三个文件分别对应web页面上的Status、System、Network三个导航栏。所以要添加一个导航时可以在这里新建一个lua文件。
在controller中提到,target 可以使用 template 可以指定对应的htm文件作为界面显示。同样,在使用cbi,form等方法指定target时,最后也会根据htm文件来渲染界面。具体流程在后续解释。这个htm文件所属范畴就是View中,浏览器界面主要由两种途径产生,使用CBI模型进行生成,或者是自定义htm显示。CBI模型是使用Luci预定义的一些CBI组件来生成常用的表单。如果需要制作一些CBI文件满足不了的页面,可以自己定义htm文件来满足需求。
htm文件中通常由三大部分构成:Lua代码、HTML代码、JavaScript代码
语法解释:
在htm文件中,可以在HTML代码和JavaScript代码中插入lua代码,使用下面的语法格式标志代码为Lua代码。可以嵌套进行使用。具体实例在后续CBI模型渲染过程的解析中。
对于一些视图页面上信息动态显示,luci采用在html文件中嵌入lua程序,利用API直接获取,采用JavaScript动态显示的方法实现。
例如关联移动台信息的显示问题,直接在view/admin_network/wifi_overview.htm文件下利用嵌入lua,与JavaScript语言进行动态显示
在controller中提到。target 使用 cbi可以根据给出的文件创建一个CBI模型进行界面渲染。官方的说,CBI模型是Lua文件描述UCI配置文件的结构和由此参生的HTML表单来评估CBI解析器。通俗的说就是CBI模型通常由LuCI预定义的一些CBI控件组成,不同的CBI控件根据其对应的模板生成页面上可见的表单元素。这些控件可以执行大部分的UCI配置的获取或者设置的处理工作。我们也可以覆盖其本来的行为来达到我们满足项目需求的目的。
在我们定义的CBI文件中,与UCI的配置息息相关,UCI配置暂时可以理解为系统的各个应用程序的配置的统一接口。后面再详细介绍UCI。CBI模型文件定义在 /model/cbi/ 目录下
所有CBI luci.cbi.Map类型的模型文件必须返回一个map对象。
这里有一级节点 Status、System、Network、Logout,二级节点 System、Administration、Software、Startup 等。
在我们使用浏览器向路由器发起请求时,LuCI 会从 controller 目录下的 index.lua 开始组织这些节点。index.lua 中定义了根节点 root,其他所有的节点都挂在这个根节点上。
module("luci.controller.xx.xx.xx", package.seeall)
上面的 luci.controller.xx.xx.xx 表示该文件的路径,luci.controller 表示/usr/lib/lua/luci/controller/,比如上面的 index.lua 其 第一行为:module("luci.controller.admin.index", package.seeall),表示其路径为:/usr/lib/lua/luci/controller/admin/index.lua
entry (path, target, title, order)
- path 指定该节点的位置(例如 node1.node2.node3)
- target 指定当该节点被调度(即用户点击)时的行为,主要有三种:call、template 和 cbi,后面有 3 个实例。
- title:标题,即我们在网页中看到的菜单
- order:同一级节点之间的顺序,越小越靠前,反之越靠后(可选)
LuCI 默认开启了缓存机制,这样当我们修改了代码后,不会立即生效,除非删除缓存,操作如下:
效果和上面一样,不同的是上节使用 call 调度执行一个函数,本节直接调用一个 html 页面。 创建 html 文件:/usr/lib/lua/luci/view/example# ls example.htm,其内容如下
这里的<% %>用来在 html 代码中嵌入 LUA 脚本,这里的<%+header%>表示首先加载/usr/lib/lua/luci/view/header.htm
修改后的/usr/lib/lua/luci/controller/example.lua 内容如下:
对比前一种方法,entry只是从call变成了template
firstchild (): Alias the first (lowest order) page automatically
该方法与 UCI 配置息息相关。主要用来修改 UCI 配置文件以及使配置生效
ref: https://openwrt.github.io/luci/api/modules/luci.http.html
luci是一套web框架,虽然对http协议进行了封装,开发人员可以不用关心具体http底层如何处理,但是我们还是需要用到http请求的一些接口。
比如我们在自定义http请求接口时,需要获取http请求的表单参数、cookies等,有时候也需要处理上传的文件、url重定向。
接口 | 说明 |
---|---|
luci.http.formvalue(param) | 获取表单提交的数据,支持GET和POST |
luci.http.content() | Return the request content if the request was of unknown type. |
luci.http.getcookie(name) | 获取cookie值 |
luci.http.getenv(name) | 获取环境变量 |
luci.http.prepare_content() | 设置content-type,如luci.http.prepare_content(“application/json”) |
luci.http.source() | Get the RAW HTTP input source |
luci.http.write(content) | 返回数据,content为字符串类型 |
luci.http.write_json(object) | 返回json格式数据,object为对象,会自动转换成json格式 |
luci.http.redirect(url) | 重定向到指定url |
luci.http.urldecode(str) | Return the URL-decoded equivalent of a string. |
luci.http.urlencode(str) | Return the URL-encoded equivalent of a string. |
luci.http.setfilehandler (callback) | Set a handler function for incoming user file uploads |
LuCI system utilities / network related functions.系统和网络相关功能
接口 | 说明 |
---|---|
arptable () | 以two-dimensional table 的形式返回当前的 arp 表条目 |
conntrack () | 返回 conntrack 信息 |
deviceinfo () | 返回有关可用网络接口的信息 |
devices () | 确定可用网络接口的名称available network interfaces |
host_hints () | 返回主机提示的two-dimensional table |
ipv4_hints () | 返回 IPv4 地址提示的two-dimensional table |
ipv6_hints () | 返回 IPv6 地址提示的two-dimensional table |
mac_hints () | 返回 mac 地址提示的two-dimensional table |
pingtest (host) | 测试给定主机是否响应 ping 探测 |
routes () | 返回当前kernel routing table entries |
routes6 () | 返回当前的 ipv6 kernel routing table entries |
host_hints: Table of table containing known hosts from various sources, indexed by mac address. Each subtable contains at least one of the fields "name", "ipv4" or "ipv6".
https://blog.csdn.net/qq_19004627/article/details/80137746 https://mankitty.github.io/2018/06/22/OpenWrt/OpenWrt%E4%B9%8BXHR%E6%96%B9%E6%B3%95/
htm中通过XHR调用lua脚本,lua script location : /usr/lib/lua/luci/controller/admin/xxx.lua
XHR方法定义在xhr.js文件中,其中定义了get,post,poll,run等方法,其中比较常用的是poll以及get,post方法
xxx.htm:
var callPath='<%=luci.dispatcher.build_url("admin", "xxx", "dev_info")%>';
callPath=callPath+'/'+param1+'/'+param2; //如果需要传参
XHR.get(callPath,null,function(x,rval){
......
}
);
原型:XHR.poll = function(interval, callPath, data, callback)
作用:XHR.poll可以通过callPath定时的从后台获取到数据\
var callPath='<%=luci.dispatcher.build_url("admin", "xxx", "dev_info",parameter)%>';
XHR.poll(5,callPath, null,function(x,callPath rval){ ......
}
);
第一个参数interval: 为定时刷新时间(单位:second)
第二个参数callPath: 是luci生成的路径(获取数据的路径),其中parameter是传递给lua的参数
第三个参数data: 暂时未知,填null不影响
第四个callback: 是回调函数,处理获取到的数据:
x:是XHR对象,一般用不着
rval:是lua函数的返回值(一般取值rval[0])
Examples: [1]. 获取源数据,使用json格式发送出去
function dev_info()
local date = {}
......
luci.http.prepare_content("application/json")
luci.http.write('[')
luci.http.write_json(date)
luci.http.write(']')
end
[2]. 使用XHR.poll获取解析数据
var callPath='<%=luci.dispatcher.build_url("admin", "system", "devinfo",parameter)%>';
XHR.poll(interval,callPath, null,function(x,date){
......
}
);
我们知道在lua中表是非常重要的数据结构
table.concat()
原型:table.concat (table [, sep [, i [, j]]])
解释:返回表中的特定项连接后的数据,要求所连接的数据必须为数字或者字符串,级返回table[i]..sep..table[i+1] ··· sep..table[j]
其中参数sep表示连接符,默认为空字符串"",参数i默认为1
参数j默认是table的length,如果参数i大于j的话,返回空字符串"" \
document.getElementById是一个document对象的方法,可以通过它来获得指定id的html元素。
例如在页面里表单元素你可以给它设置id值,或name值来区别同种类型的不同元素,当你设置id document.getElementById("id")来得到这个元素,从而通过document.getElementById("id").value 得到元素的值。例如:
ref: https://www.runoob.com/js/js-output.html
window.alert(assoclist[j].signal);