fix: name the guest in VM/CT health alerts, refresh AI models, propose the recipe's volume choice

- A health problem of a VM or CT names the guest in its title and in the
  Quiet Hours summary; a recovery shows its category by name in the
  reader's language.
- AI model lists reviewed against each provider: Gemini adds 3.6/3.7/3.8
  Flash, Groq adds gpt-oss-120b, and Groq/OpenRouter models no longer
  served are removed.
- OCI installer proposes the volume choice the recipe declares, so a path
  meant for a host directory is proposed as one; Duplicati /source is
  proposed on a host directory.
This commit is contained in:
MacRimi
2026-10-26 18:47:09 +01:00
parent b6500c147e
commit 7b1736fe3e
21 changed files with 160 additions and 24 deletions
+10 -10
View File
@@ -1,16 +1,16 @@
{
"_description": "Verified AI models for ProxMenux notifications. Only models listed here will be shown to users. Models are tested to work with the chat/completions API format.",
"_updated": "2026-09-02",
"_updated": "2026-10-26",
"_verifier": "Refreshed with tools/ai-models-verifier (private). Re-run before each ProxMenux release to keep the list current. The verifier and ProxMenux share the same reasoning/thinking-model handlers so their verdicts stay aligned with runtime behaviour.",
"groq": {
"models": [
"allam-2-7b",
"qwen/qwen3.8-27b",
"openai/gpt-oss-20b",
"groq/compound-mini"
"openai/gpt-oss-120b"
],
"recommended": "openai/gpt-oss-20b",
"_note": "Verified 2026-09-02 with the Groq API. 4 of 12 tested pass. `llama-3.3-70b-versatile` no longer served by Groq. Recommended kept as `openai/gpt-oss-20b` (english-capable) — `allam-2-7b` is fastest but Arabic-focused; add manually if serving Arabic notifications."
"_note": "Verified 2026-10-26 with the Groq API. 4 of 8 tested pass. `groq/compound-mini` is no longer served and was dropped; `openai/gpt-oss-120b` added. Recommended kept as `openai/gpt-oss-20b` (english-capable) — `allam-2-7b` is fastest but Arabic-focused."
},
"gemini": {
"models": [
@@ -21,7 +21,10 @@
"gemini-flash-lite-latest",
"gemini-2.5-flash",
"gemini-3-flash-preview",
"gemini-3.5-flash"
"gemini-3.5-flash",
"gemini-3.6-flash",
"gemini-3.7-flash",
"gemini-3.8-flash"
],
"recommended": "gemini-2.5-flash-lite",
"_deprecated": [
@@ -31,7 +34,7 @@
"gemini-1.0-pro",
"gemini-pro"
],
"_note": "Verified 2026-09-02. gemini-flash-lite-latest passes consistently but gemini-2.5-flash-lite remains recommended because 'latest' aliases can drift. gemini-3.1-flash-lite is the stable successor to 3-flash-preview. Pro variants continue to reject thinkingBudget=0 and are overkill for notification translation."
"_note": "Verified 2026-10-26. Added gemini-3.6-flash, 3.7-flash and 3.8-flash. gemini-flash-lite-latest passes consistently but gemini-2.5-flash-lite remains recommended because 'latest' aliases can drift. Pro variants continue to reject thinkingBudget=0 and are overkill for notification translation; gemini-4-argon passes but takes ~20s per message."
},
"openai": {
"models": [
@@ -116,13 +119,10 @@
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
"nvidia/nemotron-3-super-120b-a12b:free",
"google/gemma-4-26b-a4b-it:free",
"nvidia/nemotron-nano-12b-v2-vl:free",
"nvidia/nemotron-3-nano-30b-a3b:free",
"poolside/laguna-s-2.1:free",
"openai/gpt-oss-20b:free"
"poolside/laguna-s-2.1:free"
],
"recommended": "meta-llama/llama-3.3-70b-instruct",
"_note": "Curated manually from the 249+ models OpenRouter serves — the auto-verifier passes ~200 today but only a subset are practical for notification translation. Includes the 11 previously-curated paid models plus 22 new mainstream additions (gpt-5.1, claude-opus-4.8, sonnet-5, grok-4.3/4.20, kimi-k2, deepseek-v3.2, nova, cohere-a, sonar, ...) and 6 `:free` variants re-added manually — the free tier is intentionally supported per user request even though today's rate limits blocked some of them from passing the test."
"_note": "Curated manually from the 249+ models OpenRouter serves — the auto-verifier passes ~200 today but only a subset are practical for notification translation. Includes the 11 previously-curated paid models plus 22 new mainstream additions (gpt-5.1, claude-opus-4.8, sonnet-5, grok-4.3/4.20, kimi-k2, deepseek-v3.2, nova, cohere-a, sonar, ...) and 6 `:free` variants re-added manually — the free tier is intentionally supported per user request even though today's rate limits blocked some of them from passing the test. Reviewed 2026-10-26: three `:free` variants no longer served by OpenRouter were dropped."
},
"ollama": {
"_note": "Ollama models are local, we don't filter them. User manages their own models.",
@@ -2125,9 +2125,22 @@ def render_template(event_type: str, data: Dict[str, Any],
or ''
)
_ent = str(_ent).strip()
# A VM or CT health issue records the guest as id, vmname and type.
if not _ent and str(variables.get('category', '')) == 'vms' and variables.get('id'):
_ent = f"{variables.get('type') or 'VM/CT'} {variables['id']}"
if variables.get('vmname'):
_ent += f" ({variables['vmname']})"
variables['entity'] = _ent
variables['entity_suffix'] = f' — {_ent}' if _ent else ''
# Health categories reach the templates by their key ("vms"); show the
# name the health summary uses for them, in the reader's language.
_category = str(variables.get('category', '')).strip()
if _category:
_category_label = runtime_message(f'healthDegraded.categories.{_category}', language)
if _category_label:
variables['category'] = _category_label
# `title_or_default` lets a template surface a caller-computed title
# when the caller already knows the entity context (e.g.
# flask_server's health collector), and fall back to a stock string
@@ -0,0 +1,55 @@
"""A health problem of a VM or CT names the guest in its title, and health
categories are shown by their name, not by their internal key."""
import sys
import unittest
from pathlib import Path
from unittest import mock
SCRIPTS_DIR = Path(__file__).resolve().parents[1]
if str(SCRIPTS_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPTS_DIR))
import notification_templates as templates
QMP = {'category': 'vms', 'severity': 'WARNING', 'error_key': 'vm_101',
'reason': 'VM 101 (web): QMP command failed or timed out.',
'id': '101', 'vmname': 'web', 'type': 'VM'}
class VmHealthTitleTests(unittest.TestCase):
def setUp(self):
patcher = mock.patch.object(templates, '_get_hostname', return_value='pve')
patcher.start()
self.addCleanup(patcher.stop)
def test_a_guest_problem_names_the_guest(self):
title = templates.render_template('system_problem', dict(QMP))['title']
self.assertEqual(title, 'pve: System problem detected — VM 101 (web)')
def test_a_container_without_a_name_is_named_by_its_id(self):
data = dict(QMP, id='105', vmname='', type='CT')
self.assertTrue(templates.render_template('system_problem', data)['title'].endswith('— CT 105'))
def test_the_recovery_names_the_category_and_the_guest(self):
data = dict(QMP, severity='OK', original_severity='WARNING', duration='6m', is_recovery=True)
title = templates.render_template('error_resolved', data)['title']
self.assertEqual(title, 'pve: Resolved - VMs and containers — VM 101 (web)')
def test_the_category_follows_the_language_of_the_reader(self):
data = dict(QMP, severity='OK', original_severity='WARNING', duration='6m', is_recovery=True)
title = templates.render_template('error_resolved', data, 'es')['title']
self.assertIn('Máquinas virtuales y contenedores', title)
self.assertNotIn(' vms', title)
def test_a_category_without_a_name_keeps_its_key(self):
data = {'category': 'temperature', 'severity': 'OK', 'reason': 'Back to normal',
'original_severity': 'WARNING', 'duration': '3m', 'is_recovery': True}
self.assertIn('Resolved - temperature', templates.render_template('error_resolved', data)['title'])
def test_other_events_with_an_id_are_not_given_a_guest(self):
data = {'category': 'storage', 'severity': 'WARNING', 'reason': 'x', 'id': '7', 'type': 'VM'}
self.assertNotIn('VM 7', templates.render_template('system_problem', data)['title'])
if __name__ == '__main__':
unittest.main()