mirror of
https://github.com/openwrt/luci.git
synced 2026-06-17 14:50:21 +04:00
luci-app-example: rewrite RPC side using ucode
Signed-off-by: George Sapkin <george@sapk.in>
This commit is contained in:
committed by
Paul Donald
parent
0f743ad0f2
commit
f32d9204a7
+1
-2
@@ -9,8 +9,7 @@ listed by the shell command
|
||||
|
||||
$ ubus list
|
||||
|
||||
Custom scripts can be placed in /usr/libexec/rpcd, and must emit JSON. The name of the file
|
||||
in that directory will be the value for the object key in the declared map.
|
||||
Custom ucode scripts can be placed in /usr/share/rpcd/ucode, and must emit JSON.
|
||||
|
||||
Permissions to make these calls must be granted in /usr/share/rpcd/acl.d
|
||||
via a file named the same as the application package name (luci-app-example)
|
||||
|
||||
+1
-2
@@ -9,8 +9,7 @@ listed by the shell command
|
||||
|
||||
$ ubus list
|
||||
|
||||
Custom scripts can be placed in /usr/libexec/rpcd, and must emit JSON. The name of the file
|
||||
in that directory will be the value for the object key in the declared map.
|
||||
Custom ucode scripts can be placed in /usr/share/rpcd/ucode, and must emit JSON.
|
||||
|
||||
Permissions to make these calls must be granted in /usr/share/rpcd/acl.d
|
||||
via a file named the same as the application package name (luci-app-example)
|
||||
|
||||
@@ -9,8 +9,7 @@ listed by the shell command
|
||||
|
||||
$ ubus list
|
||||
|
||||
Custom scripts can be placed in /usr/libexec/rpcd, and must emit JSON. The name of the file
|
||||
in that directory will be the value for the object key in the declared map.
|
||||
Custom ucode scripts can be placed in /usr/share/rpcd/ucode, and must emit JSON.
|
||||
|
||||
Permissions to make these calls must be granted in /usr/share/rpcd/acl.d
|
||||
via a file named the same as the application package name (luci-app-example)
|
||||
@@ -104,9 +103,7 @@ return view.extend({
|
||||
// return is used to modify the DOM that the browser shows.
|
||||
render: function (data) {
|
||||
// data[0] will be the result from load_sample1
|
||||
var sample1 = data[0] || {};
|
||||
// data[1] will be the result from load_sample_yaml
|
||||
var sample_yaml = data[1] || {};
|
||||
const sample1 = data[0] || {};
|
||||
|
||||
// Render the tables as individual sections.
|
||||
return E('div', {}, [
|
||||
|
||||
@@ -4,6 +4,10 @@ touch /etc/config/example
|
||||
uci set example.first=first
|
||||
uci set example.second=second
|
||||
uci set example.third=third
|
||||
uci commit
|
||||
uci set example.animals=animals
|
||||
uci set example.animals.num_cats=1
|
||||
uci set example.animals.num_dogs=2
|
||||
uci set example.animals.num_parakeets=4
|
||||
uci commit example
|
||||
|
||||
return 0
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env ucode
|
||||
|
||||
'use strict';
|
||||
|
||||
import { cursor } from 'uci';
|
||||
|
||||
// Rather than parse files in /etc/config, we can use `cursor`.
|
||||
const uci = cursor();
|
||||
|
||||
const methods = {
|
||||
get_sample1: {
|
||||
call: function() {
|
||||
const num_cats = uci.get('example', 'animals', 'num_cats');
|
||||
const num_dogs = uci.get('example', 'animals', 'num_dogs');
|
||||
const num_parakeets = uci.get('example', 'animals', 'num_parakeets');
|
||||
const result = {
|
||||
num_cats,
|
||||
num_dogs,
|
||||
num_parakeets,
|
||||
is_this_real: false,
|
||||
not_found: null,
|
||||
};
|
||||
|
||||
uci.unload();
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
get_sample2: {
|
||||
call: function() {
|
||||
const result = {
|
||||
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],
|
||||
},
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.example': methods };
|
||||
@@ -9,6 +9,8 @@
|
||||
│ └── example
|
||||
│ ├── form.js
|
||||
│ ├── htmlview.js
|
||||
│ ├── rpc-jsonmap-tablesection.js
|
||||
│ ├── rpc-jsonmap-typedsection.js
|
||||
│ └── rpc.js
|
||||
├── Makefile
|
||||
├── po
|
||||
@@ -21,16 +23,15 @@
|
||||
│ └── uci-defaults
|
||||
│ └── 80_example
|
||||
└── usr
|
||||
├── libexec
|
||||
│ └── rpcd
|
||||
│ └── luci.example
|
||||
└── share
|
||||
├── luci
|
||||
│ └── menu.d
|
||||
│ └── luci-app-example.json
|
||||
└── rpcd
|
||||
└── acl.d
|
||||
└── luci-app-example.json
|
||||
├── acl.d
|
||||
│ └── luci-app-example.json
|
||||
└── ucode
|
||||
└── example.uc
|
||||
|
||||
```
|
||||
|
||||
@@ -68,9 +69,7 @@ LuCI apps do not have to have any additional files such as Lua scripts or UCI de
|
||||
|
||||
### Installing additional files
|
||||
|
||||
Any additional files needed by this application should be placed in `root/` using the directory tree that applies. This example application needs a RPCd script to be installed, so it places a file in `root/usr/libexec/rpcd/` and calls it `luci.example`. Scripts must have their execution bit set, and committed to the git repository with the bit set.
|
||||
|
||||
This example application also installs a file in `/etc/` by putting it in `root/etc/luci.example.yaml`.
|
||||
Any additional files needed by this application should be placed in `root/` using the directory tree that applies. This example application needs a ucode RPCd script to be installed, so it places a file in `root/usr/share/rpcd/ucode` and called `example.uc`.
|
||||
|
||||
The OpenWrt packaging system will install these files automatically.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user