add web (launcher)

This commit is contained in:
avaksru 2026-03-23 10:37:13 +03:00
parent 64c6e38428
commit 14e80919a8
8 changed files with 1471 additions and 467 deletions

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,7 @@ var channelCatalog = []channelCatalogItem{
{Name: "maixcam", ConfigKey: "maixcam"}, {Name: "maixcam", ConfigKey: "maixcam"},
{Name: "matrix", ConfigKey: "matrix"}, {Name: "matrix", ConfigKey: "matrix"},
{Name: "irc", ConfigKey: "irc"}, {Name: "irc", ConfigKey: "irc"},
{Name: "mqtt", ConfigKey: "mqtt"},
} }
// registerChannelRoutes binds read-only channel catalog endpoints to the ServeMux. // registerChannelRoutes binds read-only channel catalog endpoints to the ServeMux.

View file

@ -189,6 +189,19 @@ func validateConfig(cfg *config.Config) []string {
errs = append(errs, "channels.discord.token is required when discord channel is enabled") errs = append(errs, "channels.discord.token is required when discord channel is enabled")
} }
// MQTT: broker and client_id required when enabled
if cfg.Channels.MQTT.Enabled {
if cfg.Channels.MQTT.Broker == "" {
errs = append(errs, "channels.mqtt.broker is required when mqtt channel is enabled")
}
if cfg.Channels.MQTT.ClientID == "" {
errs = append(errs, "channels.mqtt.client_id is required when mqtt channel is enabled")
}
if len(cfg.Channels.MQTT.SubscribeTopics) == 0 {
errs = append(errs, "channels.mqtt.subscribe_topics is required when mqtt channel is enabled")
}
}
if cfg.Tools.Exec.Enabled { if cfg.Tools.Exec.Enabled {
if cfg.Tools.Exec.EnableDenyPatterns { if cfg.Tools.Exec.EnableDenyPatterns {
errs = append( errs = append(

View file

@ -166,6 +166,8 @@ function isConfigured(
) )
case "irc": case "irc":
return asString(config.server) !== "" return asString(config.server) !== ""
case "mqtt":
return asString(config.broker) !== "" && asString(config.client_id) !== ""
default: default:
return false return false
} }
@ -205,6 +207,8 @@ function getRequiredFieldKeys(channelName: string): string[] {
return ["homeserver", "user_id", "access_token"] return ["homeserver", "user_id", "access_token"]
case "irc": case "irc":
return ["server"] return ["server"]
case "mqtt":
return ["broker", "client_id", "subscribe_topics"]
default: default:
return [] return []
} }

View file

@ -31,6 +31,9 @@ const SECRET_FIELDS = new Set([
"password", "password",
"nickserv_password", "nickserv_password",
"sasl_password", "sasl_password",
"tls_ca",
"tls_cert",
"tls_key",
]) ])
// Fields to skip in the generic form (handled by enabled toggle or internal). // Fields to skip in the generic form (handled by enabled toggle or internal).
@ -139,6 +142,15 @@ export function GenericForm({
channels: t("channels.form.desc.channels"), channels: t("channels.form.desc.channels"),
request_caps: t("channels.form.desc.requestCaps"), request_caps: t("channels.form.desc.requestCaps"),
max_base64_file_size_mib: t("channels.form.desc.maxBase64FileSizeMiB"), max_base64_file_size_mib: t("channels.form.desc.maxBase64FileSizeMiB"),
broker: t("channels.form.desc.broker"),
subscribe_topics: t("channels.form.desc.subscribeTopics"),
subscribe_json_key: t("channels.form.desc.subscribeJsonKey"),
reply_topic: t("channels.form.desc.replyTopic"),
reply_json_key: t("channels.form.desc.replyJsonKey"),
qos: t("channels.form.desc.qos"),
retain: t("channels.form.desc.retain"),
prefix: t("channels.form.desc.prefix"),
instruction: t("channels.form.desc.instruction"),
} }
return ( return (
descriptions[key] ?? descriptions[key] ??

View file

@ -44,6 +44,7 @@ const CHANNEL_IMPORTANCE_ORDER = [
"pico", "pico",
"maixcam", "maixcam",
"irc", "irc",
"mqtt",
"whatsapp", "whatsapp",
"whatsapp_native", "whatsapp_native",
] ]

View file

@ -240,7 +240,8 @@
"pico": "Web", "pico": "Web",
"maixcam": "MaixCam", "maixcam": "MaixCam",
"matrix": "Matrix", "matrix": "Matrix",
"irc": "IRC" "irc": "IRC",
"mqtt": "MQTT"
}, },
"field": { "field": {
"token": "Bot Token", "token": "Bot Token",
@ -328,6 +329,15 @@
"channels": "IRC channels to join.", "channels": "IRC channels to join.",
"requestCaps": "IRC capability list requested on connect.", "requestCaps": "IRC capability list requested on connect.",
"maxBase64FileSizeMiB": "Maximum size in MiB for converting local files to base64 before upload. 0 means unlimited. Applies only to local files, not URL uploads.", "maxBase64FileSizeMiB": "Maximum size in MiB for converting local files to base64 before upload. 0 means unlimited. Applies only to local files, not URL uploads.",
"broker": "MQTT broker URL (e.g. tcp://broker.emqx.io:1883).",
"subscribeTopics": "MQTT topics to subscribe to, separated by commas.",
"subscribeJsonKey": "JSON key to extract from incoming messages. Leave empty to use raw payload.",
"replyTopic": "Default topic for publishing replies.",
"replyJsonKey": "JSON key to wrap reply content. Leave empty to send plain text.",
"qos": "Quality of Service level (0, 1, or 2).",
"retain": "Whether to retain published messages on the broker.",
"prefix": "URL prefix for the channel.",
"instruction": "System instruction prepended to incoming messages.",
"genericField": "Used to configure {{field}}." "genericField": "Used to configure {{field}}."
} }
}, },

View file

@ -240,7 +240,8 @@
"pico": "Web", "pico": "Web",
"maixcam": "MaixCam", "maixcam": "MaixCam",
"matrix": "Matrix", "matrix": "Matrix",
"irc": "IRC" "irc": "IRC",
"mqtt": "MQTT"
}, },
"field": { "field": {
"token": "Bot Token", "token": "Bot Token",
@ -328,6 +329,15 @@
"channels": "要加入的 IRC 频道列表。", "channels": "要加入的 IRC 频道列表。",
"requestCaps": "连接时请求的 IRC 扩展能力列表。", "requestCaps": "连接时请求的 IRC 扩展能力列表。",
"maxBase64FileSizeMiB": "本地文件转为 base64 上传的最大体积,单位 MiB0 表示不限制,仅影响本地文件,不影响 URL 直传。", "maxBase64FileSizeMiB": "本地文件转为 base64 上传的最大体积,单位 MiB0 表示不限制,仅影响本地文件,不影响 URL 直传。",
"broker": "MQTT 代理地址(例如 tcp://broker.emqx.io:1883。",
"subscribeTopics": "要订阅的 MQTT 主题,多个值用逗号分隔。",
"subscribeJsonKey": "从接收消息中提取的 JSON 键名。留空则使用原始内容。",
"replyTopic": "发布回复的默认主题。",
"replyJsonKey": "用于包装回复内容的 JSON 键名。留空则发送纯文本。",
"qos": "服务质量等级0、1 或 2。",
"retain": "是否在代理上保留已发布的消息。",
"prefix": "频道 URL 前缀。",
"instruction": "附加到接收消息前的系统指令。",
"genericField": "用于配置{{field}}。" "genericField": "用于配置{{field}}。"
} }
}, },