mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-09 06:56:37 +00:00
fix(notifications): localize sensor alerts and preserve email context
This commit is contained in:
@@ -1586,6 +1586,9 @@ class HealthMonitor:
|
||||
'temperature': max_temp, 'duration': actual_duration,
|
||||
'dismissable': True, 'value': max_temp, 'threshold': 80,
|
||||
'details': f'High samples span {duration_str}.',
|
||||
# Explicit provenance for render-time localization;
|
||||
# older/manual detail strings remain verbatim.
|
||||
'temperature_detail_kind': 'high_samples_span',
|
||||
}
|
||||
)
|
||||
elif len(recovery_samples) >= 3:
|
||||
|
||||
@@ -1049,6 +1049,13 @@ class EmailChannel(NotificationChannel):
|
||||
# ── Build structured detail rows from known data fields ──
|
||||
detail_rows = self._build_detail_rows(data, event_type, group, html_mod)
|
||||
|
||||
# The temperature renderer has already validated whether both
|
||||
# measurements exist and localized its sampled-span detail. Its body
|
||||
# is authoritative: raw fields can otherwise print a misleading °C
|
||||
# value for an incomplete manual/persisted payload and hide the body.
|
||||
if event_type == 'temp_high':
|
||||
detail_rows = []
|
||||
|
||||
# Vzdump bodies are authoritative multi-item inventories. Keep their
|
||||
# lines exactly once, but retain the localized structured status row;
|
||||
# the remaining structured backup metadata only duplicates the report.
|
||||
@@ -1093,7 +1100,7 @@ class EmailChannel(NotificationChannel):
|
||||
# ── Reason / details block (long text, displayed separately) ──
|
||||
reason = data.get('reason', '')
|
||||
reason_html = ''
|
||||
if reason and len(reason) > 80:
|
||||
if reason and len(reason) > 80 and not (event_type == 'temp_high' and reason in body):
|
||||
reason_html = f'''
|
||||
<div style="margin:16px 0 0;padding:12px 16px;border:1px solid #d1d5db;border-radius:6px;">
|
||||
<p style="margin:0 0 4px;font-size:11px;font-weight:600;color:#374151;text-transform:uppercase;letter-spacing:0.05em;">{_runtime_text('email.details', data)}</p>
|
||||
|
||||
@@ -1899,13 +1899,25 @@ def render_template(event_type: str, data: Dict[str, Any],
|
||||
except (OverflowError, ValueError, TypeError):
|
||||
return False
|
||||
|
||||
# Only the known sampler-generated detail is eligible: duration alone
|
||||
# cannot establish provenance and manual/legacy text is not rewritten.
|
||||
seconds = data.get('duration')
|
||||
if (data.get('temperature_detail_kind') == 'high_samples_span'
|
||||
and type(seconds) is int and 0 <= seconds <= 240):
|
||||
span = (f'{seconds // 60}m {seconds % 60}s'
|
||||
if seconds >= 60 else f'{seconds}s')
|
||||
if data.get('details') == f'High samples span {span}.':
|
||||
variables['details'] = runtime_message(
|
||||
'temperature.sampleSpan', language, duration=span)
|
||||
|
||||
if not all(finite_measurement(key) for key in ('value', 'threshold')):
|
||||
template['title'] = runtime_message(
|
||||
variables['_temperature_fallback_title'] = runtime_message(
|
||||
'fallback.temperatureAlertTitle', language, hostname=variables['hostname'],
|
||||
)
|
||||
template['title'] = '{_temperature_fallback_title}'
|
||||
lines = [runtime_message('fallback.temperatureAlertBody', language)]
|
||||
reason = data.get('reason')
|
||||
details = data.get('details')
|
||||
details = variables.get('details')
|
||||
reason = reason.strip() if isinstance(reason, str) else ''
|
||||
details = details.strip() if isinstance(details, str) else ''
|
||||
if reason:
|
||||
|
||||
@@ -110,7 +110,12 @@ class RuntimeCatalogTests(unittest.TestCase):
|
||||
values.update({"hostname": "HOST-ŽILINA", "severity": "WARNING"})
|
||||
with mock.patch.object(notification_templates, "_get_hostname", return_value="HOST-ŽILINA"):
|
||||
for event_type in notification_templates.TEMPLATES:
|
||||
rendered = notification_templates.render_template(event_type, values, language="sk")
|
||||
event_values = dict(values)
|
||||
if event_type == "temp_high":
|
||||
# Temperature is a measured numeric contract; arbitrary
|
||||
# DYNAMIC_VALUE is correctly rejected by its fallback.
|
||||
event_values.update(value="89", threshold="80")
|
||||
rendered = notification_templates.render_template(event_type, event_values, language="sk")
|
||||
combined = rendered["title"] + "\n" + rendered["body"]
|
||||
if notification_templates.TEMPLATES[event_type].get("formatter"):
|
||||
continue
|
||||
@@ -119,7 +124,7 @@ class RuntimeCatalogTests(unittest.TestCase):
|
||||
+ notification_templates.TEMPLATES[event_type]["body"]
|
||||
):
|
||||
if name not in {"entity_suffix", "title_or_default"}:
|
||||
self.assertIn(str(values[name]), combined, f"{event_type}:{name}")
|
||||
self.assertIn(str(event_values[name]), combined, f"{event_type}:{name}")
|
||||
|
||||
def test_lxc_update_result_and_details_render_in_slovak(self):
|
||||
data = {
|
||||
|
||||
Reference in New Issue
Block a user