Merge pull request #2160 from remakeelectric/app-mosq
luci-app-mosquitto: import new application
This commit is contained in:
commit
1b6e3bc5ae
3 changed files with 258 additions and 0 deletions
13
applications/luci-app-mosquitto/Makefile
Normal file
13
applications/luci-app-mosquitto/Makefile
Normal file
|
@ -0,0 +1,13 @@
|
|||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=Mosquitto LuCI interface
|
||||
LUCI_DEPENDS:=+luci-mod-admin-full mosquitto
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_DESCRIPTION:=Provides a webadmin for most basic mosquitto parameters.
|
||||
|
||||
PKG_MAINTAINER:= Karl Palsson <karlp@etactica.com>
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
|
@ -0,0 +1,16 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface for mosquitto
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
]]--
|
||||
|
||||
module("luci.controller.mosquitto", package.seeall)
|
||||
|
||||
function index()
|
||||
entry({"admin", "services", "mosquitto"}, cbi("mosquitto"), _("Mosquitto"))
|
||||
end
|
229
applications/luci-app-mosquitto/luasrc/model/cbi/mosquitto.lua
Normal file
229
applications/luci-app-mosquitto/luasrc/model/cbi/mosquitto.lua
Normal file
|
@ -0,0 +1,229 @@
|
|||
--[[
|
||||
LuCI model for mosquitto MQTT broker configuration management
|
||||
Copyright eTactica ehf, 2018
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
]]--
|
||||
|
||||
local datatypes = require("luci.cbi.datatypes")
|
||||
|
||||
--- Like a Flag, but with an option to remove/set to default.
|
||||
local function OptionalFlag(section, key, title, description)
|
||||
local o = section:option(ListValue, key, title, description)
|
||||
o.optional = true
|
||||
o:value("", "Default")
|
||||
o:value("1", "Enabled")
|
||||
o:value("0", "Disabled")
|
||||
return o
|
||||
end
|
||||
|
||||
m = Map("mosquitto", "Mosquitto MQTT Broker",
|
||||
[[mosquitto - the <a href='http://www.mosquitto.org'>blood thirsty</a>
|
||||
MQTT messaging broker. Note, only some of the available configuration files
|
||||
are supported at this stage, use the checkbox below to use config generated
|
||||
by this page, or the stock mosquitto configuration file in
|
||||
/etc/mosquitto/mosquitto.conf]])
|
||||
|
||||
s = m:section(TypedSection, "owrt", "OpenWRT")
|
||||
s.anonymous = true
|
||||
p = s:option(Flag, "use_uci", "Use this LuCI configuration page",
|
||||
[[If checked, mosquitto runs with a config generated
|
||||
from this page. (Or from UCI directly) If unchecked, mosquitto
|
||||
runs with the config in /etc/mosquitto/mosquitto.conf
|
||||
(and this page is ignored)]])
|
||||
|
||||
s = m:section(TypedSection, "mosquitto", "Mosquitto")
|
||||
s.anonymous = true
|
||||
|
||||
p = s:option(MultiValue, "log_dest", "Log destination",
|
||||
"You can have multiple, but 'none' will override all others")
|
||||
p:value("stderr", "stderr")
|
||||
p:value("stdout", "stdout")
|
||||
p:value("syslog", "syslog")
|
||||
p:value("topic", "$SYS/broker/log/[severity]")
|
||||
p:value("none", "none")
|
||||
|
||||
OptionalFlag(s, "no_remote_access", "Disallow remote access to this broker",
|
||||
[[Outbound bridges will still work, but this will make the primary listener
|
||||
only available from localhost]])
|
||||
|
||||
local o
|
||||
o = s:option(Value, "sys_interval", "Time in seconds between updates of the $SYS tree", "Set to zero to disable")
|
||||
o.datatype = "uinteger"
|
||||
o.optional = true
|
||||
|
||||
o = s:option(Value, "max_inflight_messages", "Max Inflight Messages", "Limit for message allowed inflight")
|
||||
o.datatype = "uinteger"
|
||||
o.optional = true
|
||||
o = s:option(Value, "max_queued_messages", "Max Queued Messages", "Limit for message queue when offline")
|
||||
o.datatype = "uinteger"
|
||||
o.optional = true
|
||||
o = s:option(Value, "max_queued_bytes", "Max Queued bytes", "Limit for message queue when offline, zero to disable)")
|
||||
o.datatype = "uinteger"
|
||||
o.optional = true
|
||||
|
||||
|
||||
s = m:section(TypedSection, "persistence", "Persistence")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
s:option(Flag, "persistence", "Persistence enabled", "Should persistence to disk be enabled at all").rmempty = false
|
||||
o = s:option(Value, "client_expiration", "Client expiration", "Remove persistent clients if they haven't reconnected in this period, eg 6h, 3d, 2w")
|
||||
o.optional = true
|
||||
o:depends("persistence", true)
|
||||
o = OptionalFlag(s, "autosave_on_changes", "Autosave on changes", "Autosave interval applies to change counts instead of time")
|
||||
o:depends("persistence", true)
|
||||
o = s:option(Value, "autosave_interval", "Autosave interval", "Save persistence file after this many seconds or changes")
|
||||
o.optional = true
|
||||
o:depends("persistence", true)
|
||||
o = s:option(Value, "file", "Persistent file name")
|
||||
o.optional = true
|
||||
o:depends("persistence", true)
|
||||
o = s:option(Value, "location", "Persistent file path (with trailing/)", "Path to persistent file")
|
||||
o.optional = true
|
||||
o:depends("persistence", true)
|
||||
|
||||
s = m:section(TypedSection, "listener", "Listeners", "You can configure additional listeners here")
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
s:option(Value, "port", "Port").datatype = "port"
|
||||
|
||||
o = s:option(ListValue, "protocol", "Protocol to use when listening")
|
||||
o:value("", "Default")
|
||||
o:value("mqtt", "MQTT")
|
||||
o:value("websockets", "WebSockets")
|
||||
|
||||
s:option(Value, "http_dir", "http_dir to serve on websockets listeners").optional = true
|
||||
OptionalFlag(s, "use_username_as_clientid", "use_username_as_clientid")
|
||||
o = s:option(Value, "cafile", "CA file path")
|
||||
o.optional = true
|
||||
o.datatype = "file"
|
||||
o = s:option(Value, "capath", "CA path to search")
|
||||
o.optional = true
|
||||
o.datatype = "file"
|
||||
o = s:option(Value, "certfile", "server certificate file (PEM encoded)")
|
||||
o.optional = true
|
||||
o.datatype = "file"
|
||||
o = s:option(Value, "keyfile", "keyfile (PEM encoded)")
|
||||
o.optional = true
|
||||
o.datatype = "file"
|
||||
|
||||
o = s:option(ListValue, "tls_version", "TLS Version",
|
||||
"Depends on your openssl version, empty to support all")
|
||||
o.optional = true
|
||||
o:value("", "Default")
|
||||
o:value("tlsv1")
|
||||
o:value("tlsv1.1")
|
||||
o:value("tlsv1.2")
|
||||
|
||||
OptionalFlag(s, "require_certificate", "Require clients to present a certificate")
|
||||
OptionalFlag(s, "use_identity_as_username", "use_identity_as_username")
|
||||
s:option(Value, "crlfile", "CRL to use if require_certificate is enabled").optional = true
|
||||
s:option(Value, "ciphers", "Ciphers control. Should match 'openssl ciphers' format").optional = true
|
||||
s:option(Value, "psk_hint", "PSK Hint to provide to connecting clients").optional = true
|
||||
|
||||
-- we want to allow multiple bridge sections
|
||||
s = m:section(TypedSection, "bridge", "Bridges",
|
||||
"You can configure multiple bridge connections here")
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
|
||||
conn = s:option(Value, "connection", "Connection name",
|
||||
"unique name for this bridge configuration")
|
||||
|
||||
local function validate_address(self, value)
|
||||
local host, port = unpack(luci.util.split(value, ":"))
|
||||
if (datatypes.host(host)) then
|
||||
if port and #port then
|
||||
if not datatypes.port(port) then
|
||||
return nil, "Please enter a valid port after the :"
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
return nil, "Please enter a hostname or an IP address"
|
||||
end
|
||||
|
||||
addr = s:option(Value, "address", "address", "address[:port] of remote broker")
|
||||
addr.datatype = "string"
|
||||
addr.validate = validate_address
|
||||
|
||||
-- TODO - make the in/out/both a dropdown/radio or something....
|
||||
topics = s:option(DynamicList, "topic", "topic",
|
||||
"full topic string for mosquitto.conf, eg: 'power/# out 2'")
|
||||
|
||||
OptionalFlag(s, "cleansession", "Clean session")
|
||||
OptionalFlag(s, "notifications", "notifications",
|
||||
"Attempt to notify the local and remote broker of connection status, defaults to $SYS/broker/connections/<clientid>/state")
|
||||
s:option(Value, "notification_topic", "Topic to use for local+remote remote for notifications.").optional = true
|
||||
|
||||
s:option(Value, "remote_clientid", "Client id to use on remote end of this bridge connection").optional = true
|
||||
s:option(Value, "local_clientid", "Client id to use locally. Important when bridging to yourself").optional = true
|
||||
o = s:option(Value, "keepalive_interval", "Keepalive interval for this bridge")
|
||||
o.datatype = "uinteger"
|
||||
o.optional = true
|
||||
o = s:option(ListValue, "start_type", "How should this bridge be started")
|
||||
o.optional = true
|
||||
o:value("", "Default")
|
||||
o:value("automatic", "Automatic, includes restarts")
|
||||
o:value("lazy", "Automatic, but stopped when not used")
|
||||
o:value("once", "Automatic, but no restarts")
|
||||
o = s:option(Value, "restart_timeout", "How long to wait before reconnecting")
|
||||
o.datatype = "uinteger"
|
||||
o.optional = true
|
||||
o = s:option(Value, "idle_timeout", "How long to wait before disconnecting")
|
||||
o.datatype = "uinteger"
|
||||
o.optional = true
|
||||
o = s:option(Value, "threshold", "How many messages to queue before restarting lazy bridge")
|
||||
o.datatype = "uinteger"
|
||||
o.optional = true
|
||||
|
||||
OptionalFlag(s, "try_private", "try_private",
|
||||
"attempt to notify the remote broker that this is a bridge, not all brokers support this.")
|
||||
s:option(Value, "remote_username", "Remote username").optional = true
|
||||
o = s:option(Value, "remote_password", "Remote password")
|
||||
o.optional = true
|
||||
o.password = true
|
||||
|
||||
s:option(Value, "identity", "PSK Bridge Identity", "Identity for TLS-PSK").optional = true
|
||||
|
||||
-- no hex validation available in datatypes
|
||||
local function validate_psk_key(self, value)
|
||||
if (value:match("^[a-fA-F0-9]+$")) then
|
||||
return value
|
||||
end
|
||||
return nil, "Only hex numbers are allowed (use A-F characters and 0-9 digits)"
|
||||
end
|
||||
|
||||
psk_key = s:option(Value, "psk", "Bridge PSK", "Key for TLS-PSK")
|
||||
psk_key.password = true
|
||||
psk_key.optional = true
|
||||
psk_key.datatype = "string"
|
||||
psk_key.validate = validate_psk_key
|
||||
|
||||
b_tls_version = s:option(ListValue, "tls_version", "TLS Version",
|
||||
"The remote broker must support the same version of TLS for the connection to succeed.")
|
||||
b_tls_version:value("", "Default")
|
||||
b_tls_version:value("tlsv1")
|
||||
b_tls_version:value("tlsv1.1")
|
||||
b_tls_version:value("tlsv1.2")
|
||||
b_tls_version.optional = true
|
||||
|
||||
o = s:option(Value, "cafile", "Path to CA file")
|
||||
o.optional = true
|
||||
o.datatype = "file"
|
||||
o = s:option(Value, "capath", "Directory to search for CA files")
|
||||
o.optional = true
|
||||
o.datatype = "file"
|
||||
o = s:option(Value, "certfile", "Path to PEM encoded server certificate file")
|
||||
o.optional = true
|
||||
o.datatype = "file"
|
||||
o = s:option(Value, "keyfile", "Path to PEM encoded keyfile")
|
||||
o.optional = true
|
||||
o.datatype = "file"
|
||||
|
||||
return m
|
Loading…
Reference in a new issue