Add davemhammer/opnsense (#315)
OPNsense health, interfaces, gateways, rules, logs, and service control.
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
--!nonstrict
|
||||
-- /opn launcher for OPNsense.
|
||||
|
||||
local STATE_KEY = "opn_snapshot"
|
||||
local COMMAND_KEY = "opn_command"
|
||||
local PANEL_ID = "davemhammer/opnsense:manager"
|
||||
local MAX_ROWS = 40
|
||||
|
||||
local snapshot = noctalia.state.get(STATE_KEY) or {
|
||||
available = false,
|
||||
configured = false,
|
||||
loading = true,
|
||||
widgets = {},
|
||||
interfaces = {},
|
||||
gateways = {},
|
||||
services = {},
|
||||
issueCount = 0,
|
||||
host = "",
|
||||
error = "",
|
||||
}
|
||||
|
||||
noctalia.state.watch(STATE_KEY, function(value)
|
||||
if type(value) == "table" then
|
||||
snapshot = value
|
||||
end
|
||||
end)
|
||||
|
||||
local function trim(s)
|
||||
return noctalia.string.trim(tostring(s or ""))
|
||||
end
|
||||
|
||||
local function lower(s)
|
||||
return string.lower(tostring(s or ""))
|
||||
end
|
||||
|
||||
local function send(action, values)
|
||||
local command = { action = action, requestId = "launcher-" .. tostring(os.time()) }
|
||||
if type(values) == "table" then
|
||||
for k, v in pairs(values) do command[k] = v end
|
||||
end
|
||||
noctalia.state.set(COMMAND_KEY, command)
|
||||
end
|
||||
|
||||
local function scoreText(filter, ...)
|
||||
if filter == "" then return 1 end
|
||||
local best = nil
|
||||
for i = 1, select("#", ...) do
|
||||
local text = tostring(select(i, ...) or "")
|
||||
if text ~= "" then
|
||||
local s = noctalia.fuzzyScore(filter, text)
|
||||
if s ~= nil and (best == nil or s > best) then best = s end
|
||||
if best == nil and lower(text):find(lower(filter), 1, true) then best = 0.5 end
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
|
||||
local function statusRow(title, subtitle, glyph)
|
||||
return { id = "", title = title, subtitle = subtitle, glyph = glyph or "shield" }
|
||||
end
|
||||
|
||||
local function topCategories()
|
||||
return {
|
||||
{ id = "cat:status", title = noctalia.tr("launcher.cat.status"), subtitle = noctalia.tr("launcher.cat.status-sub"), glyph = "heart-rate-monitor", score = 100 },
|
||||
{ id = "cat:interfaces", title = noctalia.tr("launcher.cat.interfaces"), subtitle = noctalia.tr("launcher.cat.interfaces-sub"), glyph = "network", score = 90 },
|
||||
{ id = "cat:gateways", title = noctalia.tr("launcher.cat.gateways"), subtitle = noctalia.tr("launcher.cat.gateways-sub"), glyph = "router", score = 85 },
|
||||
{ id = "cat:services", title = noctalia.tr("launcher.cat.services"), subtitle = noctalia.tr("launcher.cat.services-sub"), glyph = "settings", score = 80 },
|
||||
{ id = "cat:rules", title = noctalia.tr("launcher.cat.rules"), subtitle = noctalia.tr("launcher.cat.rules-sub"), glyph = "list-check", score = 75 },
|
||||
{ id = "cat:logs", title = noctalia.tr("launcher.cat.logs"), subtitle = noctalia.tr("launcher.cat.logs-sub"), glyph = "file-text", score = 72 },
|
||||
{ id = "act:panel", title = noctalia.tr("launcher.cat.panel"), subtitle = noctalia.tr("launcher.cat.panel-sub"), glyph = "layout-dashboard", score = 70 },
|
||||
{ id = "act:ui", title = noctalia.tr("launcher.cat.ui"), subtitle = noctalia.tr("launcher.cat.ui-sub"), glyph = "external-link", score = 60 },
|
||||
{ id = "act:refresh", title = noctalia.tr("launcher.cat.refresh"), subtitle = noctalia.tr("launcher.cat.refresh-sub"), glyph = "refresh", score = 50 },
|
||||
}
|
||||
end
|
||||
|
||||
local function listStatus(filter)
|
||||
local rows = {}
|
||||
for _, w in ipairs(snapshot.widgets or {}) do
|
||||
local s = scoreText(filter, w.name, w.status, w.message)
|
||||
if s ~= nil then
|
||||
table.insert(rows, {
|
||||
id = "st:" .. w.id,
|
||||
title = w.name,
|
||||
subtitle = w.status .. (w.message ~= "" and (" · " .. w.message) or ""),
|
||||
glyph = w.ok and "circle-check" or "circle-x",
|
||||
score = s,
|
||||
})
|
||||
end
|
||||
end
|
||||
if #rows == 0 then rows[1] = statusRow(noctalia.tr("launcher.no-matches"), filter, "search") end
|
||||
return rows
|
||||
end
|
||||
|
||||
local function listIfaces(filter)
|
||||
local rows = {}
|
||||
for _, i in ipairs(snapshot.interfaces or {}) do
|
||||
local s = scoreText(filter, i.name, i.description, i.status, i.ipv4)
|
||||
if s ~= nil then
|
||||
table.insert(rows, {
|
||||
id = "if:" .. i.id,
|
||||
title = i.name,
|
||||
subtitle = `{i.status} · {i.ipv4}`,
|
||||
glyph = "network",
|
||||
score = s,
|
||||
})
|
||||
end
|
||||
end
|
||||
if #rows == 0 then rows[1] = statusRow(noctalia.tr("launcher.no-matches"), filter, "search") end
|
||||
return rows
|
||||
end
|
||||
|
||||
local function listGateways(filter)
|
||||
local rows = {}
|
||||
for _, g in ipairs(snapshot.gateways or {}) do
|
||||
local s = scoreText(filter, g.name, g.status, g.address)
|
||||
if s ~= nil then
|
||||
table.insert(rows, {
|
||||
id = "gw:" .. g.id,
|
||||
title = g.name,
|
||||
subtitle = `{g.status} · {g.address}`,
|
||||
glyph = "router",
|
||||
score = s,
|
||||
})
|
||||
end
|
||||
end
|
||||
if #rows == 0 then rows[1] = statusRow(noctalia.tr("launcher.no-matches"), filter, "search") end
|
||||
return rows
|
||||
end
|
||||
|
||||
local function serviceActions(name)
|
||||
return {
|
||||
{ id = "svcact:restart:" .. name, title = noctalia.tr("launcher.action.restart"), subtitle = name, glyph = "refresh", score = 100 },
|
||||
{ id = "svcact:start:" .. name, title = noctalia.tr("launcher.action.start"), subtitle = name, glyph = "player-play", score = 90 },
|
||||
{ id = "svcact:stop:" .. name, title = noctalia.tr("launcher.action.stop"), subtitle = name, glyph = "player-stop", score = 80 },
|
||||
{ id = "svcact:copy:" .. name, title = noctalia.tr("launcher.action.copy"), subtitle = name, glyph = "copy", score = 70 },
|
||||
}
|
||||
end
|
||||
|
||||
local function listServices(filter)
|
||||
local rows = {}
|
||||
for _, s in ipairs(snapshot.services or {}) do
|
||||
local sc = scoreText(filter, s.name, s.status, s.description)
|
||||
if sc ~= nil then
|
||||
table.insert(rows, {
|
||||
id = "svc:" .. s.id,
|
||||
title = s.name,
|
||||
subtitle = s.status .. (s.description ~= "" and (" · " .. s.description) or ""),
|
||||
glyph = s.running and "player-play" or "player-stop",
|
||||
score = sc,
|
||||
})
|
||||
end
|
||||
end
|
||||
table.sort(rows, function(a, b) return (a.score or 0) > (b.score or 0) end)
|
||||
while #rows > MAX_ROWS do table.remove(rows) end
|
||||
if #rows == 0 then rows[1] = statusRow(noctalia.tr("launcher.no-matches"), filter, "search") end
|
||||
return rows
|
||||
end
|
||||
|
||||
local function findService(name)
|
||||
name = trim(name)
|
||||
for _, s in ipairs(snapshot.services or {}) do
|
||||
if s.name == name or s.id == name then return s end
|
||||
end
|
||||
local hits = {}
|
||||
local q = lower(name)
|
||||
for _, s in ipairs(snapshot.services or {}) do
|
||||
if lower(s.name):find(q, 1, true) then table.insert(hits, s) end
|
||||
end
|
||||
if #hits == 1 then return hits[1] end
|
||||
return nil
|
||||
end
|
||||
|
||||
function onQuery(query)
|
||||
if not snapshot.configured then
|
||||
launcher.setResults(query, {
|
||||
statusRow(noctalia.tr("panel.not_configured"), "", "settings"),
|
||||
{ id = "act:panel", title = noctalia.tr("launcher.cat.panel"), subtitle = "Configure in plugin settings", glyph = "layout-dashboard" },
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if snapshot.loading and not snapshot.available then
|
||||
send("refresh")
|
||||
launcher.setResults(query, { statusRow(noctalia.tr("launcher.loading"), snapshot.host, "loader") })
|
||||
return
|
||||
end
|
||||
|
||||
if not snapshot.available then
|
||||
launcher.setResults(query, {
|
||||
statusRow(noctalia.tr("launcher.unavailable"), snapshot.error or "", "cloud-off"),
|
||||
{ id = "act:refresh", title = noctalia.tr("launcher.cat.refresh"), subtitle = "", glyph = "refresh" },
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local text = trim(query)
|
||||
if text == "" then
|
||||
launcher.setResults(query, topCategories())
|
||||
return
|
||||
end
|
||||
|
||||
local tokens = {}
|
||||
for t in text:gmatch("%S+") do table.insert(tokens, t) end
|
||||
local head = lower(tokens[1] or "")
|
||||
local rest = table.concat(tokens, " ", 2)
|
||||
|
||||
local function is(name, aliases)
|
||||
if head == name then return true end
|
||||
for _, a in ipairs(aliases) do if head == a then return true end end
|
||||
return false
|
||||
end
|
||||
|
||||
if is("status", { "st", "health" }) then
|
||||
launcher.setResults(query, listStatus(rest))
|
||||
return
|
||||
end
|
||||
if is("interfaces", { "iface", "if", "int" }) then
|
||||
launcher.setResults(query, listIfaces(rest))
|
||||
return
|
||||
end
|
||||
if is("gateways", { "gw", "gateway" }) then
|
||||
launcher.setResults(query, listGateways(rest))
|
||||
return
|
||||
end
|
||||
if is("services", { "svc", "service", "s" }) then
|
||||
local svc = findService(rest)
|
||||
if svc and (lower(rest) == lower(svc.name) or rest:find(svc.name, 1, true)) and rest ~= "" then
|
||||
if lower(rest) == lower(svc.name) then
|
||||
launcher.setResults(query, serviceActions(svc.name))
|
||||
return
|
||||
end
|
||||
end
|
||||
launcher.setResults(query, listServices(rest))
|
||||
return
|
||||
end
|
||||
|
||||
local rows = {}
|
||||
for _, row in ipairs(topCategories()) do
|
||||
local s = scoreText(text, row.title, row.id)
|
||||
if s ~= nil then
|
||||
row.score = s
|
||||
table.insert(rows, row)
|
||||
end
|
||||
end
|
||||
for _, r in ipairs(listServices(text)) do
|
||||
if r.id ~= "" then table.insert(rows, r) end
|
||||
end
|
||||
if #rows == 0 then rows[1] = statusRow(noctalia.tr("launcher.no-matches"), text, "search") end
|
||||
launcher.setResults(query, rows)
|
||||
end
|
||||
|
||||
function onActivate(id)
|
||||
if id == nil or id == "" then return end
|
||||
|
||||
if id == "cat:status" then launcher.setQuery("status "); return end
|
||||
if id == "cat:interfaces" then launcher.setQuery("interfaces "); return end
|
||||
if id == "cat:gateways" then launcher.setQuery("gateways "); return end
|
||||
if id == "cat:services" then launcher.setQuery("services "); return end
|
||||
if id == "cat:rules" then noctalia.togglePanel(PANEL_ID); return end
|
||||
if id == "cat:logs" then noctalia.togglePanel(PANEL_ID); return end
|
||||
|
||||
if id == "act:panel" then noctalia.togglePanel(PANEL_ID); return end
|
||||
if id == "act:ui" then send("open_ui"); return end
|
||||
if id == "act:refresh" then
|
||||
send("refresh")
|
||||
noctalia.notify(noctalia.tr("title"), noctalia.tr("widget.refresh_requested"))
|
||||
return
|
||||
end
|
||||
|
||||
local svc = id:match("^svc:(.+)$")
|
||||
if svc then
|
||||
launcher.setQuery("services " .. svc .. " ")
|
||||
return
|
||||
end
|
||||
|
||||
local act, name = id:match("^svcact:([%w]+):(.+)$")
|
||||
if act and name then
|
||||
if act == "restart" then send("restart_service", { name = name })
|
||||
elseif act == "start" then send("start_service", { name = name })
|
||||
elseif act == "stop" then send("stop_service", { name = name })
|
||||
elseif act == "copy" then send("copy", { name = name })
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local st = id:match("^st:(.+)$")
|
||||
if st then send("copy", { name = st }); return end
|
||||
local iface = id:match("^if:(.+)$")
|
||||
if iface then send("copy", { name = iface }); return end
|
||||
local gw = id:match("^gw:(.+)$")
|
||||
if gw then send("copy", { name = gw }); return end
|
||||
end
|
||||
Reference in New Issue
Block a user