mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-02-22 10:26:26 +00:00
Update notification service
This commit is contained in:
@@ -238,8 +238,6 @@ def _build_webhook_fallback():
|
||||
f"webhook: {_PVE_ENDPOINT_ID}",
|
||||
f"\tmethod post",
|
||||
f"\turl {_PVE_WEBHOOK_URL}",
|
||||
'\theader Content-Type=application/json',
|
||||
'\tbody {"title":"{{ escape title }}","message":"{{ escape message }}","severity":"{{ severity }}","timestamp":{{ timestamp }},"fields":{{ json fields }}}',
|
||||
"",
|
||||
f"matcher: {_PVE_MATCHER_ID}",
|
||||
f"\ttarget {_PVE_ENDPOINT_ID}",
|
||||
@@ -314,12 +312,13 @@ def setup_pve_webhook_core() -> dict:
|
||||
# - A matching entry in priv/notifications.cfg (even if empty)
|
||||
# The body template uses PVE's Handlebars syntax to pass notification
|
||||
# metadata to our webhook handler as structured JSON.
|
||||
# PVE sends JSON by default (title, message, severity, timestamp, fields).
|
||||
# Do NOT set header or body -- PVE's config parser rejects custom header
|
||||
# formats and a missing/malformed header line corrupts the ENTIRE config.
|
||||
endpoint_block = (
|
||||
f"webhook: {_PVE_ENDPOINT_ID}\n"
|
||||
f"\tmethod post\n"
|
||||
f"\turl {_PVE_WEBHOOK_URL}\n"
|
||||
f'\theader Content-Type=application/json\n'
|
||||
f'\tbody {{"title":"{{{{ escape title }}}}","message":"{{{{ escape message }}}}","severity":"{{{{ severity }}}}","timestamp":{{{{ timestamp }}}},"fields":{{{{ json fields }}}}}}\n'
|
||||
)
|
||||
|
||||
matcher_block = (
|
||||
@@ -382,6 +381,35 @@ def setup_pve_webhook_core() -> dict:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ── Step 10: Configure body and header via pvesh API ──
|
||||
# Writing header/body directly to the config file uses a different
|
||||
# internal format that PVE's parser rejects. Using pvesh set handles
|
||||
# escaping and the priv-config wire format correctly.
|
||||
import subprocess
|
||||
|
||||
# Body template: PVE Handlebars that sends JSON to our webhook
|
||||
body_template = '{"title":"{{ escape title }}","message":"{{ escape message }}","severity":"{{ severity }}","timestamp":"{{ timestamp }}"}'
|
||||
|
||||
try:
|
||||
# Set body template
|
||||
subprocess.run(
|
||||
['pvesh', 'set',
|
||||
f'/cluster/notifications/endpoints/webhook/{_PVE_ENDPOINT_ID}',
|
||||
'--body', body_template],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
|
||||
# Set Content-Type header
|
||||
subprocess.run(
|
||||
['pvesh', 'set',
|
||||
f'/cluster/notifications/endpoints/webhook/{_PVE_ENDPOINT_ID}',
|
||||
'--header', 'Content-Type:application/json'],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
except Exception as e:
|
||||
# Non-fatal: webhook still works, just sends raw format
|
||||
result['body_config_warning'] = str(e)
|
||||
|
||||
result['configured'] = True
|
||||
result['secret'] = secret
|
||||
return result
|
||||
@@ -635,14 +663,40 @@ def proxmox_webhook():
|
||||
|
||||
# ── Parse and process payload ──
|
||||
try:
|
||||
content_type = request.content_type or ''
|
||||
raw_data = request.get_data(as_text=True) or ''
|
||||
|
||||
# Try JSON first
|
||||
payload = request.get_json(silent=True) or {}
|
||||
|
||||
# If not JSON, try form data
|
||||
if not payload:
|
||||
payload = dict(request.form)
|
||||
|
||||
# If still empty, try parsing raw data as JSON (PVE may not set Content-Type)
|
||||
if not payload and raw_data:
|
||||
try:
|
||||
import json
|
||||
payload = json.loads(raw_data)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
pass
|
||||
|
||||
# If still empty, create a minimal test event from raw data
|
||||
if not payload:
|
||||
return _reject(400, 'invalid_payload', 400)
|
||||
if raw_data:
|
||||
payload = {
|
||||
'type': 'webhook_test',
|
||||
'title': 'PVE Webhook Test',
|
||||
'body': raw_data[:500],
|
||||
'severity': 'info',
|
||||
}
|
||||
else:
|
||||
return _reject(400, 'empty_payload', 400)
|
||||
|
||||
result = notification_manager.process_webhook(payload)
|
||||
status_code = 200 if result.get('accepted') else 400
|
||||
return jsonify(result), status_code
|
||||
except Exception:
|
||||
return jsonify({'accepted': False, 'error': 'internal_error'}), 500
|
||||
# Always return 200 to PVE -- a non-200 makes PVE report the webhook as broken.
|
||||
# The 'accepted' field in the JSON body indicates actual processing status.
|
||||
return jsonify(result), 200
|
||||
except Exception as e:
|
||||
# Still return 200 to avoid PVE flagging the webhook as broken
|
||||
return jsonify({'accepted': False, 'error': 'internal_error', 'detail': str(e)}), 200
|
||||
|
||||
@@ -363,6 +363,14 @@ TEMPLATES = {
|
||||
'default_enabled': True,
|
||||
},
|
||||
|
||||
# ── PVE webhook test ──
|
||||
'webhook_test': {
|
||||
'title': '{hostname}: Webhook test received',
|
||||
'body': 'PVE webhook connectivity test successful.\n{reason}',
|
||||
'group': 'system',
|
||||
'default_enabled': True,
|
||||
},
|
||||
|
||||
# ── Burst aggregation summaries ──
|
||||
'burst_auth_fail': {
|
||||
'title': '{hostname}: {count} auth failures in {window}',
|
||||
|
||||
Reference in New Issue
Block a user