v1.23 - AI-assisted report template generation
v1.23 - 2026-04-17
AI-assisted report template generation graduates from beta1 to the
stable 1.23 release, combining the original template wizard with a
second iteration that fixed everything real testers hit: template
mangling on save, limited provider choice, hard-coded timeouts, and
missing operator UI. The "AI-assisted" feature itself still wears a
"beta" label in the UI so operators know the LLM-generated output
needs human review, but everything around it (the Settings editor
for [admin.ai], the Tool Exposure bubble for the extensions group,
the Shortcuts widget category, the Report Header widget fix) is
stable and ready for general use.
Highlights
- Visual editor gains a Shortcuts widget category (Logo + nine
one-click variable chips) replacing the "Insert variable..."
dropdown. - Seven LLM providers supported end-to-end: Anthropic, OpenAI,
Google Gemini, Azure OpenAI, Ollama (self-hosted, API key
optional), Mistral, Groq. Configurable from a new "AI Template
Generation" section in/settingsinstead of hand-editing
config.toml. - Server-side Jinja validation on every template save so a broken
template never reaches/etc/zabbix-mcp/templates- the operator
gets an actionable error in the editor instead of a preview that
silently dies. - Auto-header removed from
base.html; each builtin report
(availability / capacity_host / capacity_network / backup / new
showcase) owns its own header block, so custom templates have
full control via the Shortcuts widgets.
Changes since v1.23b1 (beta1 -> stable)
Iteration on the v1.23b1 reporting beta. Focuses on the gaps real
testers hit in the AI template wizard (mangled Jinja on save,
unsupported providers, hard-coded timeouts) and on making the visual
editor emit templates that actually render.
Added
- Admin portal UI for
[admin.ai]- new "AI Template Generation" section at the bottom of/settingsthat mirrors the TOML config so operators no longer need to hand-editconfig.tomlto enable the AI wizard. Exposes Enabled toggle (drives a new[admin.ai].enabledkey, defaults to True for backward compatibility), Provider dropdown, API Base URL (auto-locked for providers with a canonical endpoint so the field cannot be filled in by mistake, editable for Azure OpenAI + Ollama), API Key (masked password input with a Show toggle; leaving the field blank on save preserves the existing secret via a newSECRET_KEEP_EMPTYrule so operators do not have to re-paste on every save), Model (empty = provider default), Timeout (30-600 s), Max tokens (1000-32000). Settings writer now walks dotted section names (admin.ai), so deeper config sub-tables can reuse the same pattern in the future. - Five additional LLM providers - on top of Anthropic + OpenAI, the wizard now supports Google Gemini, Azure OpenAI (via operator-supplied deployment URL +
api-versionquery param), Ollama (self-hosted; API key optional, driven byPROVIDERS_KEY_OPTIONAL), Mistral, and Groq. Anthropic and Gemini each get their own provider class; the OpenAI wire format is reused for Mistral/Groq/Ollama by swapping thebase_url. Every new class is integration-tested against the real upstream endpoint (fake keys return provider-specific 401/403/400 errors, confirming the class is wired end-to-end). APROVIDER_DEFAULTSregistry is the single source of truth for default base URL + model per provider. - Shortcuts category in the visual editor - new draggable widget category alongside Zabbix / Layout containing a Logo block (wraps
<img src="{{ logo_base64 }}">with the HTML-comment if-trick so the Jinja control flow survives GrapesJS re-serialization) and one-click chips for every common template variable: Company, Subtitle, Period label, Period from/to, Availability %, Hosts count, Events count, Generated at. Replaces the old "Insert variable..." dropdown - widgets are first-class citizens now instead of a side pull-down menu. - "Use logo" toolbar button on image components - selecting any
<img>in the visual editor now exposes a logo icon in the component toolbar that replaces the image with the Logo shortcut widget (full{% if logo_base64 %}<img src="{{ logo_base64 }}">{% endif %}block, not a mangled-src trait). Safer than asrcattribute swap because GrapesJS's image component synchronously validates src against URL format and strips non-URL values like Jinja placeholders. - Showcase builtin report template - new
showcase.htmllives next to availability / capacity / backup and demonstrates every widget that ships with the v1.23 visual editor (gauge, metric cards, summary row, two/three-column layout, note callout, page breaks, host table, capacity bars, inline hosts loop, backup matrix, network interfaces). Intended as a starting point operators duplicate and trim down to the sections they actually need. - Server-side Jinja validation on template save -
POST /templates/createandPOST /templates/<id>now run the submitted HTML through the sameSandboxedEnvironment + sample contextrender the AI wizard already used. A template with a syntax error is refused and the operator is returned to the editor with a specific error ("line 3: expected token ')', got 'integer'") instead of silently writing a broken file that explodes at every subsequent preview / PDF attempt. Legacy behavior preserved when the reporting extras are not installed. - Proper error page for preview failures -
/templates/preview(both the POST-with-html form and the GET-by-id form) used to return a bare<p style="color:red">Template error: ...</p>on Jinja failures, which looked broken inside the preview iframe. Now returns a full HTML document with a styled error card (red title, error type, highlighted error message, and a hint listing the three most common causes: unbalanced{% if %}/{% endif %}, ternary written as(x y z)instead ofx if cond else z, loop variable used outside its{% for %}block).
Changed
- Default
[admin.ai].timeoutbumped from 60 s to 180 s - large reasoning models (Claude Opus, GPT-5) regularly take 90-150 s for a full template and the 60 s default reliably produced "The read operation timed out" errors in testing. 180 s gives headroom without making the UI wait indefinitely on a genuinely stuck call. Applies to all seven provider classes + theget_provider()fallback. Operators who want more can now bump it in the Settings UI (30-600 s range). - Auto-header removed from
base.html- the automatic<div class="header">...subtitle + logo...</div>that every report inherited is gone. Each builtin template (availability / capacity_host / capacity_network / backup / showcase) now includes its own header block so operators have full control via the Shortcuts widgets when designing custom templates. Existing custom templates that extendedbase.htmland relied on the auto-header will need an explicit header block - either drop the Report Header widget from the Zabbix category or paste the markup from any builtin template. - Report Header widget actually renders the logo - the v1.23b1 widget had
<img src="">(empty) because binding a Jinja placeholder to the attribute mangled through GrapesJS's URL validator. Switched to the HTML-comment if-trick (<!--{% if logo_base64 %}--><img src="{{ logo_base64 }}">...<!--{% endif %}-->) so the directive survives the visual editor round-trip and the img ends up with the actual base64 data URI at PDF render time. - AI generator no longer mangles the generated template on load - the old flow called
editor.setComponents(generatedHtml)and thenswitchTab('html'), which round-tripped the Jinja through the GrapesJS HTML parser (moving<tr>out of{% for %}blocks, stripping inline styles, etc.) and synced the mangled output BACK into the textarea the operator would save. Now the generated HTML is written straight to the textarea and HTML mode is forced directly, bypassing GrapesJS entirely. When the operator later clicks Visual Editor on a template with Jinja control flow, a confirm dialog warns them that the parse can destroy working syntax. - AI system prompt tightened - explicit rules added so the LLM stops producing
{{ 'yellow' 97 'red' }}style malformed ternaries (must beA if cond else B, nested for multi-way), empty{% for %}{% endfor %}shells with the<tr>body outside the loop, and unterminated{% if %}/{% endif %}pairs. These were the three patterns that kept showing up in v1.23b1 OpenAI GPT-5 output and made the saved template fail to render. - Provider dropdown copy rewritten for clarity - replaced "(none - require BYO)" (meaningless to anyone who does not know the BYO jargon) with "None - each admin pastes own key in modal". "(custom key)" variants in the AI modal became "(paste your key)". Section description rewrites explain that a server-side default is shared across admins while leaving the field as "None" forces every admin to paste their own key on every use.
Fixed
ReportEnginemutated shared module-level template registry -load_custom_templates()used to write into_REPORT_TEMPLATES(the module-level dict), so adding a custom template in one engine instance leaked into every other instance and made custom templates show up under "Built-in" in the dashboard. Now scoped toself._templates = dict(_REPORT_TEMPLATES)per engine instance.- Tool Exposure UI missing the
extensionsgroup - the Settings page hadTOOL_DATAhardcoded with only the five Zabbix-API groups (monitoring / data_collection / alerts / users / administration), so the initMAX extension tools (graph_render,anomaly_detect,capacity_forecast,report_generate,action_prepare,action_confirm,zabbix_raw_api_call,health_check) were invisible to the bubble editor. Operators who wanted to disable reporting but keep monitoring had no way to do it from the portal. Added theextensionsgroup to the UI; the tool allowlist hint andconfig.example.tomlcomments now list it too. Also corrected the staleconfig.example.tomlclaim thathealth_check/zabbix_raw_api_callwere "always registered" - they have been in the extensions group and gated by_ext_allowed()since v1.16.
Initial v1.23 beta1 scope (2026-04-16)
The feature that made v1.23 and the reason the release exists in the first place:
- AI-assisted report template generation (beta) - a new "Generate with AI" button on
/templates/createand/templates/<id>opens a dialog where the operator describes the report they want in plain English ("Weekly SRE review with a big availability gauge, total-hosts/total-events summary row, and a table of hosts sorted by event count"). The dialog calls an LLM via[admin.ai]config, receives a valid Jinja2 template, validates it in theSandboxedEnvironmentwith the same sample context the preview uses, and loads it into the HTML editor. No valid template = clear validation error back to the operator so they can refine the prompt. Feature is off by default - add[admin.ai]toconfig.tomlwithprovider+api_keyto enable. Admin + operator roles only (viewer cannot generate). POST /templates/generateendpoint - JSON API backing the UI, CSRF-protected, audit-logged (provider, model, request/response character counts, elapsed ms - not the request content to avoid leaking NDA'd descriptions). Returns 412 when AI is disabled, 400 on validation failure (malformed Jinja, unknown variables, sandbox denied operation), 502 on upstream LLM failure, 200 with{html, provider, model, elapsed_ms}on success.[admin.ai]config section -provider,api_key(supports${ENV_VAR}expansion so the key never hits the audit log or UI),model(empty = provider default),max_tokens,timeout. Documented inconfig.example.toml. Missing section or empty key = feature silently disabled, no error at startup.src/zabbix_mcp/admin/ai_template.pymodule - provider-agnostic LLM abstraction (LLMProviderprotocol + per-provider concrete classes using stdliburllibso no new pinned dependency is needed), prompt builder with full variable catalog derived fromreporting.data_fetcherreturn shapes, CSS class list frombase.html, one worked example (availability.html), and validation viajinja2.sandbox.SandboxedEnvironmentbefore returning to the UI. Sandboxed rendering means a malicious/hallucinated template cannot escape through Python introspection even if the operator tries to save it.