docs: Synchronize with Wiki
The Wiki on GitHub has newer version of documentation. Still we may have references to the docs folder. Update all pages but also add a notice: See [online wiki](https://github.com/openwrt/luci/wiki/) for latest version. The LAR page is removed because there no any mentions of it anywhere in Luci. This closes #2360 Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
This commit is contained in:
parent
b98b8d6d6a
commit
155d2bdd6e
11 changed files with 777 additions and 694 deletions
159
docs/CBI.md
159
docs/CBI.md
|
@ -1,78 +1,83 @@
|
|||
# Writing LuCI CBI models
|
||||
|
||||
# CBI models
|
||||
are Lua files describing the structure of an UCI config file and the resulting HTML form to be evaluated by the CBI parser.<br />
|
||||
All CBI model files must return an object of type **luci.cbi.Map**.<br />
|
||||
For a commented example of a CBI model, see the [Writing Modules tutorial](ModulesHowTo.md#cbimodels).
|
||||
See [online wiki](https://github.com/openwrt/luci/wiki/CBI) for latest version.
|
||||
|
||||
The scope of a CBI model file is automatically extended by the contents of the module **luci.cbi** and the _translate_ function from **luci.i18n**
|
||||
CBI models are Lua files describing the structure of an UCI config file and the resulting HTML form to be evaluated by the CBI parser.<br />
|
||||
All CBI model files must return an object of type `luci.cbi.Map`.<br />
|
||||
For a commented example of a CBI model, see the [Writing Modules tutorial](./ModulesHowTo.md).
|
||||
|
||||
The scope of a CBI model file is automatically extended by the contents of the module `luci.cbi` and the `translate` function from `luci.i18n`.
|
||||
|
||||
This Reference covers **the basics** of the CBI system.
|
||||
|
||||
|
||||
## class Map (_config, title, description_)
|
||||
## class Map (config, title, description)
|
||||
This is the root object of the model.
|
||||
|
||||
* **config:** configuration filename to be mapped, see [UCI documentation](https://openwrt.org/docs/guide-user/base-system/uci) and the files in /etc/config
|
||||
* **title:** title shown in the UI
|
||||
* **description:** description shown in the UI
|
||||
* `config:` configuration filename to be mapped, see [UCI documentation](https://openwrt.org/docs/guide-user/base-system/uci) and the files in `/etc/config`
|
||||
* `title:` title shown in the UI
|
||||
* `description:` description shown in the UI
|
||||
|
||||
#### function :section (_sectionclass_, ...)
|
||||
#### function :section (sectionclass, ...)
|
||||
Creates a new section
|
||||
* **sectionclass**: a class object of the section
|
||||
* `sectionclass`: a class object of the section
|
||||
* _additional parameters passed to the constructor of the section class_
|
||||
|
||||
----
|
||||
|
||||
## class NamedSection (_name, type, title, description_)
|
||||
## class NamedSection (name, type, title, description)
|
||||
An object describing an UCI section selected by the name.<br />
|
||||
To instantiate use: `Map:section(NamedSection, "name", "type", "title", "description")`
|
||||
|
||||
* **name:** UCI section name
|
||||
* **type:** UCI section type
|
||||
* **title:** The title shown in the UI
|
||||
* **description:** description shown in the UI
|
||||
* `name:` UCI section name
|
||||
* `type:` UCI section type
|
||||
* `title:` The title shown in the UI
|
||||
* `description:` description shown in the UI
|
||||
|
||||
#### function :option(_optionclass_, ...)
|
||||
#### function :option(optionclass, ...)
|
||||
Creates a new option
|
||||
* **optionclass:** a class object of the section
|
||||
* `optionclass:` a class object of the section
|
||||
* _additional parameters passed to the constructor of the option class_
|
||||
|
||||
#### property .addremove = false
|
||||
Allows the user to remove and recreate the configuration section.
|
||||
|
||||
#### property .dynamic = false
|
||||
Marks this section as dynamic. Dynamic sections can contain an undefinded number of completely userdefined options.
|
||||
Marks this section as dynamic.
|
||||
Dynamic sections can contain an undefinded number of completely userdefined options.
|
||||
|
||||
#### property .optional = true
|
||||
Parse optional options
|
||||
|
||||
----
|
||||
|
||||
## class TypedSection (_type, title, description_)
|
||||
## class TypedSection (type, title, description)
|
||||
An object describing a group of UCI sections selected by their type.<br />
|
||||
To instantiate use: `Map:section(TypedSection, "type", "title", "description")`
|
||||
* **type:** UCI section type
|
||||
* **title:** The title shown in the UI
|
||||
* **description:** description shown in the UI
|
||||
* `type:` UCI section type
|
||||
* `title:` The title shown in the UI
|
||||
* `description:` description shown in the UI
|
||||
|
||||
#### function :option(_optionclass_, ...)
|
||||
#### function :option(optionclass, ...)
|
||||
Creates a new option
|
||||
**optionclass:** a class object of the section
|
||||
_additional parameters passed to the constructor of the option class_
|
||||
* `optionclass:` a class object of the section
|
||||
* _additional parameters passed to the constructor of the option class_
|
||||
|
||||
#### function :depends(_key, value_)
|
||||
Only select those sections where _key == value_ <br />
|
||||
#### function :depends(key, value)
|
||||
Only show this option field if another option `key` is set to `value` in the same section.<br />
|
||||
If you call this function several times the dependencies will be linked with **"or"**
|
||||
|
||||
#### function .filter(_self, section_) -abstract-
|
||||
#### function .filter(self, section) -abstract-
|
||||
You can override this function to filter certain sections that will not be parsed.
|
||||
The filter function will be called for every section that should be parsed and returns **nil** for sections that should be filtered. For all other sections it should return the section name as given in the second parameter.
|
||||
The filter function will be called for every section that should be parsed and returns `nil` for sections that should be filtered.
|
||||
For all other sections it should return the section name as given in the second parameter.
|
||||
|
||||
#### property .addremove = false
|
||||
Allows the user to remove and recreate the configuration section
|
||||
|
||||
#### property .dynamic = false
|
||||
Marks this section as dynamic. Dynamic sections can contain an undefinded number of completely userdefined options.
|
||||
Marks this section as dynamic.
|
||||
Dynamic sections can contain an undefinded number of completely userdefined options.
|
||||
|
||||
#### property .optional = true
|
||||
Parse optional options
|
||||
|
@ -82,16 +87,16 @@ Do not show UCI section names
|
|||
|
||||
----
|
||||
|
||||
## class Value (_option, title, description_)
|
||||
## class Value (option, title, description)
|
||||
An object describing an option in a section of a UCI File. Creates a standard text field in the formular.<br />
|
||||
To instantiate use: `NamedSection:option(Value, "option", "title", "description")`<br />
|
||||
or `TypedSection:option(Value, "option", "title", "description")`
|
||||
* **option:** UCI option name
|
||||
* **title:** The title shown in the UI
|
||||
* **description:** description shown in the UI
|
||||
* `option:` UCI option name
|
||||
* `title:` The title shown in the UI
|
||||
* `description:` description shown in the UI
|
||||
|
||||
#### function :depends(key, value)
|
||||
Only show this option field if another option _key_ is set to _value_ in the same section.<br />
|
||||
Only show this option field if another option `key` is set to `value` in the same section.<br />
|
||||
If you call this function several times the dependencies will be linked with **"or"**
|
||||
|
||||
#### function :value(key, value)
|
||||
|
@ -101,10 +106,10 @@ Convert this text field into a combobox if possible and add a selection option.
|
|||
The default value
|
||||
|
||||
#### property .maxlength = nil
|
||||
The maximum inputlength (of chars) of the value
|
||||
The maximum input length (of chars) of the value
|
||||
|
||||
#### property .optional = false
|
||||
Marks this option as optional, implies .rmempty = true
|
||||
Marks this option as optional, implies `.rmempty = true`
|
||||
|
||||
#### property .rmempty = true
|
||||
Removes this option from the configuration file when the user enters an empty value
|
||||
|
@ -114,30 +119,30 @@ The maximum number of chars displayed by form field
|
|||
|
||||
----
|
||||
|
||||
## class ListValue (_option, title, description_)
|
||||
## class ListValue (option, title, description)
|
||||
An object describing an option in a section of a UCI File.<br />
|
||||
Creates a list box or list of radio (for selecting one of many choices) in the formular.<br />
|
||||
To instantiate use: `NamedSection:option(ListValue, "option", "title", "description")`<br />
|
||||
or `TypedSection:option(ListValue, "option", "title", "description")`
|
||||
* **option:** UCI option name
|
||||
* **title:** The title shown in the UI
|
||||
* **description:** description shown in the UI
|
||||
* `option:` UCI option name
|
||||
* `title:` The title shown in the UI
|
||||
* `description:` description shown in the UI
|
||||
|
||||
#### function :depends(key, value)
|
||||
Only show this option field if another option _key_ is set to _value_ in the same section.<br />
|
||||
Only show this option field if another option `key` is set to `value` in the same section.<br />
|
||||
If you call this function several times the dependencies will be linked with **"or"**
|
||||
|
||||
#### function :value(_key, value_)
|
||||
#### function :value(key, value)
|
||||
Adds an entry to the selection list
|
||||
|
||||
#### property .widget = "select"
|
||||
**"select"** shows a selection list, **"radio"** shows a list of radio buttons inside form
|
||||
`select` shows a selection list, `radio` shows a list of radio buttons inside form
|
||||
|
||||
#### property .default = nil
|
||||
The default value
|
||||
|
||||
#### property .optional = false
|
||||
Marks this option as optional, implies .rmempty = true
|
||||
Marks this option as optional, implies `.rmempty = true`
|
||||
|
||||
#### property .rmempty = true
|
||||
Removes this option from the configuration file when the user enters an empty value
|
||||
|
@ -147,17 +152,17 @@ The size of the form field
|
|||
|
||||
----
|
||||
|
||||
## class Flag (_option, title, description_)
|
||||
## class Flag (option, title, description)
|
||||
An object describing an option with two possible values in a section of a UCI File.<br />
|
||||
Creates a checkbox field in the formular.<br />
|
||||
To instantiate use: `NamedSection:option(Flag, "option", ""title", "description")`<br />
|
||||
To instantiate use: `NamedSection:option(Flag, "option", "title", "description")`<br />
|
||||
or `TypedSection:option(Flag, "option", "title", "description")`
|
||||
* **option:** UCI option name
|
||||
* **title:** The title shown in the UI
|
||||
* **description:** description shown in the UI
|
||||
* `option:` UCI option name
|
||||
* `title:` The title shown in the UI
|
||||
* `description:` description shown in the UI
|
||||
|
||||
#### function :depends (_key, value_)
|
||||
Only show this option field if another option _key_ is set to _value_ in the same section.<br />
|
||||
#### function :depends (key, value)
|
||||
Only show this option field if another option `key` is set to `value` in the same section.<br />
|
||||
If you call this function several times the dependencies will be linked with **"or"**
|
||||
|
||||
#### property .default = nil
|
||||
|
@ -170,31 +175,31 @@ the value that should be set if the checkbox is unchecked
|
|||
the value that should be set if the checkbox is checked
|
||||
|
||||
#### property .optional = false
|
||||
Marks this option as optional, implies .rmempty = true
|
||||
Marks this option as optional, implies `.rmempty = true`
|
||||
|
||||
#### property .rmempty = true
|
||||
Removes this option from the configuration file when the user enters an empty value
|
||||
|
||||
----
|
||||
|
||||
## class MultiValue (_option'', ''title'', ''description_)
|
||||
## class MultiValue (option, title, description)
|
||||
An object describing an option in a section of a UCI File.<br />
|
||||
Creates a list of checkboxed or a multiselectable list as form fields.<br />
|
||||
To instantiate use: `NamedSection:option(MultiValue, "option", ""title", "description")`<br />
|
||||
To instantiate use: `NamedSection:option(MultiValue, "option", "title", "description")`<br />
|
||||
or `TypedSection:option(MultiValue, "option", "title", "description")`
|
||||
* **option:** UCI option name
|
||||
* **title:** The title shown in the UI
|
||||
* **description:** description shown in the UI
|
||||
* `option:` UCI option name
|
||||
* `title:` The title shown in the UI
|
||||
* `description:` description shown in the UI
|
||||
|
||||
#### function :depends (_key, value_)
|
||||
Only show this option field if another option _key_ is set to _value_ in the same section.<br />
|
||||
#### function :depends (key, value)
|
||||
Only show this option field if another option `key` is set to `value` in the same section.<br />
|
||||
If you call this function several times the dependencies will be linked with **"or"**
|
||||
|
||||
#### function :value(_key, value_)
|
||||
#### function :value(key, value)
|
||||
Adds an entry to the list
|
||||
|
||||
#### property .widget = "checkbox"
|
||||
**"select"** shows a selection list, **"checkbox"** shows a list of checkboxes inside form
|
||||
`select` shows a selection list, `checkbox` shows a list of checkboxes inside form
|
||||
|
||||
#### property .delimiter = " "
|
||||
The string which will be used to delimit the values inside stored option
|
||||
|
@ -203,44 +208,44 @@ The string which will be used to delimit the values inside stored option
|
|||
The default value
|
||||
|
||||
#### property .optional = false
|
||||
Marks this option as optional, implies .rmempty = true
|
||||
Marks this option as optional, implies `.rmempty = true`
|
||||
|
||||
#### property .rmempty = true
|
||||
Removes this option from the configuration file when the user enters an empty value
|
||||
|
||||
#### property .size = nil
|
||||
The size of the form field (only used if property _.widget = "select"_)
|
||||
The size of the form field (only used if property `.widget = "select"`)
|
||||
|
||||
----
|
||||
|
||||
## class StaticList (_option, title, description_)
|
||||
Similar to the MultiValue, but stores selected Values into a UCI list instead of a character-separated option.
|
||||
## class StaticList (option, title, description)
|
||||
Similar to the `MultiValue`, but stores selected Values into a UCI list instead of a character-separated option.
|
||||
|
||||
----
|
||||
|
||||
## class DynamicList (_option, title, description_)
|
||||
## class DynamicList (option, title, description)
|
||||
A extensible list of user-defined values. Stores Values into a UCI list
|
||||
|
||||
----
|
||||
|
||||
## class DummyValue (_option, title, description_)
|
||||
## class DummyValue (option, title, description)
|
||||
Creates a readonly text in the form. !It writes no data to UCI!<br />
|
||||
To instantiate use: `NamedSection:option(DummyValue, "option", ""title", "description")`<br />
|
||||
To instantiate use: `NamedSection:option(DummyValue, "option", "title", "description")`<br />
|
||||
or `TypedSection:option(DummyValue, "option", "title", "description")`
|
||||
* **option:** UCI option name
|
||||
* **title:** The title shown in the UI
|
||||
* **description:** description shown in the UI
|
||||
* `option:` UCI option name
|
||||
* `title:` The title shown in the UI
|
||||
* `description:` description shown in the UI
|
||||
|
||||
#### property :depends (_key, value_)
|
||||
Only show this option field if another option _key_ is set to _value_ in the same section.<br />
|
||||
#### function :depends (key, value)
|
||||
Only show this option field if another option `key` is set to `value` in the same section.<br />
|
||||
If you call this function several times the dependencies will be linked with **"or"**
|
||||
|
||||
----
|
||||
|
||||
## class TextValue (_option, title, description_)
|
||||
## class TextValue (option, title, description)
|
||||
An object describing a multi-line textbox in a section in a non-UCI form.
|
||||
|
||||
----
|
||||
|
||||
## class Button (_option, title, description_)
|
||||
## class Button (option, title, description)
|
||||
An object describing a Button in a section in a non-UCI form.
|
||||
|
|
|
@ -1,66 +1,120 @@
|
|||
LuCI provides some of its libraries to external applications through a JSON-RPC API.
|
||||
# HowTo: Using the JSON-RPC API
|
||||
|
||||
See [online wiki](https://github.com/openwrt/luci/wiki/JsonRpcHowTo) for latest version.
|
||||
|
||||
LuCI provides some of its libraries to external applications through a [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) API.
|
||||
This Howto shows how to use it and provides information about available functions.
|
||||
|
||||
See also
|
||||
* wiki [rpcd](https://openwrt.org/docs/techref/rpcd)
|
||||
* wiki [ubus](https://openwrt.org/docs/techref/ubus)
|
||||
|
||||
# Basics
|
||||
LuCI comes with an efficient JSON De-/Encoder together with a JSON-RPC-Server which implements the *JSON-RPC 1.0_' and 2.0 (partly) specifications. The LuCI JSON-RPC server offers several independent APIs. Therefore you have to use '_different URLs for every exported library*.
|
||||
Assuming your LuCI-Installation can be reached through */cgi-bin/luci_' any exported library can be reached via '''/cgi-bin/luci/rpc/''LIBRARY_*.
|
||||
## Basics
|
||||
To enable the API, install the following package and restart `uhttpd`:
|
||||
|
||||
```bash
|
||||
opkg install luci-mod-rpc luci-lib-ipkg luci-compat
|
||||
/etc/init.d/uhttpd restart
|
||||
```
|
||||
|
||||
LuCI comes with an efficient JSON De-/Encoder together with a JSON-RPC-Server which implements the JSON-RPC 1.0 and 2.0 (partly) specifications.
|
||||
The LuCI JSON-RPC server offers several independent APIs.
|
||||
Therefore you have to use **different URLs for every exported library**.
|
||||
Assuming your LuCI-Installation can be reached through `/cgi-bin/luci`, any exported library can be reached via `/cgi-bin/luci/rpc/LIBRARY`.
|
||||
|
||||
|
||||
# Authentication
|
||||
Most exported libraries will require a valid authentication to be called with. If you get an *HTTP 403 Forbidden_' status code you are probably missing a valid authentication token. To get such a token you have to call the function '''login''' of the RPC-Library '''auth'''. Following our example from above this login function would be provided at '_/cgi-bin/luci/rpc/auth*. The function accepts 2 parameters: username and password (of a valid user account on the host system) and returns an authentication token.
|
||||
## Authentication
|
||||
Most exported libraries will require a valid authentication to be called with.
|
||||
If you get an `HTTP 403 Forbidden` status code you are probably missing a valid authentication token.
|
||||
To get such a token you have to call the `login` method of the RPC-Library `auth`.
|
||||
Following our example from above this login function would be provided at `/cgi-bin/luci/rpc/auth`.
|
||||
The function accepts 2 parameters: `username` and `password` (of a valid user account on the host system) and returns an authentication token.
|
||||
|
||||
If you want to call any exported library which requires an authentication token you have to *append it as an URL parameter _auth''''' to the RPC-Server URL. So instead of calling '''/cgi-bin/luci/rpc/''LIBRARY''''' you have to call '''/cgi-bin/luci/rpc/''LIBRARY''?auth=''TOKEN_*.
|
||||
Example:
|
||||
```sh
|
||||
curl http://<hostname>/cgi-bin/luci/rpc/auth --data '
|
||||
{
|
||||
"id": 1,
|
||||
"method": "login",
|
||||
"params": [
|
||||
"youruser",
|
||||
"somepassword"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
response:
|
||||
```json
|
||||
{"id":1,"result":"65e60c5a93b2f2c05e61681bf5e94b49","error":null}
|
||||
```
|
||||
|
||||
If you want to call any exported library which requires an authentication token you have to append it as an URL parameter auth to the RPC-Server URL.
|
||||
E.g. instead of calling `/cgi-bin/luci/rpc/LIBRARY` you should call `/cgi-bin/luci/rpc/LIBRARY?auth=TOKEN`.
|
||||
|
||||
If your JSON-RPC client is Cookie-aware (like most browsers are) you will receive the authentication token also with a session cookie and probably don't have to append it to the RPC-Server URL.
|
||||
|
||||
|
||||
# Exported Libraries
|
||||
## uci
|
||||
The UCI-Library */rpc/uci* offers functionality to interact with the Universal Configuration Interface.
|
||||
*Exported Functions:*
|
||||
* [(string) add(config, type)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.add)
|
||||
* [(integer) apply(config)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.apply)
|
||||
* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.changes (object) changes([config])]
|
||||
* [(boolean) commit(config)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.commit)
|
||||
* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.delete (boolean) delete(config, section[, option])]
|
||||
* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.delete_all (boolean) delete_all(config[, type])]
|
||||
* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.foreach (array) foreach(config[, type])]
|
||||
* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.get (mixed) get(config, section[, option])]
|
||||
* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.get_all (object) get_all(config[, section])]
|
||||
* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.get (mixed) get_state(config, section[, option])]
|
||||
* [(boolean) revert(config)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.revert)
|
||||
* [(name) section(config, type, name, values)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.section)
|
||||
* [(boolean) set(config, section, option, value)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.set)
|
||||
* [(boolean) tset(config, section, values)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.tset)
|
||||
## Exported Libraries
|
||||
### uci
|
||||
The UCI-Library `/rpc/uci` offers functionality to interact with the Universal Configuration Interface.
|
||||
|
||||
## uvl
|
||||
The UVL-Library */rpc/uvl* offers functionality to validate UCI files and get schemes describing UCI files.
|
||||
*Exported Functions:*
|
||||
* [(array) get_scheme(scheme)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.get_scheme)
|
||||
* [(array) validate(config, section, option)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.validate)
|
||||
* [(array) validate_config(config)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.validate_config)
|
||||
* [(array) validate_section(config, section)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.validate_section)
|
||||
* [(array) validate(config, section, option)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.validate_option)
|
||||
**Exported Functions:**
|
||||
* [(string) add(config, type)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.add)
|
||||
* [(integer) apply(config)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.apply)
|
||||
* [(object) changes([config])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.changes)
|
||||
* [(boolean) commit(config)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.commit)
|
||||
* [(boolean) delete(config, section[, option])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.delete)
|
||||
* [(boolean) delete_all(config[, type])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.delete_all)
|
||||
* [(array) foreach(config[, type])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.foreach)
|
||||
* [(mixed) get(config, section[, option])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.get)
|
||||
* [(object) get_all(config[, section])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.get_all)
|
||||
* [(mixed) get_state(config, section[, option])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.get)
|
||||
* [(boolean) revert(config)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.revert)
|
||||
* [(name) section(config, type, name, values)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.section)
|
||||
* [(boolean) set(config, section, option, value)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.set)
|
||||
* [(boolean) tset(config, section, values)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.tset)
|
||||
|
||||
## fs
|
||||
The Filesystem library */rpc/fs* offers functionality to interact with the filesystem on the host machine.
|
||||
*Exported Functions:*
|
||||
Example:
|
||||
```sh
|
||||
curl http://<hostname>/cgi-bin/luci/rpc/uci?auth=yourtoken --data '
|
||||
{
|
||||
"method": "get_all",
|
||||
"params": [ "network" ]
|
||||
}'
|
||||
```
|
||||
|
||||
* [Complete luci.fs library](http://luci.subsignal.org/api/luci/modules/luci.fs.html)
|
||||
*Note:* All functions are exported as they are except for _readfile'' which encodes its return value in base64 and ''writefile'' which only accepts base64 encoded data as second argument. Note that both functions will only be available when the ''luasocket_ packet is installed on the hostsystem.
|
||||
### fs
|
||||
The Filesystem library `/rpc/fs` offers functionality to interact with the filesystem on the host machine.
|
||||
|
||||
## sys
|
||||
The System library */rpc/sys* offers functionality to interact with the operating system on the host machine.
|
||||
*Exported Functions:*
|
||||
* [Complete luci.sys library](http://luci.subsignal.org/api/luci/modules/luci.sys.html)
|
||||
* [Complete luci.sys.group library](http://luci.subsignal.org/api/luci/modules/luci.sys.group.html) with prefix *group.*
|
||||
* [Complete luci.sys.net library](http://luci.subsignal.org/api/luci/modules/luci.sys.net.html) with prefix *net.*
|
||||
* [Complete luci.sys.process library](http://luci.subsignal.org/api/luci/modules/luci.sys.process.html) with prefix *process.*
|
||||
* [Complete luci.sys.user library](http://luci.subsignal.org/api/luci/modules/luci.sys.user.html) with prefix *user.*
|
||||
* [Complete luci.sys.wifi library](http://luci.subsignal.org/api/luci/modules/luci.sys.wifi.html) with prefix *wifi.*
|
||||
**Exported Functions:**
|
||||
|
||||
* [Complete luci.fs library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.fs.html)
|
||||
|
||||
**Note:** All functions are exported as they are except for `readfile` which encodes its return value in [Base64](https://en.wikipedia.org/wiki/Base64) and `writefile` which only accepts Base64 encoded data as second argument.
|
||||
Note that both functions will only be available when the `luasocket` packet is installed on the host system.
|
||||
|
||||
### sys
|
||||
The System library `/rpc/sys` offers functionality to interact with the operating system on the host machine.
|
||||
|
||||
**Exported Functions:**
|
||||
* [Complete luci.sys library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.html)
|
||||
* [Complete luci.sys.group library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.group.html) prefixing the method name with `group.methodname`.
|
||||
* [Complete luci.sys.net library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.net.html) prefixing the method name with `net.methodname`.
|
||||
* [Complete luci.sys.process library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.process.html) prefixing the method name with `process.methodname`.
|
||||
* [Complete luci.sys.user library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.user.html) with prefix `user`.
|
||||
* [Complete luci.sys.wifi library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.wifi.html) with prefix `wifi`.
|
||||
|
||||
Example:
|
||||
```sh
|
||||
curl http://<hostname>/cgi-bin/luci/rpc/sys?auth=yourtoken --data '
|
||||
{
|
||||
"method": "net.conntrack"
|
||||
}'
|
||||
```
|
||||
|
||||
### ipkg
|
||||
The IPKG library `/rpc/ipkg` offers functionality to interact with the package manager (IPKG or OPKG) on the host machine.
|
||||
|
||||
**Exported Functions:**
|
||||
* [Complete luci.model.ipkg library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.ipkg.html)
|
||||
|
||||
## ipkg
|
||||
The IPKG library */rpc/ipkg* offers functionality to interact with the package manager (IPKG or OPKG) on the host machine.
|
||||
*Exported Functions:*
|
||||
* [Complete luci.model.ipkg library](http://luci.subsignal.org/api/luci/modules/luci.model.ipkg.html)
|
||||
|
|
87
docs/LAR.md
87
docs/LAR.md
|
@ -1,87 +0,0 @@
|
|||
LAR is a simple archive format to pack multiple lua source files and arbitrary other resources into a single file.
|
||||
|
||||
|
||||
# Format Specification
|
||||
|
||||
A LAR archive file is divided into two parts: the payload and the index lookup table.
|
||||
All segments of the archive are 4 Byte aligned to ease reading and processing of the format.
|
||||
All integers are stored in network byte order, so an implementation has to use htonl() and htons() to properly read them.
|
||||
|
||||
Schema:
|
||||
|
||||
<payload:
|
||||
<member:
|
||||
<N*4 bytes: path of file #1>
|
||||
<N*4 bytes: data of file #1>
|
||||
>
|
||||
|
||||
<member:
|
||||
<N*4 bytes: path of file #2>
|
||||
<N*4 bytes: data of file #2>
|
||||
>
|
||||
|
||||
...
|
||||
|
||||
<member:
|
||||
<N*4 bytes: path of file #N>
|
||||
<N*4 bytes: data of file #N>
|
||||
>
|
||||
>
|
||||
|
||||
<index table:
|
||||
<entry:
|
||||
<uint32: offset for path of file #1> <uint32: length for path of file #1>
|
||||
<uint32: offset for data of file #1> <uint32: length for data of file #1>
|
||||
<uint16: type of file #1> <uint16: flags of file #1>
|
||||
>
|
||||
|
||||
<entry:
|
||||
<uint32: offset for path of file #2> <uint32: length for path of file #2>
|
||||
<uint32: offset for data of file #2> <uint32: length for data of file #2>
|
||||
<uint16: type of file #2> <uint16: flags of file #2>
|
||||
>
|
||||
|
||||
...
|
||||
|
||||
<entry:
|
||||
<uint32: offset for path of file #N> <uint32: length for path of file #N>
|
||||
<uint32: offset for data of file #N> <uint32: length for data of file #N>
|
||||
<uint16: type of file #N> <uint16: flags of file #N>
|
||||
>
|
||||
>
|
||||
|
||||
<uint32: offset for begin of index table>
|
||||
|
||||
|
||||
|
||||
# Processing
|
||||
|
||||
In order to process an LAR archive, an implementation would have to do the following steps:
|
||||
|
||||
## Read Index
|
||||
|
||||
1. Locate and open the archive file
|
||||
1. Seek to end of file - 4 bytes
|
||||
1. Read 32bit index offset and swap from network to native byte order
|
||||
1. Seek to index offset, calculate index length: filesize - index offset - 4
|
||||
1. Initialize a linked list for index table entries
|
||||
1. Read each index entry until the index length is reached, read and byteswap 4 * 32bit int and 2 * 16bit int
|
||||
1. Seek to begin of file
|
||||
|
||||
## Read Member
|
||||
|
||||
1. Read the archive index
|
||||
1. Iterate through the linked index list, perform the following steps for each entry
|
||||
1. Seek to the specified file path offset
|
||||
1. Read as much bytes as specified in the file path length into a buffer
|
||||
1. Compare the contents of the buffer against the path of the searched member
|
||||
1. If buffer and searched path are equal, seek to the specified file data offset
|
||||
1. Read data until the file data length is reached, return
|
||||
1. Select the next index table entry and repeat from step 3, if there is no next entry then return
|
||||
|
||||
# Reference implementation
|
||||
|
||||
A reference implementation can be found here:
|
||||
http://luci.subsignal.org/trac/browser/luci/trunk/contrib/lar
|
||||
|
||||
The lar.pl script is a simple packer for LAR archives and cli.c provides a utility to list and dump packed LAR archives.
|
161
docs/LMO.md
161
docs/LMO.md
|
@ -1,7 +1,12 @@
|
|||
LMO is a simple binary format to pack language strings into a more efficient form. Although it's suitable to store any kind of key-value table, it's only used for the LuCI *.po based translation system at the moment. The abbreviation "LMO" stands for "Lua Machine Objects" in the style of the GNU gettext *.mo format.
|
||||
# LMO - Lua Machine Objects
|
||||
|
||||
See [online wiki](https://github.com/openwrt/luci/wiki/LMO) for latest version.
|
||||
|
||||
# Format Specification
|
||||
LMO is a simple binary format to pack language strings into a more efficient form.
|
||||
Although it's suitable to store any kind of key-value table, it's only used for the LuCI *.po based translation system at the moment.
|
||||
The abbreviation "LMO" stands for "Lua Machine Objects" in the style of the GNU gettext *.mo format.
|
||||
|
||||
## Format Specification
|
||||
|
||||
A LMO file is divided into two parts: the payload and the index lookup table.
|
||||
All segments of the file are 4 Byte aligned to ease reading and processing of the format.
|
||||
|
@ -50,95 +55,95 @@ Schema:
|
|||
|
||||
|
||||
|
||||
# Processing
|
||||
## Processing
|
||||
|
||||
In order to process a LMO file, an implementation would have to do the following steps:
|
||||
|
||||
## Read Index
|
||||
### Read Index
|
||||
|
||||
1. Locate and open the archive file
|
||||
1. Seek to end of file - 4 bytes (sizeof(uint32_t))
|
||||
1. Read 32bit index offset and swap from network to native byte order
|
||||
1. Seek to index offset, calculate index length: filesize - index offset - 4
|
||||
1. Initialize a linked list for index table entries
|
||||
1. Read each index entry until the index length is reached, read and byteswap 4 * uint32_t for each step
|
||||
1. Seek to begin of file
|
||||
2. Seek to end of file - 4 bytes (sizeof(uint32_t))
|
||||
3. Read 32bit index offset and swap from network to native byte order
|
||||
4. Seek to index offset, calculate index length: filesize - index offset - 4
|
||||
5. Initialize a linked list for index table entries
|
||||
6. Read each index entry until the index length is reached, read and byteswap 4 * uint32_t for each step
|
||||
7. Seek to begin of file
|
||||
|
||||
## Read Entry
|
||||
### Read Entry
|
||||
|
||||
1. Calculate the unsigned 32bit hash of the entries key value (see "Hash Function" section below)
|
||||
1. Obtain the archive index
|
||||
1. Iterate through the linked index list, perform the following steps for each entry:
|
||||
1. Compare the entry hash value with the calculated hash from step 1
|
||||
2. If the hash values are equal proceed with step 4
|
||||
3. Select the next entry and repeat from step 3.1
|
||||
1. Seek to the file offset specified in the selected entry
|
||||
1. Read as much bytes as specified in the entry length into a buffer
|
||||
1. Return the buffer value
|
||||
2. Obtain the archive index
|
||||
3. Iterate through the linked index list, perform the following steps for each entry:
|
||||
1. Compare the entry hash value with the calculated hash from step 1
|
||||
2. If the hash values are equal proceed with step 4
|
||||
3. Select the next entry and repeat from step 3.1
|
||||
4. Seek to the file offset specified in the selected entry
|
||||
5. Read as much bytes as specified in the entry length into a buffer
|
||||
6. Return the buffer value
|
||||
|
||||
# Hash Function
|
||||
## Hash Function
|
||||
|
||||
The current LuCI-LMO implementation uses the "Super Fast Hash" function which was kindly put in the public domain by it's original author. See http://www.azillionmonkeys.com/qed/hash.html for details. Below is the C-Implementation of this function:
|
||||
|
||||
|
||||
#if (defined(__GNUC__) && defined(__i386__))
|
||||
#define sfh_get16(d) (*((const uint16_t *) (d)))
|
||||
#else
|
||||
#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
|
||||
+(uint32_t)(((const uint8_t *)(d))[0]) )
|
||||
#endif
|
||||
|
||||
uint32_t sfh_hash(const char * data, int len)
|
||||
{
|
||||
uint32_t hash = len, tmp;
|
||||
int rem;
|
||||
|
||||
if (len <= NULL) return 0;
|
||||
|
||||
rem = len & 3;
|
||||
len >>= 2;
|
||||
|
||||
/* Main loop */
|
||||
for (;len > 0; len--) {
|
||||
hash += sfh_get16(data);
|
||||
tmp = (sfh_get16(data+2) << 11) ^ hash;
|
||||
hash = (hash << 16) ^ tmp;
|
||||
data += 2*sizeof(uint16_t);
|
||||
hash += hash >> 11;
|
||||
}
|
||||
|
||||
/* Handle end cases */
|
||||
switch (rem) {
|
||||
case 3: hash += sfh_get16(data);
|
||||
hash ^= hash << 16;
|
||||
hash ^= data[sizeof(uint16_t)] << 18;
|
||||
hash += hash >> 11;
|
||||
break;
|
||||
case 2: hash += sfh_get16(data);
|
||||
hash ^= hash << 11;
|
||||
hash += hash >> 17;
|
||||
break;
|
||||
case 1: hash += *data;
|
||||
hash ^= hash << 10;
|
||||
hash += hash >> 1;
|
||||
}
|
||||
|
||||
/* Force "avalanching" of final 127 bits */
|
||||
hash ^= hash << 3;
|
||||
hash += hash >> 5;
|
||||
hash ^= hash << 4;
|
||||
hash += hash >> 17;
|
||||
hash ^= hash << 25;
|
||||
hash += hash >> 6;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
```c
|
||||
#if (defined(__GNUC__) && defined(__i386__))
|
||||
#define sfh_get16(d) (*((const uint16_t *) (d)))
|
||||
#else
|
||||
#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
|
||||
+(uint32_t)(((const uint8_t *)(d))[0]) )
|
||||
#endif
|
||||
|
||||
# Reference Implementation
|
||||
uint32_t sfh_hash(const char * data, int len)
|
||||
{
|
||||
uint32_t hash = len, tmp;
|
||||
int rem;
|
||||
|
||||
if (len <= 0 || data == NULL) return 0;
|
||||
|
||||
rem = len & 3;
|
||||
len >>= 2;
|
||||
|
||||
/* Main loop */
|
||||
for (;len > 0; len--) {
|
||||
hash += sfh_get16(data);
|
||||
tmp = (sfh_get16(data+2) << 11) ^ hash;
|
||||
hash = (hash << 16) ^ tmp;
|
||||
data += 2*sizeof(uint16_t);
|
||||
hash += hash >> 11;
|
||||
}
|
||||
|
||||
/* Handle end cases */
|
||||
switch (rem) {
|
||||
case 3: hash += sfh_get16(data);
|
||||
hash ^= hash << 16;
|
||||
hash ^= data[sizeof(uint16_t)] << 18;
|
||||
hash += hash >> 11;
|
||||
break;
|
||||
case 2: hash += sfh_get16(data);
|
||||
hash ^= hash << 11;
|
||||
hash += hash >> 17;
|
||||
break;
|
||||
case 1: hash += *data;
|
||||
hash ^= hash << 10;
|
||||
hash += hash >> 1;
|
||||
}
|
||||
|
||||
/* Force "avalanching" of final 127 bits */
|
||||
hash ^= hash << 3;
|
||||
hash += hash >> 5;
|
||||
hash ^= hash << 4;
|
||||
hash += hash >> 17;
|
||||
hash ^= hash << 25;
|
||||
hash += hash >> 6;
|
||||
|
||||
return hash;
|
||||
}
|
||||
```
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
A reference implementation can be found here:
|
||||
http://luci.subsignal.org/trac/browser/luci/trunk/libs/lmo/src
|
||||
https://github.com/openwrt/luci/blob/master/modules/luci-base/src/template_lmo.c
|
||||
|
||||
The lmo_po2lmo.c executable implements a *.po to *.lmo conversation utility and lmo_lookup.c is a simple *.lmo test utility.
|
||||
Lua bindings for lmo are defined in lmo_lualib.c and associated headers.
|
||||
The `po2lmo.c` executable implements a `*.po` to `*.lmo` conversation utility.
|
||||
Lua bindings for lmo are defined in `template_lualib.c` and associated headers.
|
||||
|
|
|
@ -1,148 +1,144 @@
|
|||
[[PageOutline(2-5, Table of Contents, floated)]]
|
||||
# New in LuCI 0.10
|
||||
|
||||
See [online wiki](https://github.com/openwrt/luci/wiki/LuCI-0.10) for latest version.
|
||||
|
||||
This document describes new features and incompatibilities to LuCI 0.9.x.
|
||||
It is targeted at module authors developing external addons to LuCI.
|
||||
|
||||
# I18N Changes
|
||||
## I18N Changes
|
||||
|
||||
## API
|
||||
### API
|
||||
|
||||
The call conventions for the i18n api changed, there is no dedicated translation
|
||||
key anymore and the english text is used for lookup instead. This was done to
|
||||
ease the maintenance of language files.
|
||||
|
||||
Code that uses _translate()'' or ''i18n()_ must be changed as follows:
|
||||
Code that uses `translate()` or `i18n()` must be changed as follows:
|
||||
|
||||
|
||||
-- old style:
|
||||
translate("some_text", "Some Text")
|
||||
translatef("some_format_text", "Some formatted Text: %d", 123)
|
||||
|
||||
-- new style:
|
||||
translate("Some Text")
|
||||
translatef("Some formatted Text: %d", 123)
|
||||
|
||||
```lua
|
||||
-- old style:
|
||||
translate("some_text", "Some Text")
|
||||
translatef("some_format_text", "Some formatted Text: %d", 123)
|
||||
|
||||
-- new style:
|
||||
translate("Some Text")
|
||||
translatef("Some formatted Text: %d", 123)
|
||||
```
|
||||
|
||||
Likewise for templates:
|
||||
|
||||
|
||||
<!-- old style: -->
|
||||
<%:some_text Some Text%>
|
||||
|
||||
<!-- new style: -->
|
||||
<%:Some Text%>
|
||||
|
||||
```html
|
||||
<!-- old style: -->
|
||||
<%:some_text Some Text%>
|
||||
|
||||
<!-- new style: -->
|
||||
<%:Some Text%>
|
||||
```
|
||||
|
||||
If code must support both LuCI 0.9.x and 0.10.x versions, it is suggested to write the calls as follows:
|
||||
|
||||
translate("Some Text", "Some Text")
|
||||
|
||||
```lua
|
||||
translate("Some Text", "Some Text")
|
||||
```
|
||||
|
||||
An alternative is wrapping translate() calls into a helper function:
|
||||
|
||||
function tr(key, alt)
|
||||
return translate(key) or translate(alt) or alt
|
||||
end
|
||||
|
||||
```lua
|
||||
function tr(key, alt)
|
||||
return translate(key) or translate(alt) or alt
|
||||
end
|
||||
```
|
||||
|
||||
... which is used as follows:
|
||||
|
||||
tr("some_key", "Some Text")
|
||||
|
||||
```lua
|
||||
tr("some_key", "Some Text")
|
||||
```
|
||||
|
||||
## Translation File Format
|
||||
### Translation File Format
|
||||
|
||||
Translation catalogs are now maintained in *.po format files. During build those get translated
|
||||
into [*.lmo archives](http://luci.subsignal.org/trac/wiki/Documentation/LMO).
|
||||
Translation catalogs are now maintained in `*.po` format files.
|
||||
During build those get translated into [*.lmo archives](https://github.com/openwrt/luci/wiki/LMO).
|
||||
|
||||
LuCI ships a [utility script](http://luci.subsignal.org/trac/browser/luci/branches/luci-0.10/build/i18n-lua2po.pl)
|
||||
in the build/ directory to convert old Lua translation files to the *.po format. The generated *.po files should
|
||||
be placed in the appropriate subdirectories within the top po/ file in the LuCI source tree.
|
||||
|
||||
### Components built within the LuCI tree
|
||||
#### Components built within the LuCI tree
|
||||
|
||||
If components using translations are built along with the LuCI tree, the newly added *.po file are automatically
|
||||
compiled into *.lmo archives during the build process. In order to bundle the appropriate *.lmo files into the
|
||||
corresponding *.ipk packages, component Makefiles must include a "PO" variable specifying the files to include.
|
||||
|
||||
Given a module _applications/example/'' which uses ''po/en/example.po'' and ''po/en/example-extra.po_,
|
||||
the _applications/example/Makefile_ must be changed as follows:
|
||||
Given a module `applications/example/` which uses `po/en/example.po` and `po/en/example-extra.po`,
|
||||
the `applications/example/Makefile` must be changed as follows:
|
||||
|
||||
|
||||
PO = example example-extra
|
||||
|
||||
include ../../build/config.mk
|
||||
include ../../build/module.mk
|
||||
|
||||
```Makefile
|
||||
PO = example example-extra
|
||||
|
||||
### Standalone components
|
||||
include ../../build/config.mk
|
||||
include ../../build/module.mk
|
||||
```
|
||||
|
||||
Authors who externally package LuCI components must prepare required *.lmo archives themselves.
|
||||
To convert existing Lua based message catalogs to the *.po format, the build/i18n-lua2po.pl helper script can be used.
|
||||
In order to convert *.po files into *.lmo files, the standalone "po2lmo" utility must be compiled as follows:
|
||||
#### Standalone components
|
||||
|
||||
|
||||
$ svn co http://svn.luci.subsignal.org/luci/branches/luci-0.10/libs/lmo
|
||||
$ cd lmo/
|
||||
$ make
|
||||
$ ./src/po2lmo translations.po translations.lmo
|
||||
|
||||
Authors who externally package LuCI components must prepare required `*.lmo` archives themselves.
|
||||
To convert existing Lua based message catalogs to the `*.po` format, the `build/i18n-lua2po.pl` helper script can be used.
|
||||
In order to convert `*.po` files into `*.lmo` files, the standalone `po2lmo` utility must be compiled as follows:
|
||||
|
||||
```
|
||||
$ svn co http://svn.luci.subsignal.org/luci/branches/luci-0.10/libs/lmo
|
||||
$ cd lmo/
|
||||
$ make
|
||||
$ ./src/po2lmo translations.po translations.lmo
|
||||
```
|
||||
|
||||
Note that at the time of writing, the utility program needs Lua headers installed on the system in order to compile properly.
|
||||
|
||||
# CBI
|
||||
## CBI
|
||||
|
||||
## Datatypes
|
||||
### Datatypes
|
||||
|
||||
The server side UVL validation has been dropped to reduce space requirements on the target.
|
||||
Instead it is possible to define datatypes for CBI widgets now:
|
||||
|
||||
|
||||
opt = section:option(Value, "optname", "Title Text")
|
||||
opt.datatype = "ip4addr"
|
||||
|
||||
```lua
|
||||
opt = section:option(Value, "optname", "Title Text")
|
||||
opt.datatype = "ip4addr"
|
||||
```
|
||||
|
||||
User provided data is validated once on the frontend via JavaScript and on the server side prior to saving it.
|
||||
A list of possible datatypes can be found in the [luci.cbi.datatypes](http://luci.subsignal.org/trac/browser/luci/branches/luci-0.10/libs/web/luasrc/cbi/datatypes.lua#L26) class.
|
||||
A list of possible datatypes can be found in the [luci.cbi.datatypes](https://github.com/openwrt/luci/blob/master/modules/luci-compat/luasrc/cbi/datatypes.lua) class.
|
||||
|
||||
## Validation
|
||||
### Validation
|
||||
|
||||
Server-sided validator function can now return custom error messages to provide better feedback on invalid input.
|
||||
Server-side validator functions can now return custom error messages to provide better feedback on invalid input.
|
||||
|
||||
|
||||
opt = section:option(Value, "optname", "Title Text")
|
||||
|
||||
function opt.validate(self, value, section)
|
||||
if input_is_valid(value) then
|
||||
return value
|
||||
else
|
||||
return nil, "The value is invalid because ..."
|
||||
end
|
||||
end
|
||||
|
||||
```lua
|
||||
opt = section:option(Value, "optname", "Title Text")
|
||||
|
||||
## Tabs
|
||||
function opt.validate(self, value, section)
|
||||
if input_is_valid(value) then
|
||||
return value
|
||||
else
|
||||
return nil, "The value is invalid because ..."
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Tabs
|
||||
|
||||
It is now possible to break up CBI sections into multiple tabs to better organize longer forms.
|
||||
The TypedSection and NamedSection classes gained two new functions to define tabs, _tab()'' and ''taboption()_.
|
||||
The TypedSection and NamedSection classes gained two new functions to define tabs, `tab()` and `taboption()`.
|
||||
|
||||
|
||||
sct = map:section(TypedSection, "name", "type", "Title Text")
|
||||
|
||||
sct:tab("general", "General Tab Title", "General Tab Description")
|
||||
sct:tab("advanced", "Advanced Tab Title", "Advanced Tab Description")
|
||||
|
||||
opt = sct:taboption("general", Value, "optname", "Title Text")
|
||||
...
|
||||
|
||||
```lua
|
||||
sct = map:section(TypedSection, "name", "type", "Title Text")
|
||||
|
||||
The _tab()_ function is declares a new tab and takes up to three arguments:
|
||||
sct:tab("general", "General Tab Title", "General Tab Description")
|
||||
sct:tab("advanced", "Advanced Tab Title", "Advanced Tab Description")
|
||||
|
||||
opt = sct:taboption("general", Value, "optname", "Title Text")
|
||||
```
|
||||
|
||||
The `tab()` function declares a new tab and takes up to three arguments:
|
||||
* Internal name of the tab, must be unique within the section
|
||||
* Title text of the tab
|
||||
* Optional description text for the tab
|
||||
|
||||
The _taboption()'' function wraps ''option()_ and assigns the option object to the given tab.
|
||||
The `taboption()` function wraps `option()` and assigns the option object to the given tab.
|
||||
It takes up to five arguments:
|
||||
|
||||
* Name of the tab to assign the option to
|
||||
|
@ -151,52 +147,50 @@ It takes up to five arguments:
|
|||
* Title text of the option
|
||||
* Optional description text of the option
|
||||
|
||||
If tabs are used within a particular section, the _option()_ function must not be used,
|
||||
If tabs are used within a particular section, the `option()` function must not be used,
|
||||
doing so results in undefined behaviour.
|
||||
|
||||
## Hooks
|
||||
### Hooks
|
||||
|
||||
The CBI gained support for _hooks_ which can be used to trigger additional actions during the
|
||||
The CBI gained support for `hooks` which can be used to trigger additional actions during the
|
||||
life-cycle of a map:
|
||||
|
||||
|
||||
map = Map("config", "Title Text")
|
||||
|
||||
function map.on_commit(self)
|
||||
-- do something if the UCI configuration got committed
|
||||
end
|
||||
|
||||
```lua
|
||||
map = Map("config", "Title Text")
|
||||
|
||||
function map.on_commit(self)
|
||||
-- do something if the UCI configuration got committed
|
||||
end
|
||||
```
|
||||
|
||||
The following hooks are defined:
|
||||
|
||||
|| on_cancel || The user pressed cancel within a multi-step Delegator or a SimpleForm instance ||
|
||||
|| on_init || The CBI is about to render the Map object ||
|
||||
|| on_parse || The CBI is about to read received HTTP form values ||
|
||||
|| on_save, on_before_save || The CBI is about to save modified UCI configuration files ||
|
||||
|| on_after_save || Modified UCI configuration files just got saved
|
||||
|| on_before_commit || The CBI is about to commit the changes ||
|
||||
|| on_commit, on_after_commit, on_before_apply || Modified configurations got committed and the CBI is about to restart associated services ||
|
||||
|| on_apply, on_after_apply || All changes where completely applied (only works on Map instances with the apply_on_parse attribute set) ||
|
||||
* `on_cancel`: The user pressed cancel within a multistep Delegator or a SimpleForm instance
|
||||
* `on_init`: The CBI is about to render the Map object
|
||||
* `on_parse`: The CBI is about to read received HTTP form values
|
||||
* `on_save`, `on_before_save`: The CBI is about to save modified UCI configuration files
|
||||
* `on_after_save`: Modified UCI configuration files just got saved
|
||||
* `on_before_commit`: The CBI is about to commit the changes
|
||||
* `on_commit`, `on_after_commit`, `on_before_apply`: Modified configurations got committed and the CBI is about to restart associated services
|
||||
* `on_apply`, `on_after_apply`: All changes where completely applied (only works on Map instances with the apply_on_parse attribute set)
|
||||
|
||||
## Sortable Tables
|
||||
### Sortable Tables
|
||||
|
||||
TypedSection instances which use the "cbi/tblsection" template may now use a new attribute _sortable_ to allow the user to reorder table rows.
|
||||
TypedSection instances which use the `cbi/tblsection` template may now use a new attribute `sortable` to allow the user to reorder table rows.
|
||||
|
||||
|
||||
sct = map:section(TypedSection, "name", "type", "Title Text")
|
||||
sct.template = "cbi/tblsection"
|
||||
sct.sortable = true
|
||||
|
||||
...
|
||||
|
||||
```lua
|
||||
sct = map:section(TypedSection, "name", "type", "Title Text")
|
||||
sct.template = "cbi/tblsection"
|
||||
sct.sortable = true
|
||||
```
|
||||
|
||||
# JavaScript
|
||||
## JavaScript
|
||||
|
||||
The LuCI 0.10 branch introduced a new JavaScript file _xhr.js_ which provides support routines for XMLHttpRequest operations.
|
||||
Each theme must include this file in the <head> area of the document for forms to work correctly.
|
||||
The LuCI 0.10 branch introduced a new JavaScript file `xhr.js` which provides support routines for `XMLHttpRequest` operations.
|
||||
Each theme must include this file in the `<head>` area of the document for forms to work correctly.
|
||||
|
||||
It should be included like this:
|
||||
|
||||
|
||||
<script type="text/javascript" src="<%=resource%>/xhr.js"></script>
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="<%=resource%>/xhr.js"></script>
|
||||
```
|
||||
|
|
130
docs/Modules.md
130
docs/Modules.md
|
@ -1,94 +1,94 @@
|
|||
# Categories
|
||||
# Reference: LuCI Modules
|
||||
|
||||
See [online wiki](https://github.com/openwrt/luci/wiki/Modules) for latest version.
|
||||
|
||||
## Categories
|
||||
|
||||
The LuCI modules are divided into several category directories, namely:
|
||||
* applications (Single applications or plugins for other modules or applications)
|
||||
* i18n (Translation files)
|
||||
* libs (Independent libraries)
|
||||
* modules (Collections of applications)
|
||||
* themes (Frontend themes)
|
||||
* applications - Single applications or plugins for other modules
|
||||
* i18n - Translation files
|
||||
* libs - libraries of Luci
|
||||
* modules - main modules of Luci itself
|
||||
* protocols - network related plugins
|
||||
* themes - Frontend themes
|
||||
|
||||
Each module goes into a subdirectory of any of this category-directories.
|
||||
Each module goes into a subdirectory of this category-directories.
|
||||
|
||||
# Module directory
|
||||
## Module directory
|
||||
The contents of a module directory are as follows:
|
||||
|
||||
## Makefile
|
||||
### Makefile
|
||||
This is the module's makefile. If the module just contains Lua sourcecode or resources then the following Makefile should suffice.
|
||||
|
||||
include ../../build/config.mk
|
||||
include ../../build/module.mk
|
||||
|
||||
```Makefile
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
If you have C(++) code in your module your Makefile should at least contain the following things.
|
||||
|
||||
include ../../build/config.mk
|
||||
include ../../build/gccconfig.mk
|
||||
include ../../build/module.mk
|
||||
|
||||
compile:
|
||||
# Commands to compile and link your C-code
|
||||
# and to install them under the dist/ hierarchy
|
||||
|
||||
clean: luaclean
|
||||
# Commands to clean your compiled objects
|
||||
|
||||
LUCI_TITLE:=Title of my example applications
|
||||
LUCI_DEPENDS:=+some-package +libsome-library +luci-app-anotherthing
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
```
|
||||
|
||||
If you have C(++) code in your module you should include a `src/` subdirectory containing another Makefile supporting a `clean`, a `compile` and an `install` target.
|
||||
The `install` target should deploy its files relative to the predefined `$(DESTDIR)` variable, e.g.
|
||||
```
|
||||
mkdir -p $(DESTDIR)/usr/bin; cp myexecutable $(DESTDIR)/usr/bin/myexecutable
|
||||
```
|
||||
|
||||
## src
|
||||
The *src* directory is reserved for C sourcecode.
|
||||
### src
|
||||
The `src` directory is reserved for C sourcecode.
|
||||
|
||||
## luasrc
|
||||
*luasrc* contains all Lua sourcecode files. These will automatically be stripped or compiled depending on the Make target and are installed in the LuCI installation directory.
|
||||
### luasrc
|
||||
`luasrc` contains all Lua sourcecode files. These will automatically be stripped or compiled depending on the Make target and are installed in the LuCI installation directory.
|
||||
|
||||
## lua
|
||||
*lua* is equivalent to _luasrc_ but containing Lua files will be installed in the Lua document root.
|
||||
### lua
|
||||
`lua` is equivalent to `luasrc` but containing Lua files will be installed in the Lua document root.
|
||||
|
||||
## htdocs
|
||||
All files under *htdocs* will be copied to the document root of the target webserver.
|
||||
### htdocs
|
||||
All files under `htdocs` will be copied to the document root of the target webserver.
|
||||
|
||||
## root
|
||||
All directories and files under *root* will be copied to the installation target as they are.
|
||||
### root
|
||||
All directories and files under `root` will be copied to the installation target as they are.
|
||||
|
||||
## dist
|
||||
*dist* is reserved for the builder to create a working installation tree that will represent the filesystem on the target machine.
|
||||
*DO NOT* put any files there as they will get deleted.
|
||||
### dist
|
||||
`dist` is reserved for the builder to create a working installation tree that will represent the filesystem on the target machine.
|
||||
**DO NOT** put any files there as they will get deleted.
|
||||
|
||||
## ipkg
|
||||
*ipkg* contains IPKG package control files, like _preinst'', ''posinst'', ''prerm'', ''postrm''. ''conffiles_.
|
||||
### ipkg
|
||||
`ipkg` contains IPKG package control files, like `preinst`, `posinst`, `prerm`, `postrm`. `conffiles`.
|
||||
See IPKG documentation for details.
|
||||
|
||||
|
||||
# OpenWRT feed integration
|
||||
If you want to add your module to the LuCI OpenWRT feed you have to add several sections to the contrib/package/luci/Makefile.
|
||||
## OpenWRT feed integration
|
||||
If you want to add your module to the LuCI OpenWRT feed you have to add several sections to the `contrib/package/luci/Makefile`.
|
||||
|
||||
For a Web UI applications this is:
|
||||
|
||||
A package description:
|
||||
|
||||
define Package/luci-app-YOURMODULE
|
||||
$(call Package/luci/webtemplate)
|
||||
DEPENDS+=+some-package +some-other-package
|
||||
TITLE:=SHORT DESCRIPTION OF YOURMODULE
|
||||
endef
|
||||
|
||||
|
||||
```Makefile
|
||||
define Package/luci-app-YOURMODULE
|
||||
$(call Package/luci/webtemplate)
|
||||
DEPENDS+=+some-package +some-other-package
|
||||
TITLE:=SHORT DESCRIPTION OF YOURMODULE
|
||||
endef
|
||||
```
|
||||
|
||||
A package installation target:
|
||||
|
||||
define Package/luci-app-YOURMODULE/install
|
||||
$(call Package/luci/install/template,$(1),applications/YOURMODULE)
|
||||
endef
|
||||
|
||||
```Makefile
|
||||
define Package/luci-app-YOURMODULE/install
|
||||
$(call Package/luci/install/template,$(1),applications/YOURMODULE)
|
||||
endef
|
||||
```
|
||||
|
||||
A module build instruction:
|
||||
|
||||
ifneq ($(CONFIG_PACKAGE_luci-app-YOURMODULE),)
|
||||
PKG_SELECTED_MODULES+=applications/YOURMODULE
|
||||
endif
|
||||
|
||||
|
||||
```Makefile
|
||||
ifneq ($(CONFIG_PACKAGE_luci-app-YOURMODULE),)
|
||||
PKG_SELECTED_MODULES+=applications/YOURMODULE
|
||||
endif
|
||||
```
|
||||
|
||||
A build package call:
|
||||
|
||||
$(eval $(call BuildPackage,luci-app-YOURMODULE))
|
||||
|
||||
```Makefile
|
||||
$(eval $(call BuildPackage,luci-app-YOURMODULE))
|
||||
```
|
||||
|
|
|
@ -1,153 +1,171 @@
|
|||
*Note:* If you plan to integrate your module into LuCI, you should read the [wiki:Documentation/Modules Module Reference] before.
|
||||
# HowTo: Write Modules
|
||||
|
||||
See [online wiki](https://github.com/openwrt/luci/wiki/ModulesHowTo) for latest version.
|
||||
|
||||
**Note:** If you plan to integrate your module into LuCI, you should read the [Module Reference](./Modules.md) in advance.
|
||||
|
||||
This tutorial describes how to write your own modules for the LuCI WebUI.
|
||||
For this tutorial we refer to your LuCI installation directory as *lucidir_' (/usr/lib/lua/luci if you are working with an installed version) and assume your LuCI installation is reachable through your webserver via '_/cgi-bin/luci*.
|
||||
For this tutorial we refer to your LuCI installation directory as `lucidir` (`/usr/lib/lua/luci` on your OpenWRT device) and assume your LuCI installation is reachable through your webserver via `http://192.168.1.1/cgi-bin/luci`.
|
||||
|
||||
If you are working with the development environment replace *lucidir_' with '''''/path/to/your/luci/checkout''/applications/myapplication/luasrc''' (this is a default empty module you can use for your experiments) and your LuCI installation can probably be reached via http://localhost:8080/luci/ after you ran '_make runhttpd*.
|
||||
The recommended way to set up development environment:
|
||||
|
||||
Install OpenWRT on your router/device (You could use a QEMU or VirtualBox image instead)
|
||||
|
||||
Install SSHFS on your host
|
||||
|
||||
Mount your routers' root (/) someplace on your development host (eg. /mnt/router)
|
||||
|
||||
Then open /mnt/router/(lucidir) in your favorite development studio
|
||||
|
||||
Extra: Add configurations to your dev studio which will delete the luci cache (detailed below) and then open a browser window to your routers' configuration page in order to see your module/application.
|
||||
|
||||
|
||||
When testing, if you have edited index files, be sure to remove the folder `/tmp/luci-modulecache/*` and the file(s) `/tmp/luci-indexcache*`, then refresh the LUCI page to see your edits.
|
||||
|
||||
# Show me the way (The dispatching process)
|
||||
## Show me the way (The dispatching process)
|
||||
To write a module you need to understand the basics of the dispatching process in LuCI.
|
||||
LuCI uses a dispatching tree that will be built by executing the index-Function of every available controller.
|
||||
The CGI-environment variable *PATH_INFO* will be used as the path in this dispatching tree, e.g.: /cgi-bin/luci/foo/bar/baz
|
||||
will be resolved to foo.bar.baz
|
||||
The CGI-environment variable `PATH_INFO` will be used as the path in this dispatching tree, e.g.: `/cgi-bin/luci/foo/bar/baz`
|
||||
will be resolved to `foo.bar.baz`
|
||||
|
||||
To register a function in the dispatching tree, you can use the *entry*-function of _luci.dispatcher_. entry takes 4 arguments (2 are optional):
|
||||
|
||||
entry(path, target, title=nil, order=nil)
|
||||
|
||||
To register a function in the dispatching tree, you can use the `entry`-function of `luci.dispatcher`. It takes 4 arguments (2 are optional):
|
||||
```lua
|
||||
entry(path, target, title=nil, order=nil)
|
||||
```
|
||||
|
||||
* *path* is a table that describes the position in the dispatching tree: For example a path of {"foo", "bar", "baz"} would insert your node in foo.bar.baz.
|
||||
* *target* describes the action that will be taken when a user requests the node. There are several predefined ones of which the 3 most important (call, template, cbi) are described later on on this page
|
||||
* *title* defines the title that will be visible to the user in the menu (optional)
|
||||
* *order* is a number with which nodes on the same level will be sorted in the menu (optional)
|
||||
* `path` is a table that describes the position in the dispatching tree: For example a path of `{"foo", "bar", "baz"}` would insert your node in `foo.bar.baz`.
|
||||
* `target` describes the action that will be taken when a user requests the node. There are several predefined ones of which the 3 most important (call, template, cbi) are described later on this page
|
||||
* `title` defines the title that will be visible to the user in the menu (optional)
|
||||
* `order` is a number with which nodes on the same level will be sorted in the menu (optional)
|
||||
|
||||
You can assign more attributes by manipulating the node table returned by the entry-function. A few example attributes:
|
||||
|
||||
* *i18n* defines which translation file should be automatically loaded when the page gets requested
|
||||
* *dependent* protects plugins to be called out of their context if a parent node is missing
|
||||
* *leaf* stops parsing the request at this node and goes no further in the dispatching tree
|
||||
* *sysauth* requires the user to authenticate with a given system user account
|
||||
* `i18n` defines which translation file should be automatically loaded when the page gets requested
|
||||
* `dependent` protects plugins to be called out of their context if a parent node is missing
|
||||
* `leaf` stops parsing the request at this node and goes no further in the dispatching tree
|
||||
* `sysauth` requires the user to authenticate with a given system user account
|
||||
|
||||
|
||||
# It's all about names (Naming and the module file)
|
||||
Now that you know the basics about dispatching, we can start writing modules. But before you have to choose the category and name of your new digital child.
|
||||
Now that you know the basics about dispatching, we can start writing modules. Now, choose the category and name of your new digital child.
|
||||
|
||||
We assume you want to create a new application "myapp" with a module "mymodule".
|
||||
Let's assume you want to create a new application `myapp` with a module `mymodule`.
|
||||
|
||||
So you have to create a new subdirectory *_lucidir''/controller/myapp''' with a file '_mymodule.lua* with the following content:
|
||||
|
||||
module("luci.controller.myapp.mymodule", package.seeall)
|
||||
|
||||
function index()
|
||||
|
||||
end
|
||||
|
||||
So you have to create a new sub-directory `lucidir/controller/myapp` with a file `mymodule.lua` with the following content:
|
||||
```lua
|
||||
module("luci.controller.myapp.mymodule", package.seeall)
|
||||
|
||||
function index()
|
||||
|
||||
end
|
||||
```
|
||||
|
||||
The first line is required for Lua to correctly identify the module and create its scope.
|
||||
The index-Function will be used to register actions in the dispatching tree.
|
||||
The `index`-Function will be used to register actions in the dispatching tree.
|
||||
|
||||
|
||||
|
||||
# Teaching your new child (Actions)
|
||||
So it is there and has a name but it has no actions.
|
||||
## Teaching your new child (Actions)
|
||||
So it has a name, but no actions.
|
||||
|
||||
We assume you want to reuse your module myapp.mymodule that you begun in the last step.
|
||||
We assume you want to reuse your module myapp.mymodule that you began in the last step.
|
||||
|
||||
|
||||
## Actions
|
||||
Reopen *_lucidir_/controller/myapp/mymodule.lua* and just add a function to it so that its content looks like this example:
|
||||
### Actions
|
||||
Reopen `lucidir/controller/myapp/mymodule.lua` and just add a function to it with:
|
||||
```lua
|
||||
module("luci.controller.myapp.mymodule", package.seeall)
|
||||
|
||||
|
||||
module("luci.controller.myapp.mymodule", package.seeall)
|
||||
|
||||
function index()
|
||||
entry({"click", "here", "now"}, call("action_tryme"), "Click here", 10).dependent=false
|
||||
end
|
||||
|
||||
function action_tryme()
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write("Haha, rebooting now...")
|
||||
luci.sys.reboot()
|
||||
end
|
||||
|
||||
function index()
|
||||
entry({"click", "here", "now"}, call("action_tryme"), "Click here", 10).dependent=false
|
||||
end
|
||||
|
||||
function action_tryme()
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write("Haha, rebooting now...")
|
||||
luci.sys.reboot()
|
||||
end
|
||||
```
|
||||
|
||||
And now type */cgi-bin/luci/click/here/now_' ('_[http://localhost:8080/luci/click/here/now]* if you are using the development environment) in your browser.
|
||||
And now visit the path `/cgi-bin/luci/click/here/now` (`http://192.168.1.1/luci/click/here/now` if you are using the development environment) in your browser.
|
||||
|
||||
You see these action functions simple have to be added to a dispatching entry.
|
||||
These action functions simply have to be added to a dispatching entry.
|
||||
|
||||
As you might or might not know: CGI specification requires you to send a Content-Type header before you can send your content. You will find several shortcuts (like the one used above) as well as redirecting functions in the module *luci.http*
|
||||
As you may or may not know: CGI specification requires you to send a `Content-Type` header before you can send your content. You will find several shortcuts (like the one used above) as well as redirecting functions in the module `luci.http`
|
||||
|
||||
## Views
|
||||
If you only want to show the user a text or some interesting familiy photos it may be enough to use a HTML-template. These templates can also include some Lua code but be aware that writing whole office suites by only using these templates might be called "dirty" by other developers.
|
||||
### Views
|
||||
If you only want to show the user text or some interesting family photos, it may be enough to use an HTML-template.
|
||||
These templates can also include some Lua code but be aware that writing whole office-suites by only using these templates might be considered "dirty" by other developers.
|
||||
|
||||
Now let's create a little template *_lucidir_/view/myapp-mymodule/helloworld.htm* with the content:
|
||||
Now let's create a little template `lucidir/view/myapp-mymodule/helloworld.htm` with the content:
|
||||
|
||||
|
||||
<%+header%>
|
||||
<h1><%:Hello World%></h1>
|
||||
<%+footer%>
|
||||
|
||||
```html
|
||||
<%+header%>
|
||||
<h1><%:Hello World%></h1>
|
||||
<%+footer%>
|
||||
```
|
||||
|
||||
|
||||
and add the following line to the index-Function of your module file.
|
||||
|
||||
entry({"my", "new", "template"}, template("myapp-mymodule/helloworld"), "Hello world", 20).dependent=false
|
||||
|
||||
and add the following line to the `index`-Function of your module file.
|
||||
```lua
|
||||
entry({"my", "new", "template"}, template("myapp-mymodule/helloworld"), "Hello world", 20).dependent=false
|
||||
```
|
||||
|
||||
Now type */cgi-bin/luci/my/new/template_' ('_[http://localhost:8080/luci/my/new/template]* if you are using the development environment) in your browser.
|
||||
Now visit the path `/cgi-bin/luci/my/new/template` (`http://192.168.1.1/luci/my/new/template`) in your browser.
|
||||
|
||||
You may notice those fancy <% %>-Tags, these are [wiki:Documentation/Templates|template markups] used by the LuCI template processor.
|
||||
You may notice those special `<% %>`-Tags, these are [template markups](./Templates.md) used by the LuCI template processor.
|
||||
It is always good to include header and footer at the beginning and end of a template as those create the default design and menu.
|
||||
|
||||
## <a name=cbimodels></a> CBI models
|
||||
The CBI is one of the uber coolest features of LuCI. It creates a formular based user interface and saves its contents to a specific UCI config file. You only have to describe the structure of the configuration file in a CBI model file and Luci does the rest of the work. This includes generating, parsing and validating a XHTML form and reading and writing the UCI file.
|
||||
### CBI models
|
||||
The CBI is one of the coolest features of LuCI.
|
||||
It creates a formulae based user interface and saves its contents to a specific UCI config file.
|
||||
You only have to describe the structure of the configuration file in a CBI model file and Luci does the rest of the work.
|
||||
This includes generating, parsing and validating an XHTML form and reading and writing the UCI file.
|
||||
|
||||
So let's be serious at least for this paragraph and create a real pratical example *_lucidir_/model/cbi/myapp-mymodule/netifaces.lua* with the following contents:
|
||||
So let's be serious at least for this paragraph and create a practical example `lucidir/model/cbi/myapp-mymodule/netifaces.lua` with the following contents:
|
||||
|
||||
|
||||
m = Map("network", "Network") -- We want to edit the uci config file /etc/config/network
|
||||
|
||||
s = m:section(TypedSection, "interface", "Interfaces") -- Especially the "interface"-sections
|
||||
s.addremove = true -- Allow the user to create and remove the interfaces
|
||||
function s:filter(value)
|
||||
return value ~= "loopback" and value -- Don't touch loopback
|
||||
end
|
||||
s:depends("proto", "static") -- Only show those with "static"
|
||||
s:depends("proto", "dhcp") -- or "dhcp" as protocol and leave PPPoE and PPTP alone
|
||||
|
||||
p = s:option(ListValue, "proto", "Protocol") -- Creates an element list (select box)
|
||||
p:value("static", "static") -- Key and value pairs
|
||||
p:value("dhcp", "DHCP")
|
||||
p.default = "static"
|
||||
|
||||
s:option(Value, "ifname", "interface", "the physical interface to be used") -- This will give a simple textbox
|
||||
|
||||
s:option(Value, "ipaddr", translate("ip", "IP Address")) -- Ja, das ist eine i18n-Funktion ;-)
|
||||
|
||||
s:option(Value, "netmask", "Netmask"):depends("proto", "static") -- You may remember this "depends" function from above
|
||||
|
||||
mtu = s:option(Value, "mtu", "MTU")
|
||||
mtu.optional = true -- This one is very optional
|
||||
|
||||
dns = s:option(Value, "dns", "DNS-Server")
|
||||
dns:depends("proto", "static")
|
||||
dns.optional = true
|
||||
function dns:validate(value) -- Now, that's nifty, eh?
|
||||
return value:match("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") -- Returns nil if it doesn't match otherwise returns match
|
||||
end
|
||||
|
||||
gw = s:option(Value, "gateway", "Gateway")
|
||||
gw:depends("proto", "static")
|
||||
gw.rmempty = true -- Remove entry if it is empty
|
||||
|
||||
return m -- Returns the map
|
||||
|
||||
```lua
|
||||
m = Map("network", "Network") -- We want to edit the uci config file /etc/config/network
|
||||
|
||||
and of course don't forget to add something like this to your module's index-Function.
|
||||
|
||||
entry({"admin", "network", "interfaces"}, cbi("myapp-mymodule/netifaces"), "Network interfaces", 30).dependent=false
|
||||
|
||||
s = m:section(TypedSection, "interface", "Interfaces") -- Especially the "interface"-sections
|
||||
s.addremove = true -- Allow the user to create and remove the interfaces
|
||||
function s:filter(value)
|
||||
return value ~= "loopback" and value -- Don't touch loopback
|
||||
end
|
||||
s:depends("proto", "static") -- Only show those with "static"
|
||||
s:depends("proto", "dhcp") -- or "dhcp" as protocol and leave PPPoE and PPTP alone
|
||||
|
||||
There are many more features, see [wiki:Documentation/CBI the CBI reference] and the modules shipped with LuCI.
|
||||
p = s:option(ListValue, "proto", "Protocol") -- Creates an element list (select box)
|
||||
p:value("static", "static") -- Key and value pairs
|
||||
p:value("dhcp", "DHCP")
|
||||
p.default = "static"
|
||||
|
||||
s:option(Value, "ifname", "interface", "the physical interface to be used") -- This will give a simple textbox
|
||||
|
||||
s:option(Value, "ipaddr", translate("ip", "IP Address")) -- Yes, this is an i18n function ;-)
|
||||
|
||||
s:option(Value, "netmask", "Netmask"):depends("proto", "static") -- You may remember this "depends" function from above
|
||||
|
||||
mtu = s:option(Value, "mtu", "MTU")
|
||||
mtu.optional = true -- This one is very optional
|
||||
|
||||
dns = s:option(Value, "dns", "DNS-Server")
|
||||
dns:depends("proto", "static")
|
||||
dns.optional = true
|
||||
function dns:validate(value) -- Now, that's nifty, eh?
|
||||
return value:match("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") -- Returns nil if it doesn't match otherwise returns match
|
||||
end
|
||||
|
||||
gw = s:option(Value, "gateway", "Gateway")
|
||||
gw:depends("proto", "static")
|
||||
gw.rmempty = true -- Remove entry if it is empty
|
||||
|
||||
return m -- Returns the map
|
||||
```
|
||||
|
||||
and of course remember to add something like this to your module's `index`-Function.
|
||||
```lua
|
||||
entry({"admin", "network", "interfaces"}, cbi("myapp-mymodule/netifaces"), "Network interfaces", 30).dependent=false
|
||||
```
|
||||
|
||||
There are many more features. See [the CBI reference](./CBI.md) and the modules shipped with LuCI.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# LuCI Documentation
|
||||
|
||||
See Wiki [LuCI Technical Reference](https://openwrt.org/docs/techref/luci)
|
||||
|
||||
## API Reference
|
||||
|
||||
- [Client side JavaScript APIs](jsapi/index.html)
|
||||
|
|
|
@ -1,65 +1,68 @@
|
|||
## Templates
|
||||
|
||||
See [online wiki](https://github.com/openwrt/luci/wiki/Templates) for latest version.
|
||||
|
||||
LuCI has a simple regex based template processor which parses HTML-files to Lua functions and allows to store precompiled template files.
|
||||
The simplest form of a template is just an ordinary HTML-file. It will be printed out to the user as is.
|
||||
|
||||
In LuCI every template is an object with an own scope. It can therefore be instantiated and each instance can has a different scope. As every template processor. LuCI supports several special markups. Those are enclosed in `<% %>`-Tags.
|
||||
In LuCI every template is an object with an own scope
|
||||
It can therefore be instanced and each instance can have a different scope.
|
||||
As every template processor. LuCI supports several special markups. Those are enclosed in `<% %>`-Tags.
|
||||
|
||||
By adding `-` (dash) right after the opening `<%` every whitespace before the markup will be stripped. Adding a `-` right before the closing `%>` will equivalently strip every whitespace behind the markup.
|
||||
By adding `-` (dash) right after the opening `<%` every whitespace before the markup will be stripped.
|
||||
Adding a `-` right before the closing `%>` will equivalently strip every whitespace behind the markup.
|
||||
|
||||
|
||||
# Builtin functions and markups
|
||||
## Including Lua code
|
||||
## Builtin functions and markups
|
||||
### Including Lua code
|
||||
*Markup:*
|
||||
|
||||
<% code %>
|
||||
|
||||
```
|
||||
<% code %>
|
||||
```
|
||||
|
||||
|
||||
## Writing variables and function values
|
||||
### Writing variables and function values
|
||||
*Syntax:*
|
||||
|
||||
<% write (value) %>
|
||||
|
||||
```
|
||||
<% write (value) %>
|
||||
```
|
||||
|
||||
*Short-Markup:*
|
||||
|
||||
<%=value%>
|
||||
|
||||
```
|
||||
<%=value%>
|
||||
```
|
||||
|
||||
## Including templates
|
||||
### Including templates
|
||||
*Syntax:*
|
||||
|
||||
<% include (templatename) %>
|
||||
|
||||
```
|
||||
<% include (templatename) %>
|
||||
```
|
||||
|
||||
*Short-Markup:*
|
||||
|
||||
<%+templatename%>
|
||||
|
||||
```
|
||||
<%+templatename%>
|
||||
```
|
||||
|
||||
|
||||
## Translating
|
||||
### Translating
|
||||
*Syntax:*
|
||||
|
||||
<%= translate("Text to translate") %>
|
||||
|
||||
|
||||
```
|
||||
<%= translate("Text to translate") %>
|
||||
```
|
||||
|
||||
*Short-Markup:*
|
||||
|
||||
<%:Text to translate%>
|
||||
|
||||
```
|
||||
<%:Text to translate%>
|
||||
```
|
||||
|
||||
|
||||
## Commenting
|
||||
### Commenting
|
||||
*Markup:*
|
||||
|
||||
<%# comment %>
|
||||
|
||||
```
|
||||
<%# comment %>
|
||||
```
|
||||
|
||||
# Builtin constants
|
||||
| Name | Value |
|
||||
---------|---------
|
||||
|`REQUEST_URI`|The current URL (without server part)|
|
||||
|`controller`|Path to the Luci main dispatcher|
|
||||
|`resource`|Path to the resource directory|
|
||||
|`media`|Path to the active theme directory|
|
||||
## Builtin constants
|
||||
* `REQUEST_URI`: The current URL (without server part)
|
||||
* `controller`: Path to the Luci main dispatcher
|
||||
* `resource`: Path to the resource directory
|
||||
* `media`: Path to the active theme directory
|
||||
|
|
|
@ -1,76 +1,81 @@
|
|||
# HowTo: Create Themes
|
||||
*Note:* You should read the [Module Reference](Modules.md) and the [Template Reference](Templates.md) before.
|
||||
**Note:** You should read the [Module Reference](./Modules.md) and the [Template Reference](./Templates.md) before.
|
||||
|
||||
We assume you want to call your new theme _mytheme_. Make sure you replace this by your module name every time this is mentionend in this Howto.
|
||||
We assume you want to call your new theme `mytheme`.
|
||||
Make sure you replace this by your module name everytime this is mentionend in this Howto.
|
||||
|
||||
## Creating the structure
|
||||
At first create a new theme directory `themes/luci-theme-mytheme`.
|
||||
|
||||
Create a `Makefile` inside your theme directory with the following content:
|
||||
```Makefile
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
# Creating the structure
|
||||
At first create a new theme directory *themes/_mytheme_*.
|
||||
LUCI_TITLE:=Title of mytheme
|
||||
|
||||
Create a _Makefile_ inside your theme directory with the following content:
|
||||
|
||||
include ../../build/config.mk
|
||||
include ../../build/module.mk
|
||||
|
||||
include ../../luci.mk
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
```
|
||||
|
||||
Create the following directory structure inside your theme directory.
|
||||
* ipkg
|
||||
* htdocs
|
||||
* luci-static
|
||||
* _mytheme_
|
||||
* `mytheme`
|
||||
* luasrc
|
||||
* view
|
||||
* themes
|
||||
* _mytheme_
|
||||
* `mytheme`
|
||||
* root
|
||||
* etc
|
||||
* uci-defaults
|
||||
|
||||
|
||||
## Designing
|
||||
Create two LuCI HTML-Templates named `header.htm` and `footer.htm` under `luasrc/view/themes/mytheme`.
|
||||
The `header.htm` will be included at the beginning of each rendered page and the `footer.htm` at the end.
|
||||
So your `header.htm` will probably contain a DOCTYPE description, headers,
|
||||
the menu and layout of the page and the `footer.htm` will close all remaining open tags and may add a footer bar.
|
||||
But hey that's your choice you are the designer ;-).
|
||||
|
||||
# Designing
|
||||
Create two LuCI HTML-Templates named _header.htm'' and ''footer.htm'' under *luasrc/view/themes/''mytheme_*.
|
||||
The _header.htm'' will be included at the beginning of each rendered page and the ''footer.htm_ at the end.
|
||||
So your _header.htm'' will probably contain a DOCTYPE description, headers, the menu and layout of the page and the ''footer.htm_ will close all remaining open tags and may add a footer bar but hey that's your choice you are the designer ;-).
|
||||
Just make sure your `header.htm` begins with the following lines:
|
||||
```
|
||||
<%
|
||||
require("luci.http").prepare_content("text/html")
|
||||
-%>
|
||||
```
|
||||
|
||||
Just make sure your _header.htm_ *begins* with the following lines:
|
||||
|
||||
<%
|
||||
require("luci.http").prepare_content("text/html")
|
||||
-%>
|
||||
|
||||
|
||||
This makes sure your content will be sent to the client with the right content type. Of course you can adapt _text/html_ to your needs.
|
||||
This makes sure your content will be sent to the client with the right content type.
|
||||
Of course you can adapt `text/html` to your needs.
|
||||
|
||||
|
||||
Put any stylesheets, Javascripts, images, ... into *htdocs/luci-static/_mytheme_*.
|
||||
You should refer to this directory in your header and footer templates as: _<%=media%>''. That means for a stylesheet *htdocs/luci-static/''mytheme_/cascade.css* you would write:
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="<%=media%>/cascade.css" />
|
||||
|
||||
Put any stylesheets, Javascripts, images, ... into `htdocs/luci-static/mytheme`.
|
||||
You should refer to this directory in your header and footer templates as: `<%=media%>`.
|
||||
That means for a stylesheet `htdocs/luci-static/mytheme/cascade.css` you would write:
|
||||
```html
|
||||
<link rel="stylesheet" type="text/css" href="<%=media%>/cascade.css" />
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Making the theme selectable
|
||||
## Making the theme selectable
|
||||
If you are done with your work there are two last steps to do.
|
||||
To make your theme OpenWRT-capable and selectable on the settings page you should now create a file *root/etc/uci-defaults/luci-theme-_mytheme_* with the following contents:
|
||||
|
||||
#!/bin/sh
|
||||
uci batch <<-EOF
|
||||
set luci.themes.MyTheme=/luci-static/mytheme
|
||||
commit luci
|
||||
EOF
|
||||
|
||||
To make your theme OpenWrt-capable and selectable on the settings page you should now create a file `root/etc/uci-defaults/luci-theme-mytheme` with the following contents:
|
||||
```sh
|
||||
#!/bin/sh
|
||||
uci batch <<-EOF
|
||||
set luci.themes.MyTheme=/luci-static/mytheme
|
||||
commit luci
|
||||
EOF
|
||||
exit 0
|
||||
```
|
||||
|
||||
and another file *ipkg/postinst* with the following content:
|
||||
|
||||
#!/bin/sh
|
||||
[ -n "${IPKG_INSTROOT}" ] || {
|
||||
( . /etc/uci-defaults/luci-theme-mytheme ) && rm -f /etc/uci-defaults/luci-theme-mytheme
|
||||
}
|
||||
|
||||
and another file `ipkg/postinst` with the following content:
|
||||
```sh
|
||||
#!/bin/sh
|
||||
[ -n "${IPKG_INSTROOT}" ] || {
|
||||
( . /etc/uci-defaults/luci-theme-mytheme ) && rm -f /etc/uci-defaults/luci-theme-mytheme
|
||||
}
|
||||
```
|
||||
|
||||
This is some OpenWRT magic to correctly register the template with LuCI when it gets installed.
|
||||
This is some OpenWrt magic to correctly register the template with LuCI when it gets installed.
|
||||
|
||||
That's all. Now send your theme to the LuCI developers to get it into the development repository - if you like.
|
||||
|
|
114
docs/i18n.md
114
docs/i18n.md
|
@ -1,19 +1,103 @@
|
|||
# General
|
||||
Translations are saved in the folder po/ for each module and application. You find the reference in po/templates/<package>.pot. The actual translation files can be found at po/[lang]/[package].po .
|
||||
# Internationalization (i18n)
|
||||
|
||||
In order to use the commands below you need to have the _gettext'' utilities (''msgcat'', ''msgfmt'', ''msgmerge_) installed on your system.
|
||||
See [online wiki](https://github.com/openwrt/luci/wiki/i18n) for latest version.
|
||||
|
||||
# Rebuild po files
|
||||
## Use translation function
|
||||
|
||||
### Translations in JavaScript
|
||||
|
||||
Wrap translatable strings with `_()` e.g. `_('string to translate')` and the `i18n-scan.pl` and friends will correctly identify these strings as they do with all the existing translations.
|
||||
|
||||
If you have multi line strings you can split them with concatenation:
|
||||
```js
|
||||
var mystr = _('this string will translate ' +
|
||||
'correctly even though it is ' +
|
||||
'a multi line string!');
|
||||
```
|
||||
|
||||
You may also use line continuations `\` syntax:
|
||||
|
||||
```js
|
||||
var mystr = _('this string will translate \
|
||||
correctly even though it is \
|
||||
a multi line string');
|
||||
```
|
||||
|
||||
Usually if you have multiple sentences you may need to use a line break then use the `<br />` HTML tag:
|
||||
```js
|
||||
var mystr = _('Port number.<br />' +
|
||||
'E.g. 80 for HTTP');
|
||||
```
|
||||
|
||||
To simplify a job for translators it may be better to split into separate keys without the `<br />`:
|
||||
```js
|
||||
var mystr = _('Port number.') + '<br />' +
|
||||
_('E.g. 80 for HTTP');
|
||||
```
|
||||
Please use `<br />` and **not** `<br>` or `<br/>`.
|
||||
|
||||
If you have a link inside a translation then try to move its attributes out of a translation key like:
|
||||
```js
|
||||
var mystr = _('For further information <a %s>check the wiki</a>')
|
||||
.format('href="https://openwrt.org/docs/" target="_blank" rel="noreferrer"')
|
||||
```
|
||||
This will generate a full link with HTML `For further information <a href="https://openwrt.org/docs/" target="_blank" rel="noreferrer">check the wiki</a>`. The `noreferrer` is important when making a link that is opened in a new tab (`target="_blank"`).
|
||||
|
||||
### Translations in LuCI lua+html templates
|
||||
Use the `<%: text to translate %>` as documented on [Templates](./Templates.md)
|
||||
|
||||
### Translations in Lua controller code and Lua CBIs
|
||||
As hinted at in the Templates doc, the `%:` is actually invoking a `translate()` function.
|
||||
In most controller contexts, this is already available for you, but if necessary, is available for include in `luci.i18n.translate`
|
||||
|
||||
|
||||
## Translation files
|
||||
Translations are saved in the folder `po/` within each individual LuCI component directory, e.g. `applications/luci-app-acl/po/`.
|
||||
You find the reference in `po/templates/<package>.pot`.
|
||||
The actual translation files can be found at `po/[lang]/[package].po`.
|
||||
|
||||
In order to use the commands below you need to have the `gettext` utilities (`msgcat`, `msgfmt`, `msgmerge`) installed on your system.
|
||||
On Debian/Ubuntu you can install with `sudo apt install gettext`.
|
||||
|
||||
### Rebuild po files
|
||||
If you want to rebuild the translations after you made changes to a package this is an easy way:
|
||||
|
||||
./build/i18n-scan.pl applications/[application] > applications/[application]/po/templates/[application_basename].pot
|
||||
./build/i18n-update.pl applications/[application]/po
|
||||
|
||||
Example:
|
||||
./build/i18n-scan.pl applications/luci-app-firewall > applications/luci-app-firewall/po/templates/firewall.pot
|
||||
./build/i18n-update.pl applications/luci-app-firewall/po
|
||||
(note that the directory argument can be omitted for i18n-update.pl to update all apps)
|
||||
|
||||
*Note:* Some packages share translation files, in this case you need to scan through all their folders. The first command from above should then be:
|
||||
|
||||
./build/i18n-scan.pl applications/[package-1] applications/[package-2] applications/[package-n] > [location of shared template]/[application].pot
|
||||
./build/i18n-scan.pl applications/[application] > applications/[application]/po/templates/[application_basename].pot
|
||||
./build/i18n-update.pl applications/[application]/po
|
||||
|
||||
Example:
|
||||
|
||||
./build/i18n-scan.pl applications/luci-app-acl > applications/luci-app-acl/po/templates/acl.pot
|
||||
./build/i18n-update.pl applications/luci-app-acl/po
|
||||
|
||||
Note that the directory argument can be omitted for `i18n-update.pl` to update all apps.
|
||||
|
||||
Some packages share translation files, in this case you need to scan through all their folders.
|
||||
The first command from above should then be:
|
||||
|
||||
./build/i18n-scan.pl applications/[package-1] applications/[package-2] applications/[package-n] > [location of shared template]/[application].pot
|
||||
|
||||
*Note:* The translation catalog for the base system covers multiple components, use the following commands to update it:
|
||||
|
||||
./build/mkbasepot.sh
|
||||
./build/i18n-update.pl
|
||||
|
||||
### LMO files
|
||||
The `*.po` files are big so Luci needs them in a compact compiled [LMO format](./LMO.md).
|
||||
Luci reads `*.lmo` translations from `/usr/lib/lua/luci/i18n/` folder.
|
||||
E.g. `luci-app-acl` has an Arabic translation in `luci-i18n-acl-ar` package that installs `/usr/lib/lua/luci/i18n/acl.ar.lmo` file.
|
||||
|
||||
In order to quickly convert a single `.po` file to `.lmo` file for testing on the target system use the `po2lmo` utility.
|
||||
You will need to compile it from the `luci-base` module:
|
||||
|
||||
$ cd modules/luci-base/src/
|
||||
$ make po2lmo
|
||||
$ ./po2lmo
|
||||
Usage: ./po2lmo input.po output.lmo
|
||||
|
||||
Now you can compile and upload translation:
|
||||
|
||||
./po2lmo ../../../applications/luci-app-acl/po/ar/acl.po ./acl.ar.lmo
|
||||
scp ./acl.ar.lmo root@192.168.1.1:/usr/lib/lua/luci/i18n/
|
||||
|
||||
You can change language in [System /Language and Style](http://192.168.1.1/cgi-bin/luci/admin/system/system) and check the translation.
|
Loading…
Reference in a new issue