summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-example/root/usr
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-example/root/usr')
-rwxr-xr-xapplications/luci-app-example/root/usr/libexec/rpcd/luci.example248
-rw-r--r--applications/luci-app-example/root/usr/share/luci/menu.d/luci-app-example.json27
-rw-r--r--applications/luci-app-example/root/usr/share/rpcd/acl.d/luci-app-example.json12
3 files changed, 279 insertions, 8 deletions
diff --git a/applications/luci-app-example/root/usr/libexec/rpcd/luci.example b/applications/luci-app-example/root/usr/libexec/rpcd/luci.example
new file mode 100755
index 0000000000..e3e5f8795e
--- /dev/null
+++ b/applications/luci-app-example/root/usr/libexec/rpcd/luci.example
@@ -0,0 +1,248 @@
+#!/usr/bin/env lua
+
+-- If you need filesystem access, use nixio.fs
+local fs = require "nixio.fs"
+
+-- LuCI JSON is used for checking the arguments and converting tables to JSON.
+local jsonc = require "luci.jsonc"
+
+-- Nixio provides syslog functionality
+local nixio = require "nixio"
+
+-- To access /etc/config files, use the uci module
+local UCI = require "luci.model.uci"
+
+-- Slight overkill, but leaving room to do log_info etcetera.
+local function log_to_syslog(level, message) nixio.syslog(level, message) end
+
+local function log_error(message)
+ log_to_syslog("err", "[luci.example]: " .. message)
+end
+
+local function using_uci_directly(section)
+ -- Rather than parse files in /etc/config, you can rely on the
+ -- luci.model.uci module.
+ local uci = UCI.cursor()
+
+ -- https://openwrt.github.io/luci/api/modules/luci.model.uci.html
+ local config_name = uci:get("example", section)
+
+ uci.unload("example")
+
+ if not config_name then
+ local msg = "'" .. section .. "' not found in /etc/config/example"
+ -- Send the log message to syslog so it can be found with logread
+ log_error(msg)
+
+ -- Convert a lua table into JSON notation and print to stdout
+ -- .stringify() is equivalent to cjson's .encode()
+ print(jsonc.stringify({uci_error = msg}))
+
+ -- Indicate failure in the return code
+ os.exit(1)
+ end
+
+ return config_name
+end
+
+-- The methods table defines all of the APIs to expose to rpcd.
+-- rpcd will execute this Lua file with the 'list' argument to discover the
+-- method names that can be presented over ubus, as well as any arguments
+-- those methods take.
+local methods = {
+ -- How to call this API:
+ -- echo '{"section": "first"}' | lua /usr/libexec/rpcd/luci.example call get_uci_value
+ -- echo '{"section": "does_not_exist"}' | lua /usr/libexec/rpcd/luci.example call get_uci_value
+ get_uci_value = {
+ -- Args are specified as a table, where the argument type is specified by example
+ -- The value is not used as a default.
+ args = {section = "a_string"},
+ -- A special key of 'call' points to a function definition for execution.
+ call = function(args)
+ -- A table for the result.
+ local r = {}
+ r.result = jsonc.stringify({
+ example_section = using_uci_directly(args.section)
+ })
+ -- The 'call' handler will refer to '.code', but also defaults if not found.
+ r.code = 0
+ -- Return the table object; the call handler will access the attributes
+ -- of the table.
+ return r
+ end
+ },
+ -- How to call this API:
+ -- echo '{}' | lua /usr/libexec/rpcd/luci.example call get_sample1
+ -- ubus call luci.example get_sample1
+ get_sample1 = {
+ call = function()
+ local r = {}
+ -- This structure does not map well to a JSONMap in the LuCI form setup.
+ -- It can be rendered as a table easily enough with loops.
+ r.result = jsonc.stringify({
+ num_cats = 1,
+ num_dogs = 2,
+ num_parakeets = 4,
+ is_this_real = false,
+ not_found = nil
+ })
+ return r
+ end
+ },
+ -- How to call this API:
+ -- echo '{}' | lua /usr/libexec/rpcd/luci.example call get_sample2
+ -- ubus call luci.example get_sample2
+ get_sample2 = {
+ call = function()
+ local r = {}
+ -- This is the structural data that JSONMap will work with in the JS file
+ local data = {
+ option_one = {
+ name = "Some string value",
+ value = "A value string",
+ parakeets = {"one", "two", "three"},
+ },
+ option_two = {
+ name = "Another string value",
+ value = "And another value",
+ parakeets = {3, 4, 5},
+ }
+ }
+ r.result = jsonc.stringify(data)
+ return r
+ end
+ }
+}
+
+local function parseInput()
+ -- Input parsing - the RPC daemon calls the Lua script and
+ -- sends input to it via stdin, not as an argument on the CLI.
+ -- Thus, any testing via the lua interpreter needs to be in the form
+ -- echo '{jsondata}' | lua /usr/libexec/rpcd/script call method_name
+ local parse = jsonc.new()
+ local done, err
+
+ while true do
+ local chunk = io.read(4096)
+ if not chunk then
+ break
+ elseif not done and not err then
+ done, err = parse:parse(chunk)
+ end
+ end
+
+ if not done then
+ print(jsonc.stringify({
+ error = err or "Incomplete input for argument parsing"
+ }))
+ os.exit(1)
+ end
+
+ return parse:get()
+end
+
+local function validateArgs(func, uargs)
+ -- Validates that arguments picked out by parseInput actually match
+ -- up to the arguments expected by the function being called.
+ local method = methods[func]
+ if not method then
+ print(jsonc.stringify({error = "Method not found in methods table"}))
+ os.exit(1)
+ end
+
+ -- Lua has no length operator for tables, so iterate to get the count
+ -- of the keys.
+ local n = 0
+ for _, _ in pairs(uargs) do n = n + 1 end
+
+ -- If the method defines an args table (so empty tables are not allowed),
+ -- and there were no args, then give a useful error message about that.
+ if method.args and n == 0 then
+ print(jsonc.stringify({
+ error = "Received empty arguments for " .. func ..
+ " but it requires " .. jsonc.stringify(method.args)
+ }))
+ os.exit(1)
+ end
+
+ uargs.ubus_rpc_session = nil
+
+ local margs = method.args or {}
+ for k, v in pairs(uargs) do
+ if margs[k] == nil or (v ~= nil and type(v) ~= type(margs[k])) then
+ print(jsonc.stringify({
+ error = "Invalid argument '" .. k .. "' for " .. func ..
+ " it requires " .. jsonc.stringify(method.args)
+ }))
+ os.exit(1)
+ end
+ end
+
+ return method
+end
+
+if arg[1] == "list" then
+ -- When rpcd starts up, it executes all scripts in /usr/libexec/rpcd
+ -- passing 'list' as the first argument. This block of code examines
+ -- all of the entries in the methods table, and looks for an attribute
+ -- called 'args' to see if there are arguments for the method.
+ --
+ -- The end result is a JSON struct like
+ -- {
+ -- "api_name": {},
+ -- "api2_name": {"host": "some_string"}
+ -- }
+ --
+ -- Which will be converted by ubus to
+ -- "api_name":{}
+ -- "api2_name":{"host":"String"}
+ local _, rv = nil, {}
+ for _, method in pairs(methods) do rv[_] = method.args or {} end
+ print((jsonc.stringify(rv):gsub(":%[%]", ":{}")))
+elseif arg[1] == "call" then
+ -- rpcd will execute the Lua script with a first argument of 'call',
+ -- a second argument of the method name, and a third argument that's
+ -- stringified JSON.
+ --
+ -- To debug your script, it's probably easiest to start with direct
+ -- execution, as calling via ubus will hide execution errors. For example:
+ -- echo '{}' | lua /usr/libexec/rpcd/luci.example call get_sample2
+ --
+ -- or
+ --
+ -- echo '{"section": "firstf"}' | /usr/libexec/rpcd/luci.example call get_uci_value
+ --
+ -- See https://openwrt.org/docs/techref/ubus for more details on using
+ -- ubus to call your RPC script (which is what LuCI will be doing).
+ local args = parseInput()
+ local method = validateArgs(arg[2], args)
+ local run = method.call(args)
+ -- Use the result from the table which we know to be JSON already.
+ -- Anything printed on stdout is sent via rpcd to the caller. Use
+ -- the syslog functions, or logging to a file, if you need debug
+ -- logs.
+ print(run.result)
+ -- And exit with the code supplied.
+ os.exit(run.code or 0)
+elseif arg[1] == "help" then
+ local helptext = [[
+Usage:
+
+ To see what methods are exported by this script:
+
+ lua luci.example list
+
+ To call a method that has no arguments:
+
+ echo '{}' | lua luci.example call method_name
+
+ To call a method that takes arguments:
+
+ echo '{"valid": "json", "argument": "value"}' | lua luci.example call method_name
+
+ To call this script via ubus:
+
+ ubus call luci.example method_name '{"valid": "json", "argument": "value"}'
+]]
+ print(helptext)
+end
diff --git a/applications/luci-app-example/root/usr/share/luci/menu.d/luci-app-example.json b/applications/luci-app-example/root/usr/share/luci/menu.d/luci-app-example.json
index 7b4772328a..4c82dd988a 100644
--- a/applications/luci-app-example/root/usr/share/luci/menu.d/luci-app-example.json
+++ b/applications/luci-app-example/root/usr/share/luci/menu.d/luci-app-example.json
@@ -26,5 +26,32 @@
"type": "view",
"path": "example/htmlview"
}
+ },
+
+ "admin/example/rpc-array": {
+ "title": "RPC Array Example",
+ "order": 3,
+ "action": {
+ "type": "view",
+ "path": "example/rpc"
+ }
+ },
+
+ "admin/example/rpc-jsonmap-table": {
+ "title": "RPC JSONMap Table Example",
+ "order": 4,
+ "action": {
+ "type": "view",
+ "path": "example/rpc-jsonmap-tablesection"
+ }
+ },
+
+ "admin/example/rpc-jsonmap-typed": {
+ "title": "RPC JSONMap Typed Example",
+ "order": 5,
+ "action": {
+ "type": "view",
+ "path": "example/rpc-jsonmap-typedsection"
+ }
}
}
diff --git a/applications/luci-app-example/root/usr/share/rpcd/acl.d/luci-app-example.json b/applications/luci-app-example/root/usr/share/rpcd/acl.d/luci-app-example.json
index 136f9aed55..b6849ffe6c 100644
--- a/applications/luci-app-example/root/usr/share/rpcd/acl.d/luci-app-example.json
+++ b/applications/luci-app-example/root/usr/share/rpcd/acl.d/luci-app-example.json
@@ -1,10 +1,11 @@
{
"luci-app-example": {
- "description": "Grant UCI access to LuCI app example",
+ "description": "Grant UCI and RPC access to LuCI app example",
"read": {
"ubus": {
- "uci": [
- "get"
+ "luci.example": [
+ "get_sample1",
+ "get_sample2"
]
},
"uci": [
@@ -12,11 +13,6 @@
]
},
"write": {
- "ubus": {
- "uci": [
- "set", "commit"
- ]
- },
"uci": [
"example"
]