luci-base: dispatcher.lua: support declarative node dependencies
authorJo-Philipp Wich <jo@mein.io>
Thu, 31 Oct 2019 17:25:23 +0000 (18:25 +0100)
committerJo-Philipp Wich <jo@mein.io>
Fri, 1 Nov 2019 11:03:33 +0000 (12:03 +0100)
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>
modules/luci-base/luasrc/dispatcher.lua

index f571144566fffb9e689cb53a6a4dd7398a0c3997..b43b94fdef9b6a56200ab36ffda919db89a39c0b 100644 (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