Add documentation converted from old Trac wiki pages

Signed-off-by: Jo-Philipp Wich <jow@openwrt.org>
This commit is contained in:
Jo-Philipp Wich 2015-06-16 10:11:03 +02:00
parent 37a9d6aee7
commit 4b11843e4c
11 changed files with 1186 additions and 0 deletions

248
documentation/CBI.md Normal file
View file

@ -0,0 +1,248 @@
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.
All CBI model files must return an object of type *luci.cbi.Map*. For a commented example of a CBI model, see the [[Documentation/ModulesHowTo#CBImodels|Writing Modules tutorial]].
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_)
This is the root object of the model.
* *config*: configuration name to be mapped, see uci documentation and the files in /etc/config
* *title*: title shown in the UI
* *description*: description shown in the UI
## :section (_sectionclass_, ...)
Creates a new section
* *sectionclass*: a class object of the section
* _additional parameters passed to the constructor of the section class_
----
# class NamedSection (_name'', ''type'', ''title'', ''description_)
An object describing an UCI section selected by the name.
Use [[#A.3Asection.28.27.27sectionclass.27.27.2C....29|Map:section(NamedSection, _name'', ''type'', ''title'', ''description_)]] to instantiate.
* *name*: section name
* *type*: section type
* *title*: The title shown in the UI
* *description*: description shown in the UI
## .addremove = false
Allows the user to remove and recreate the configuration section
## .dynamic = false
Marks this section as dynamic. Dynamic sections can contain an undefinded number of completely userdefined options.
## .optional = true
Parse optional options
## :option (_optionclass_, ...)
Creates a new option
* *optionclass*: a class object of the section
* _additional parameters passed to the constructor of the option class_
----
# class TypedSection (_type'', ''title'', ''description_)
An object describing a group of UCI sections selected by their type.
Use [[#A.3Asection.28.27.27sectionclass.27.27.2C....29|Map:section(TypedSection, _type'', ''title'', ''description_)]] to instantiate.
* *type*: section type
* *title*: The title shown in the UI
* *description*: description shown in the UI
## .addremove = false
Allows the user to remove and recreate the configuration section
## .dynamic = false
Marks this section as dynamic. Dynamic sections can contain an undefinded number of completely userdefined options.
## .optional = true
Parse optional options
## .anonymous = false
Do not show section names
## :depends (_key'', ''value_)
Only select those sections where the option _key'' == ''value_<br />
If you call this function several times the dependencies will be linked with *or*
## .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.
## :option (_optionclass_, ...)
Creates a new option
_optionclass_: a class object of the section
additional parameters passed to the constructor of the option class
----
# 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.
Use [[#A.3Aoption.28.27.27optionclass.27.27.2C....29|NamedSection:option(Value, _option'', ''title'', ''description'')]] or [[#A.3Aoption.28.27.27optionclass.27.27.2C....29-1|TypedSection:option(Value, ''option'', ''title'', ''description_)]] to instantiate.
* *option*: section name
* *title*: The title shown in the UI
* *description*: description shown in the UI
## .default = nil
The default value
## .maxlength = nil
The maximum length of the value
## .optional = false
Marks this option as optional, implies .rmempty = true
## .rmempty = true
Removes this option from the configuration file when the user enters an empty value
## .size = nil
The size of the form field
## :value (_key'', ''value'' = ''key_)
Convert this text field into a combobox if possible and add a selection option.
## :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 ListValue (_option'', ''title'', ''description_)
An object describing an option in a section of a UCI File. Creates a list box in the formular.
Use [[#A.3Aoption.28.27.27optionclass.27.27.2C....29|NamedSection:option(Value, _option'', ''title'', ''description'')]] or [[#A.3Aoption.28.27.27optionclass.27.27.2C....29-1|TypedSection:option(Value, ''option'', ''title'', ''description_)]] to instantiate.
* *option*: section name
* *title*: The title shown in the UI
* *description*: description shown in the UI
## .default = nil
The default value
## .optional = false
Marks this option as optional, implies .rmempty = true
## .rmempty = true
Removes this option from the configuration file when the user enters an empty value
## .size = nil
The size of the form field
## .widget = "select"
selects the form widget to be used
## :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*
## :value (_key'', ''value'' = ''key_)
Adds an entry to the selection list
----
# class Flag (_option'', ''title'', ''description_)
An object describing an option with two possible values in a section of a UCI File. Creates a checkbox field in the formular.
Use [[#A.3Aoption.28.27.27optionclass.27.27.2C....29|NamedSection:option(Value, _option'', ''title'', ''description'')]] or [[#A.3Aoption.28.27.27optionclass.27.27.2C....29-1|TypedSection:option(Value, ''option'', ''title'', ''description_)]] to instantiate.
* *option*: section name
* *title*: The title shown in the UI
* *description*: description shown in the UI
## .default = nil
The default value
## .disabled = 0
the value that shoudl be set if the checkbox is unchecked
## .enabled = 1
the value that should be set if the checkbox is checked
## .optional = false
Marks this option as optional, implies .rmempty = true
## .rmempty = true
Removes this option from the configuration file when the user enters an empty value
## .size = nil
The size of the form field
## :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 MultiValue (_option'', ''title'', ''description_)
An object describing an option in a section of a UCI File. Creates several checkboxed as form fields.
Use [[#A.3Aoption.28.27.27optionclass.27.27.2C....29|NamedSection:option(Value, _option'', ''title'', ''description'')]] or [[#A.3Aoption.28.27.27optionclass.27.27.2C....29-1|TypedSection:option(Value, ''option'', ''title'', ''description_)]] to instantiate.
* *option*: section name
* *title*: The title shown in the UI
* *description*: description shown in the UI
## .default = nil
The default value
## .delimiter = " "
The string which will be used to delimit the values
## .optional = false
Marks this option as optional, implies .rmempty = true
## .rmempty = true
Removes this option from the configuration file when the user enters an empty value
## .size = nil
The size of the form field
## .widget = "checkbox"
selects the form widget to be used
## :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*
## :value (_key'', ''value'' = ''key_)
Adds an entry to the checkbox list
----
# class DummyValue (_option'', ''title'', ''description_)
An object describing an option in a section of a UCI File. Creates a readonly field in the form.
Use [[#A.3Aoption.28.27.27optionclass.27.27.2C....29|NamedSection:option(Value, _option'', ''title'', ''description'')]] or [[#A.3Aoption.28.27.27optionclass.27.27.2C....29-1|TypedSection:option(Value, ''option'', ''title'', ''description_)]] to instantiate.
* *option*: section name
* *title*: The title shown in the UI
* *description*: description shown in the UI
## :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_)
An object describing a multi-line textbox in a section in a non-UCI form.
----
# class Button (_option'', ''title'', ''description_)
An object describing a Button in a section in a non-UCI form.
----
# class StaticList (_option'', ''title'', ''description_)
Similar to the MultiValue, but stores selected Values into a UCI list instead of a space-separated string.
----
# class DynamicList (_option'', ''title'', ''description_)
A list of user-defined values.

View file

@ -0,0 +1,66 @@
LuCI provides some of its libraries to external applications through a JSON-RPC API.
This Howto shows how to use it and provides information about available functions.
# 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_*.
# 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.
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_*.
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)
## 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)
## fs
The Filesystem library */rpc/fs* offers functionality to interact with the filesystem on the host machine.
*Exported Functions:*
* [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.
## 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.*
## 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
documentation/LAR.md Normal file
View file

@ -0,0 +1,87 @@
LAR is a simple archive format to pack multiple lua source files and arbitary 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.

144
documentation/LMO.md Normal file
View file

@ -0,0 +1,144 @@
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.
Only unsigned 32bit integers are used and stored in network byte order, so an implementation has to use htonl() to properly read them.
Schema:
<file:
<payload:
<entry #1: 4 byte aligned data>
<entry #2: 4 byte aligned data>
...
<entry #N: 4 byte aligned data>
>
<index table:
<entry #1:
<uint32_t: hash of the first key>
<uint32_t: hash of the first value>
<uint32_t: file offset of the first value>
<uint32_t: length of the first value>
>
<entry #2:
<uint32_t: hash of the second key>
<uint32_t: hash of the second value>
<uint32_t: file offset of the second value>
<uint32_t: length of the second value>
>
...
<entry #N:
<uint32_t: hash of the Nth key>
<uint32_t: hash of the Nth value>
<uint32_t: file offset of the Nth value>
<uint32_t: length of the Nth value>
>
>
<uint32_t: offset of the begin of index table>
>
# Processing
In order to process a LMO file, 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 (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
## 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
# 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;
}
# Reference Implementation
A reference implementation can be found here:
http://luci.subsignal.org/trac/browser/luci/trunk/libs/lmo/src
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.

202
documentation/LuCI-0.10.md Normal file
View file

@ -0,0 +1,202 @@
[[PageOutline(2-5, Table of Contents, floated)]]
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
## 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:
-- 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%>
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")
An alternative is wrapping translate() calls into a helper function:
function tr(key, alt)
return translate(key) or translate(alt) or alt
end
... which is used as follows:
tr("some_key", "Some Text")
## 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).
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
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:
PO = example example-extra
include ../../build/config.mk
include ../../build/module.mk
### Standalone components
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
## 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"
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.
## Validation
Server-sided validator function 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
## 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()_.
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")
...
The _tab()_ function is 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.
It takes up to five arguments:
* Name of the tab to assign the option to
* Option type, e.g. Value or DynamicList
* Option name
* 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,
doing so results in undefined behaviour.
## Hooks
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
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) ||
## 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.
sct = map:section(TypedSection, "name", "type", "Title Text")
sct.template = "cbi/tblsection"
sct.sortable = true
...
# 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.
It should be included like this:
<script type="text/javascript" src="<%=resource%>/xhr.js"></script>

94
documentation/Modules.md Normal file
View file

@ -0,0 +1,94 @@
# 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)
Each module goes into a subdirectory of any of this category-directories.
# Module directory
The contents of a module directory are as follows:
## 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
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
## 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.
## 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.
## 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.
## 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.
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
A package installation target:
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
A build package call:
$(eval $(call BuildPackage,luci-app-YOURMODULE))

View file

@ -0,0 +1,153 @@
*Note:* If you plan to integrate your module into LuCI, you should read the [wiki:Documentation/Modules Module Reference] before.
This tutorial describes how to write your own modules for the LuCI WebUI.
For this tutorial we refer to your LuCI installation direcotry 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*.
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*.
# 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
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)
* *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)
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
# 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.
We 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
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.
# Teaching your new child (Actions)
So it is there and has a name but it has no actions.
We assume you want to reuse your module myapp.mymodule that you begun 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:
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
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.
You see these action functions simple 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*
## 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.
Now let's create a little template *_lucidir_/view/myapp-mymodule/helloworld.htm* with the content:
<%+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
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.
You may notice those fancy <% %>-Tags, these are [wiki:Documentation/Templates|template markups] 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.
## 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.
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:
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
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
There are many more features, see [wiki:Documentation/CBI the CBI reference] and the modules shipped with LuCI.

View file

@ -0,0 +1,33 @@
# Checkout svn
svn co http://svn.luci.subsignal.org/luci/trunk
and change to that directory:
cd trunk
# Make your changes
Edit the files you want to change. If you add some new files you need to add them to the svn tree:
svn add <dir/files>
Where <dir/files> are the directories/files you have added. Its possible to specify multiple files/directories here.
# Use svn diff to generate a patch with your changes
To check if your changes look ok first do:
svn diff <dir/files>
and check the output. Again you can specify multiple dirs/directories here.
If everything looks like expected save the patch:
svn diff <dir/files> > ./mypatch.patch
# Submit patches
Use the [Ticket system](http://luci.subsignal.org/trac/newticket) to submit your patch.

View file

@ -0,0 +1,66 @@
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 instanciated and each instance can has a different scope. As every template processor. LuCI supports several special markups. Those are enclosed in *<% %>-Tags*.
By adding a *-_' 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.
<<BR>>
# Builtin functions and markups
## Including Lua code
*Markup:*
<% code %>
## Writing variables and function values
*Syntax:*
<% write (value) %>
*Short-Markup:*
<%=value%>
## Including templates
*Syntax:*
<% include (templatename) %>
*Short-Markup:*
<%+templatename%>
## Translating
*Syntax:*
<%= translate("Text to translate") %>
*Short-Markup:*
<%:Text to translate%>
## Commenting
*Markup:*
<%# 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||

View file

@ -0,0 +1,76 @@
# HowTo: Create Themes
*Note:* You should read the [wiki:Documentation/Modules Module Reference] and the [wiki:Documentation/Templates Template Reference] before.
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/_mytheme_*.
Create a _Makefile_ inside your theme directory with the following content:
include ../../build/config.mk
include ../../build/module.mk
Create the following directory structure inside your theme directory.
* ipkg
* htdocs
* luci-static
* _mytheme_
* luasrc
* view
* themes
* _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 ;-).
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.
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" />
# 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
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
}
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.

17
documentation/i18n.md Normal file
View file

@ -0,0 +1,17 @@
# General
Translations are saved in the folder 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.
# 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/[package] > po/templates/[application].pot
./build/i18n-update.pl po [application].po
*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] > po/templates/[application].pot