1.LUCI簡介
LUCI是一個小巧的語言,誕生於2008年3月份,目的是為OpenWrt韌體從 Whiterussian 到 Kamikaze實現快速配置介面。輕量級 LUA語言的官方版本只包括一個精簡的核心和最基本的庫。這使得LUA體積小、啟動速度快,從而適合嵌入在別的程式裡。UCI是OpenWrt中為實現所有系統配置的一個統一介面,英文名Unified Configuration Interface,即統一配置介面。LuCI,即是這兩個專案的合體,可以實現路由的網頁配置介面。[·引自百度百科https://baike.baidu.com/item/...]
2. 我做過什麼
其實我接觸LUCI並不久,主要用它來為OpenWrt寫UCI介面,來存取配置檔案裡的內容。存取有兩種方式:
- 使用LUCI原有機制實現存取
- 自定義介面進行存取
這兩種我都會介紹。本文主要介紹一些LUCI的基本內容和一種方式的應用。
3. LUCI的基本架構
LUCI可以歸屬為web開發行列。因為它的內容都是直接在瀏覽器裡體現出來的。它的基本架構跟很多WEB開發語言的框架一樣,都是MVC架構。以下給出一些簡單的解釋:
M(Model): 模型層,路徑是/usr/lib/lua/luci/model/。這個是資料處理的具體程式碼的層。該層中有一個cbi資料夾,那裡面是預定義的一些的邏輯檔案。
V(View): 檢視層,路徑是/usr/lib/lua/luci/view/。這個很容易理解,就是存放檢視檔案的地方。LUCI裡的檢視檔案是.htm檔案。
C(Controller): 控制層,路徑是/usr/lib/lua/luci/controller/admin/。這個層最主要的功能是設定路由;它還有一個功能跟模型層一致——處理資料。
根目錄: /usr/lib/lua/luci
資原始檔存放目錄: /www/luci-static/
4. 開始建立新頁面
以配置network檔案為例,實現一個testnet模組需要完成下面的步驟。
步驟:
- 建立一個配置檔案
- 定義控制層
- 定義模型層
1> 建立一個配置檔案
之前介紹的時候有說到,LUCI是用於存取配置檔案的資訊,所以我們的若要新建一個模組,需要一個配置檔案。配置檔案的路徑一般是在/etc/config/。由於現在我們是需要配置network檔案,而這個是系統必備檔案,它已經存在,所以我們不需要新建,但我們要了解一下里面的結構是怎麼樣的:
config interface 'loopback'
option ifname 'lo'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'
config interface 'lan'
option type 'bridge'
option ifname 'eth1'
option proto 'static'
option ipaddr '192.168.1.1'
option netmask '255.255.255.0'
option ip6assign '60'
這個是配置裡有內容,而其他配置檔案內容格式也是差不多:
-- 編輯配置檔案
root@openWrt:~# vim /etc/config/<config name>
-- 配置檔案格式
config <section type> <section name>
option <option name> <option value>
option <option name> <option value>
option <option name> <option value>
那麼我們建立一個新的配置給testnet模組使用
config interface testnets
option ifname 'testnets'
option type 'test'
option ipaddr '192.168.1.1'
option net '192.168.251.1'
2> 定義控制層
控制層中,我們主要定義訪問的路由。每一個模組的控制都是獨立的,那樣才能方便控制。路由的定義是在控制器的index()函式裡。控制器裡還可以定義其他的函式用於處理處理,相信這一點有過WEB開發經驗的童鞋們都知道。
接下來我們建立一個能以/admin/testnet訪問的路由
話不多說,直接上程式碼:
-- /controller/admin/testnet.lua
module("luci.controller.admin.testnet")
function index()
entry({"admin", "testnet", "index"}, cbi("admin_test/net"), translate("Test Net"), 10)
end
function test()
return 'This is a test function.'
end
module("luci.controller.admin.testnet"):
這一句是名稱空間的宣告,luci指的是/usr/lib/lua/luci這個的資料夾,也就是LUCI的根目錄entry({"admin", "testnet", "index"}, cbi("admin_test/net"), translate("Test Net"), 10):
這一句必須寫在控制器檔案的index()函式裡。它的格式定義是這樣子的:
entry(path, target[[, title][, order]])
-
path: 即路由規則,格式是
{"admin","testnet","index"[[,"..."][,"..."]]}
基本上可以無限延伸,但一般不建議這麼幹,到五六層已經很深了,再潛就不好了。 -
target: 即頁面指向,格式是
cbi("admin_test/net")
。這裡的cbi的函式指的是呼叫/luci/model/cbi/裡的admin_test/裡的net.lua檔案。它還有其他的使用方法:- cbi("..."):呼叫/luci/model/cbi/資料夾中的指定lua檔案,這裡指向的檔案相當於指向一個處理函式。使用的是LUCI自帶邏輯的處理方法,本文就是使用這種方法來生成的頁面。
- template("..."):呼叫/luci/view/資料夾裡指定的.htm檢視檔案。這個方法是直接呼叫檢視。在檢視裡我們也可以嵌入程式碼讀取配置。
- alias("..."): 這個是重定向函式, 一般用於頂級選單上,使其重定向到指定的子選單。
- call("..."): 這個函式的作用是將該路由指向控制下的某個函式,一般用於處理資料,作用與指向模型層的cbi("...")函式類似,只是一個指向到其他檔案,一個仍是存在於控制層檔案內。
- post("..."): 這個函式在openwrt裡的其他模組有使用過,本人研究了一下,其作用於call方法類似,但在使用的時候似乎沒有成功,若是有成功使用過的猿友,歡迎交流探討。
* title: 即標題展示,這個是設定在選單裡顯示的內容項。格式有_("Test Net")
或translate("Test Net")
,前一個使用我也沒有成功(有點摸不清楚),後一個是呼叫translate('...')函式,用於與後期的語言包進行適配。該項可以用nil
代替,表示為不在選單欄顯示。
- order: 這個是排序,為子選單進行排序,序號以1開始,最大不限。該項也可以忽略,表示為不在選單欄顯示。
* 若想讓某項隱藏,可以寫成以下格式:
entry({"admin", "testnet", "index"}, cbi("admin_test/net"), nil)
3> 定義模型層
OK,說了那麼多,終於到了我們最關鍵的模型層。在模型層,LUCI擁有一套自動生成機制,接下來讓我們一一介紹。先上程式碼再解釋——
-- /model/cbi/admin_test/net.lua
m = Map("network", translate("Test Net"))
s = m:section(NamedSection, "testnets", "interface", translate("Net Configuration")
s.addremove = true
s.anonymouse = true
ifname = s:option(Value, "ifname", translate("Ifname: "))
ifname.datatype = 'string'
itype = s:option(Value, "type", translate("Type: "))
itype.datatype = 'string'
ipaddr = s:option(Value, "ipaddr", translate("Ipaddr: "))
ipaddr.datatype = 'ipaddr'
net = s:option(Value, "net", translate("Net:"))
net.datatype = 'ipaddr'
return m
-
m = Map("network", translate("Test Net"))
:這一句是連線配置檔案,是以LUCI機制的必不可少的語句。第一個引數
是配置檔案的名稱,第二個引數
頁面的大標題。 -
s = m:section(NamedSection, "testnets", "interface", translate("Net Configuration")
:這一句是以section
`的name
名連線到指定的section
。-
第一個引數
是指定存取section的方法,本文用的是以section的name名進行查詢的方式NamedSecton
。其他還有幾種方式:- TypedName: 根據section的type來進行存取
- SimpleSection:(這個沒用過,就不多說了)
- Table:以表格的形式體現section
- Tab:以標籤的形式體現section
-
第二個引數
是section的名字 -
第三個引數
是section的型別 -
第四個引數
是模組標題的名稱
-
其他型別的程式碼示例分別如下:
-- TypedSection
s = m:section(TypedSection, "interface", translate("Net Configuration")
-- SimpleSection
s = m:section(SimpleSection, "interface", translate("Net Configuration")
-- Table
s = m:section(TypedSection, "interface", translate("Net Configuration")--也可以用NamedSection
s.Table(Table, "Table Title")
-- Tab
s = m:section(TypedSection, "interface", translate("Net Configuration")--也可以用NamedSection
s.Table(Tab, "Tab Title")
-
s.addremove = false
:開啟/關閉新增刪除按鈕,這個為true
時,會在頁面上新增一個新增和一個刪除按鈕,這樣就能夠在頁面快速進行增減項了。不過有一點要注意的是,那樣的頁面比較醜…… -
s.anonymouse = true
:這一句是不顯示section的名字在頁面上。還可以加上s.template = 'admin_test/net'
這樣的語句 -
net = s:option(Value, "net", translate("Net:"))
:這一句是連線具體的option。-
第一個引數
是顯示型別。常用的顯示型別有:- Value(普通文字框)、ListValue(下拉選單)、Flag(核取方塊)、MultiValue(文字域)、DummyValue(純文字)、TextValue(多行input)、Button(按鈕)、StaticList(靜態列表)、DynamicList(動態列表)
-
第二個引數
是option的名稱 -
第三個引數
是顯示項的說明
-
-
net.datatype = 'ipaddr'
:這一句是指定option的型別。這裡的是ip地址型別,其他還有很多型別:- neg、list、bool(布林型別)、uinteger、integer(整型)、ufloat、float(浮點型)、ipaddr(IP地址)、ip4addr(IP4型IP地址)、ip4prefix(IP4字首)、ip6addr、ip6prefix、port、portrange、macaddr、hostname、host、network、wpakey、wepkey、string、directory、file、device、uciname、range、min、max、rangelength、minlength、maxlength、phonedigit
這時訪問/admin/testnet即可以看到頁面了。
參考連結: