標籤:
Howto:如何寫Module(模組)-----------------這一部分主要是翻譯github上的document
注意:如果您打算將模組加入LUCI整合之前,您應該閱讀Module參考。
本教程介紹如何編寫自己的LUCI WebUI中的模組。在本教程中,我們假設您的luci安裝目錄lucidir(如果您目前使用的是安裝版/usr/lib/LUA/LUCI),並假設你的luci安裝經由/cgi-bin/LUCI訪問您的Web伺服器。
方法路線(針對調度過程)
要編寫模組,您需要瞭解LUCI調度過程的基礎知識。 LUCI使用,將通過執行每個可供使用的controller的索引功能建立一個調度樹。在CGI環境變數PATH_INFO將作為在這個調度樹,例如: /cgi-bin/luci/foo/bar/baz將被解析為foo.bar.baz
要註冊在調度樹中的功能,你可以使用luci.dispatcher的入口函數(Entry function)。進入需要4參數(其中的2個是可選的):
entry(path, target, title=nil, order=nil)
- path 是一個table結構,描述了在調度樹的的位置: 例如, {"foo", "bar", "baz"}的path 將被插入到 foo.bar.baz.
- target 描述當使用者請求該節點時將要採取的行動. 其中有三個預定義的對象是最重要的 (call, template, cbi) ,他們將在本頁的後邊描述
- title 定義了在可見的菜單中的選項,也就是最終luci介面中可以看到的標題內容(可選)
- order 主要依據這個數字來使同一水平上的節點在菜單上的排序 (可選)
你可以通過動作節點表的傳回值來給entry function賦更多的參數。一些典型的參數例子:
- i18n 定義了當頁面被請求時,自動載入的解釋程式(檔案)
- dependent 防止外掛程式在其父節點的確實,也就是添加外掛程式的依賴
- leaf 停止解析該節點的請求,並沒有更進一步的調度樹
- sysauth 要求使用者與給定系統的使用者帳戶進行認證
下邊就是命名與使用過程
既然你已經瞭解了調度的基礎知識,現在,我們可以開始寫module了。但是在這之前,我們必須先選擇目錄並且命名屬於你自己的新節點。
我們假設你想要建立一個新叫做myapp的應用,而且myapp的module是mymodule。
因此你必須建立一個子檔案夾lucidir/controller/myapp,在該檔案夾下建立一個mymodule.lua的檔案,檔案內容如下:
1 module("luci.controller.myapp.mymodule", package.seeall) 2 3 function index()4 5 end
第一行要求lua正確識別模組,並建立它的範圍。該index function將用於註冊在調度樹中的動作。
到目前為止,你就已經有了一個新的節點,只不過是該節點中沒有任何的功能。
我們假設你想要複用你的myapp.mymodule 模組,那麼你就必須開始最後一個步驟。
Actions
重新開啟lucidir/controller/myapp/mymodule.lua並且添加一個函數類似下邊的內容:
1 module("luci.controller.myapp.mymodule", package.seeall) 2 3 function index() 4 entry({"click", "here", "now"}, call("action_tryme"), "Click here", 10).dependent=false 5 6 end 7 8 function action_tryme() 9 luci.http.prepare_content("text/plain") 10 luci.http.write("Haha, rebooting now...") 11 luci.sys.reboot()12 end
現在在瀏覽器中輸入/cgi-bin/luci/click/here/now(http://192.168.1.1/luci/click/here/now 在你的openwrt系統上應該是)
你可以發現這些動作已經被添加到了調度樹上。
正如你可能會或可能不知道的:CGI規範要求您發送您的內容標題之前,首先發送Content-Type。你會發現幾個捷徑(如上面所使用的),以及在模組luci.http重新導向功能
Views
如果你僅僅是想展現給使用者一行字元或者一些有意思的家庭圖片,那麼使用HTML-template就足夠了。這些template也可以包含lua代碼,但是你必須瞭解到僅僅使用template可以會寫入髒代碼。
現在讓我們來建立一個小的lucidir/view/myapp-mymodule/helloworld.htm,內容如下:
1 <%+header%>2 <h1><%:Hello World%></h1> 3 <%+footer%>
然後添加下邊的一行index-function 到你的module檔案中去。
entry({"my", "new", "template"}, template("myapp-mymodule/helloworld"), "Hello world", 20).dependent=false
現在在你的瀏覽器中輸入 /cgi-bin/luci/my/new/template (在你的openwrt系統上是http://192.168.1.1/luci/my/new/template ) 。
你也許注意到了這些奇怪的標籤, 這些是被LuCI的template處理常式使用的標記. 在標準設計中使用header和footer,是個好的選擇.
CBI models
CBI是LuCI中最酷的功能. 它建立一個標準的使用者介面並且將內容儲存在一個特定的UCI config檔案中。.你只需要描述設定檔的結構,然後CBI程式會幫你完成剩下的部分。 這包括產生,解析和驗證HTML表單和讀取和寫入UCI檔案。
所以我們必須認真的建立一個執行個體 lucidir/model/cbi/myapp-mymodule/netifaces.lua 其內容如下:
1 m = Map("network", "Network") -- We want to edit the uci config file /etc/config/network 2 s = m:section(TypedSection, "interface", "Interfaces") -- Especially the "interface"-sections 3 s.addremove = true -- Allow the user to create and remove the interfaces 4 function s:filter(value) 5 return value ~= "loopback" and value -- Don‘t touch loopback 6 end s:depends("proto", "static") -- Only show thosewith"static" 7 s:depends("proto", "dhcp") -- or "dhcp" as protocol and leave PPPoE and PPTP alone 8 9 p = s:option(ListValue, "proto", "Protocol") -- Creates an element list (select box)10 p:value("static", "static") -- Key and value pairs11 p:value("dhcp", "DHCP")12 p.default = "static" 13 14 s:option(Value, "ifname", "interface", "the physical interface to be used") -- This will give a simple textbox 15 16 s:option(Value, "ipaddr", translate("IP Address")) -- Ja, das ist eine i18n-Funktion ;-) 17 18 s:option(Value, "netmask", "Netmask"):depends("proto", "static") -- You may remember this "depends" function from above 19 20 mtu = s:option(Value, "mtu", "MTU")21 mtu.optional = true -- This one is very optional 22 dns = s:option(Value, "dns", "DNS-Server")23 dns:depends("proto", "static")24 dns.optional = true25 function dns:validate(value) -- Now, that‘s nifty, eh?26 return value:match("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") -- Returns nil if it doesn‘t match otherwise returns match27 end 28 29 gw = s:option(Value, "gateway", "Gateway")30 gw:depends("proto", "static")31 gw.rmempty = true -- Remove entry if it is empty 32 33 return m -- Returns the map
當然,別忘記添加你自己的index-function函數。
1 entry({"admin", "network", "interfaces"}, cbi("myapp-mymodule/netifaces"), "Network interfaces", 30).dependent=false
如何編寫一個路由器的介面1-Luci開發入門