feat(web_viewer): cover every plugin setting, with a template reference and preview

The Plugins page now has a field for every setting the plugins read. I compared each plugin's settings_schema against config.ini.example and the keys the code reads; fourteen plugins had gaps (greeter, trace, stats, path, wx/gwx, rain, test, multitest, sports, worldcup, cmd, feed, the webhook, weather, packet capture, earthquake and prefix-collision services) and the World Cup service had no form at all. Webhook URLs, bot tokens and channel keys use the password field. A guard test needed four [Rain_Command] keys documented in config.ini.example.

Template fields get a { } button that opens a reference of the placeholders they take, with click-to-insert. Fields rendered by the piped engine (test's response_format, path's reply_prefix) also list the filters and conditionals. Every template field previews as you type: piped fields against a 2-byte, a 1-byte and a direct sample message, so a conditional's effect shows before saving, and the str.format fields (greeter, mesh info, multitest, MQTT weather) as their plugin renders them, including what it sends when the template has a mistake. The specs and renderers live in modules/template_reference.py; the preview endpoint only renders and never saves. The greeter's channel_greetings parser and multi-part split moved into module functions so the command and the preview share them.

Fixes the new fields would otherwise have exposed:
- Clearing a number field saved `key =`, which makes getint/getfloat raise and the plugin fail to load. A cleared number now removes the key.
- The weather service raised in __init__ when only some blitz_area_* corners were set. It now needs all four and logs a warning otherwise.
- A blank MQTT weather json_template saved an empty template, so every reply said no message had arrived. Blank now saves the default.
This commit is contained in:
agessaman committed 2026-10-04 15:41:45 -07:00
1 parent 58c2f8a4b8
commit 7758403148
30 files changed
+1493 -65

No files matched your search

+29
View File
@@ -230,6 +230,35 @@ settings_schema = [
**Note:** `enabled` and `channels` do not need to be defined in the
`settings_schema` as they get automatically included.
#### Template fields
A `str` field that holds a reply template can add `"template"`, built with `template_spec` from `modules.template_reference`. The Plugins page then puts a `{ }` button beside the field that opens a list of its placeholders (click one to insert it at the cursor). Name the syntax the plugin renders the value with:
- `"piped"`: the plugin renders it with `format_piped_template` from `modules.response_template`. The panel also lists the filters and conditionals and previews the template against sample messages (2-byte, 1-byte and direct).
- `"format"`: the plugin uses Python's `str.format`. The panel lists placeholders only. To preview it, add a renderer to `FORMAT_PREVIEWS` in `modules/template_reference.py` that formats sample values the way the plugin does, including what the plugin sends when the template fails, and name it with `preview=`.
```python
from modules.template_reference import MESSAGE_PLACEHOLDERS, template_spec
settings_schema = [
{
"key": "response_format",
"label": "Response format",
"type": "str",
"default": "",
"help": "Template for the reply. Empty uses the default.",
"template": template_spec(
"piped",
MESSAGE_PLACEHOLDERS + ("path_distance",),
notes=["\\n starts a new line."],
blank_default="ack @[{sender}] | {path}",
),
},
]
```
`MESSAGE_PLACEHOLDERS` are the fields every command gets from `get_standard_placeholder_fields`. For a plugin with its own fields, pass a `{name: description}` mapping instead. Set `escapes=False` if the plugin does not decode `\n`, and `blank_default` to the template it falls back to when the field is blank, so the preview shows what the plugin actually sends.
---
## Core Methods