mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
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:
@@ -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()
|
||||
Reference in New Issue
Block a user