luci-base: dispatcher.lua: support declarative node dependencies

Introduce two new properties for page nodes to allow for declaratively
specifiying system dependencies which is useful to e.g. make certain
views depend on specific uci values or the presence of certain files.

The recognized properties are:

 - `uci_depends` - a nested table in one of the following forms:

     1) `{ config = { section = { option = "exact_value" } }`
     2) `{ config = { section = { option = true } }`
     3) `{ config = { section = "exact_type" } }`
     4) `{ config = { section = true } }`
     5) `{ config = true }`

   Depending on the declaration, the uci option or section type must either
   match the given "exact_value" or "exact_type" values or be a non-nil value
   in case boolean "true" is specified.

 - `file_depends` - a flat lists of file paths that must be accessible

   If a path listed in `file_depends` points to a directory, that directory
   must be not empty, otherwise it suffices if the path exists.

Examples:

 - Only display the node if an /etc/config/wireless file exists with
   a "config wifi-device radio0" section.

    node = page(...)
    node.uci_depends = { wireless = { radio0 = "wifi-device" } }

 - Only display the node when swconfig is installed.

    node = page(...)
    node.file_depends = { "/sbin/swconfig" }

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2019-10-31 18:25:23 +01:00
parent c72c78bed9
commit 790005cdfa

View File

@ -62,9 +62,73 @@ function _ordered_children(node)
return children
end
local function dependencies_satisfied(node)
if type(node.file_depends) == "table" then
for _, file in ipairs(node.file_depends) do
local ftype = fs.stat(file, "type")
if ftype == "dir" then
local empty = true
for e in (fs.dir(file) or function() end) do
empty = false
end
if empty then
return false
end
elseif ftype == nil then
return false
end
end
end
if type(node.uci_depends) == "table" then
for config, expect_sections in pairs(node.uci_depends) do
if type(expect_sections) == "table" then
for section, expect_options in pairs(expect_sections) do
if type(expect_options) == "table" then
for option, expect_value in pairs(expect_options) do
local val = uci:get(config, section, option)
if expect_value == true and val == nil then
return false
elseif type(expect_value) == "string" then
if type(val) == "table" then
local found = false
for _, subval in ipairs(val) do
if subval == expect_value then
found = true
end
end
if not found then
return false
end
elseif val ~= expect_value then
return false
end
end
end
else
local val = uci:get(config, section)
if expect_options == true and val == nil then
return false
elseif type(expect_options) == "string" and val ~= expect_options then
return false
end
end
end
elseif expect_sections == true then
if not uci:get_first(config) then
return false
end
end
end
end
return true
end
function node_visible(node)
if node then
return not (
(not dependencies_satisfied(node)) or
(not node.title or #node.title == 0) or
(not node.target or node.hidden == true) or
(type(node.target) == "table" and node.target.type == "firstchild" and