
LuCI2 (OpenWrt web 管理界面)
一直以來OpenWrt都是采用Lua寫的web管理界面LuCI,(開機速度慢不說,居然比不過騰達等弱路由器開機速度)。 LuCI需要使用多個Lua擴展(如 ubus, luci.model.uci, nixio.fs, 等等)去存取系統信息和設置. 不幸的是這種解決方案在慢CPU和低內存的低配機器設備上是個災難,此方案相當消耗資源且並不能很好的工作。
這導致開發了LuCI2, 一個不同架構的新的web管理節目。它不再使用Lua,而是使用靜態HTML頁面加JavaScript XHR方法。 這意味着從OpenWrt設備中下載后在客戶端(瀏覽器)中構建HTML頁面, 通過ubus存取各種系統數據(通過uhttpd-mod-ubus提供基於HTTP的接口API).
重要說明
如上所述, LuCI2通過ubus和OpenWrt子系統通信(包括如network、service以及其它)。遺憾的是並非每個主要的OpenWrt工具都自己注冊了ubus,如LuCI2不能使用opkg(安裝包管理)。 LuCI2通過提供在附加ubus名稱空間的rpcd插件解決了這個問題。前面說的opkg它在ubus中注冊了一個新的luci2.opkg路徑來訪問。
綜上所述, LuCI2包括兩個方面: 打包的HTML/CSS/JS文件 (htdocs) 和一些在OpenWrt環境下運行的附加小工具。
在接下來的章節中,你會找到各種關於LuCI2開發幫助的細節。
菜單
首先需要知道的是瀏覽器接收到的關於LuCI2菜單並不固定寫死在任何文件。代替的是通過ubus使用luci.ui路徑和menu方法。可以通過使用以下命令來查看:
ubus call luci2.ui menu '{ "ubus_rpc_session": "invalid" }'
內部的rpcd插件分析在目錄/usr/share/rpcd/menu.d的所有文件,當前用戶(基於傳遞的ubus_rpc_session)不允許增加或者刪除條目。這將導致在一個二級菜單限制當前有權限訪問的條目。
頂層菜單項用下面的JSON定義:
"foo": {
"title": "Foo",
"index": 12
}
(並且通過index值排序)。
第二層菜單項通過同樣的方法定義:
"foo/bar": {
"title": "Bar",
"acls": [ "baz", "qux" ],
"view": "foo/bar",
"index": 5
}
注意,第二層菜單項可以在獨立的文件中定義,這樣就可以方便的添加新的菜單項定義而不用修改原有的文件。
模板
每一個LuCI2子頁面必須有一個存放在/www/luci2/template/目錄下的模板。它們提供了非常簡單的內容替換區的HTML文件。需要注意的是它們不包含任何變量的引用,這是JavaScript的功勞:用JavaScript去讀取並寫入內容。在為本地化系統(i18n)這些文件中唯一特定的語法類似下面的標記:
<p><%:Hello world%></p>
視圖
從模板中分離,每個LuCI2子頁面也需要一個定義並存放在/www/luci2/view/目錄下的一個視圖,視圖是使用了子頁面特定對象的L.ui.view擴展的JavaScript文件。Javascript中需要提供execute方法的實現,該方法將在膜拜加載后被執行。可選的也可以提供子頁面的title和description屬性。
有一點需要注意的是需要提供execute方法,創建視圖失敗有幾種原因,特別是當它需要ubus加載額外的數據使用的時候。所以在execute方法中提供成功或者失敗的信息是有意義的。不幸的是在異步方法加載特定數據的時候,不能簡單的返回true或false,解決的方法是返回一個Promise對象來運行推遲提供是否成功的信息。
最簡單的視圖可以如下所示:
L.ui.view.extend({
title: L.tr('Foo'), /* 可選的標題 */
description: 'Bar', /* 可選的描述 */
execute: function() {
var deferred = $.Deferred(); /* 創建一個延遲對象 */
deferred.resolve(); /* 立即Resolve,它不做任何事可以返回失敗 */
return deferred.promise(); /* 返回Promise對象 (延遲對象的子集) */
}
});
通過ubus通訊
在開始之前,需要知道的是LuCI2提供了一些存取UCI系統的一些幫助。如果寫一個簡單的管理/etc/config/下配置文件不需要完全知道ubus的調用方法,則可以跳過該部分。
構建一些更復雜的LuCI2視圖前最好先弄明白使用到的ubus調用。完整的對象和方法列表可以通過運行ubus -v list命令來得到。
下面這個簡單的例子調用了log對象和write方法,它需要提供一個event參數傳遞進去。使用ubus命令行國內根據是,需要如下所示命令:
ubus call log write '{ "event": "Foo" }'
LuCI2為ubus通訊提供了一個叫做L.rpc.declare的工具,它可以如魔法般的幫助JavaScript訪問ubus方法。注意,定義聲明方法的時候方法並不被執行,參數也未傳遞,這是為以后調用准備的一個方法。下面這個示例的方法調用了log對象的write方法:
var writeToLog = L.rpc.declare({
object: 'log',
method: 'write',
params: [ 'event' ]
})
定義了一次這個方法后,可以在任意時間通過如下示例簡單調用:
writeToLog('Foo');
在上面的例子中執行結果被忽略了,如果視圖需要處理ubus返回的數據時不能忽略執行結果。下面例子將通過訪問system對象的info方法來描述返回結果的處理。在命令行下通過以下命令訪問:
# ubus call system info
{
"uptime": 123,
"localtime": 1234567890,
"load": [
1,
2,
3
],
"memory": {
"total": 67108864,
"free": 33554432,
"shared": 0,
"buffered": 16777216
},
"swap": {
"total": 0,
"free": 0
}
}
在LuCI2(JavaScript)定義上面的訪問如下所示:
var readSystemInfo = L.rpc.declare({
object: 'system',
method: 'info',
expect: { memory: { } } /* 可選, 只提取結果的一部分memory */
})
通過簡單的調用.then方法可訪問結果數據
readSystemInfo().then(function(memory) {
console.log(memory);
});
怎樣測試
在"feeds.conf"中添加一個feed:
src-git luci2 git://git.openwrt.org/project/luci2/ui.git
安裝luci2包:
./scripts/feeds update ./scripts/feeds install luci2
在menuconfig中勾選Luci2包並編譯新固件。![]()
