docs: refresh for JS and drop Lua references

- style with clean-jsdoc-theme (supports dark mode)
- add tutorials (jaguar has a problem with this structure)
- move doc gen stubs to doc_gen folder

This change moves the generated JS API docs from /luci/jsapi
to /luci via README.md which forms the index, and shall
point to a generated html file which exists. It currently
points to LuCI.html, which depends on JSDoc naming
conventions. So it's possible the link can break if modules
change names. But the TOC is always valid.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
Paul Donald
2026-02-16 01:06:46 +01:00
parent d612a68062
commit 44fd0155ff
16 changed files with 378 additions and 211 deletions

149
doc_gen/LMO.md Normal file
View File

@@ -0,0 +1,149 @@
# LMO - Lua Machine Objects
See [online wiki](https://github.com/openwrt/luci/wiki/LMO) for latest version.
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
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
1. Calculate the unsigned 32bit hash of the entries key value (see "Hash Function" section below)
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
The current LuCI-LMO implementation uses the "Super Fast Hash" function which was kindly put in the public domain by its original author. See http://www.azillionmonkeys.com/qed/hash.html for details. Below is the C-Implementation of this function:
```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
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:
https://github.com/openwrt/luci/blob/master/modules/luci-lua-runtime/src/template_lmo.c
The `po2lmo.c` executable implements a `*.po` to `*.lmo` conversation utility.
Lua bindings for lmo are defined in `template_lualib.c` and associated headers.

10
doc_gen/README.md Normal file
View File

@@ -0,0 +1,10 @@
# LuCI Documentation
Start with the [LuCI Client side JavaScript APIs](LuCI.html)
## Historical
The older [Lua API docs](api/index.html) are available for historical reference.

35
doc_gen/extra.css Normal file
View File

@@ -0,0 +1,35 @@
.navbar-item.github-home a {
background-image: url("data:image/svg+xml,%3Csvg width='98' height='96' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z' fill='%2324292f'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: 1.5em;
background-position: 1rem center;
padding-left: 3rem;
}
.signature-attributes {
font-style:italic;
font-weight:lighter;
font-variant:sub;
}
span.param-type {
color:#00918e;
}
.type-signature {
display: inline-block;
color:#00918e;
}
.type-signature::after {
content: ' ';
}
span.signature::after {
content: ' ';
}
table.props tbody td,
table.params tbody td {
padding: 0.5rem;
}

View File

@@ -0,0 +1,77 @@
# Using the JSON-RPC API
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
The API is installed by default.
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 `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.
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 a 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:**
See [LuCI API](LuCI.uci.html)
Example:
```sh
curl http://<hostname>/cgi-bin/luci/rpc/uci?auth=yourtoken --data '
{
"method": "get_all",
"params": [ "network" ]
}'
```
### fs
The Filesystem library `/rpc/fs` offers functionality to interact with the filesystem on the host machine.
**Exported Functions:**
See [fs API](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.

View File

@@ -0,0 +1,88 @@
# Authoring LuCI Modules
## Categories
The LuCI modules are divided into several category directories, namely:
* 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 its respective 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 JS sourcecode or resources then the following Makefile should suffice.
```Makefile
include $(TOPDIR)/rules.mk
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.
```sh
mkdir -p $(DESTDIR)/usr/bin; cp myexecutable $(DESTDIR)/usr/bin/myexecutable
```
### src
The `src` directory is reserved for C sourcecode.
### 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:
```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:
```Makefile
define Package/luci-app-YOURMODULE/install
$(call Package/luci/install/template,$(1),applications/YOURMODULE)
endef
```
A module build instruction:
```Makefile
ifneq ($(CONFIG_PACKAGE_luci-app-YOURMODULE),)
PKG_SELECTED_MODULES+=applications/YOURMODULE
endif
```
A build package call:
```Makefile
$(eval $(call BuildPackage,luci-app-YOURMODULE))
```

View File

@@ -0,0 +1,88 @@
# Creating Themes
**Note:** You have already read the [Module Reference](./Modules.md).
We assume you want to call your new theme `mytheme`.
Replace `mytheme` with your module name every time this is mentioned 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
LUCI_TITLE:=Title of mytheme
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature
```
Create the following directory structure inside your theme directory.
* htdocs
* luci-static
* `mytheme`
* resources
* root
* etc
* uci-defaults
* ucode
* template
* themes
* `mytheme`
## Designing
Create two LuCI ucode Templates named `header.ut` and `footer.ut` under `ucode/template/themes/mytheme`.
The `header.ut` will be included at the beginning of each rendered page and the `footer.ut` at the end.
So your `header.ut` will probably contain a DOCTYPE description, headers,
the menu and layout of the page and the `footer.ut` 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.ut` begins with the following lines:
```
{%
import { getuid, getspnam } from 'luci.core';
const boardinfo = ubus.call('system', 'board');
http.prepare_content('text/html; charset=UTF-8');
-%}
```
This ensures your content is 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`.
Refer to this directory in your header and footer templates as: `{{ media }}`.
That means for an icon `htdocs/luci-static/mytheme/logo.svg` you would write:
```html
<link rel="icon" href="{{ media }}/logo.svg" sizes="any">
```
## 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, 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
set luci.main.mediaurlbase=/luci-static/mytheme
commit luci
EOF
exit 0
```
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 correctly registers 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.

108
doc_gen/tutorials/i18n.md Normal file
View File

@@ -0,0 +1,108 @@
# Internationalisation (i18n)
## 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 for translation.
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. Use the `<br />` HTML tag like so:
```js
var mystr = _('Port number.') + '<br />' +
_('E.g. 80 for HTTP');
```
Use `<br />` and **not** `<br>` or `<br/>`.
If you have a link inside a translation, move its attributes out of a translation key:
```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 so that it is opened in a new tab (`target="_blank"`).
### Translations in Lua controller code and Lua CBIs
As hinted at in the Templates doc, the `%:` invokes 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/`.
The template is in `po/templates/<package>.pot`.
The individual language 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, install them with `sudo apt install gettext`.
### Initialize po files
When you add or update an app, run from your `applications/luci-app-acl/` app folder:
```sh
../../build/i18n-add-language.sh
```
This creates the skeleton .po files for all available languages open for translation for your app.
Or from the luci repo root:
```sh
./build/i18n-add-language.sh
```
This creates the skeleton .po files for all existing languages open for translation for all sub-folders.
### Rebuild po files (for existing languages)
After you make changes to a package, run:
```sh
./build/i18n-sync.sh applications/[application]
```
Example:
```sh
./build/i18n-sync.sh applications/luci-app-acl
```
This only updates those language .po files that already exist in `applications/luci-app-acl/po/`. See the previous step to add a new language.
Note: the directory argument can be omitted to update all po template and po files.
Some packages share translation files, in this case you need to scan through all their folders:
```sh
./build/i18n-scan.pl applications/[package-1] applications/[package-2] applications/[package-n] > [location of shared template]/[application].pot
```
This is what the `mkbasepot.sh` script does for the `luci-base` module:
```sh
./build/i18n-scan.pl \
modules/luci-base modules/luci-compat modules/luci-lua-runtime \
modules/luci-mod-network modules/luci-mod-status modules/luci-mod-system \
protocols themes \
> modules/luci-base/po/templates/base.pot
```
*Note:* The translation catalog for the base system covers multiple components. Use the following commands to update it:
```sh
./build/mkbasepot.sh
./build/i18n-update.pl
```
### Finally
You can change languages in [System /Language and Style](http://192.168.1.1/cgi-bin/luci/admin/system/system) and check the translation.

View File

@@ -0,0 +1,11 @@
{
"i18n": {
"title": "Internationalisation - i18n"
},
"JsonRpcHowTo": {
"title": "Using JSON RPC daemon"
},
"ThemesHowTo": {
"title": "Making Themes"
}
}