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包並編譯新固件。