mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-06-11 17:34:44 +02:00
fix: notification system improvements and threshold corrections
- Fix leftover severity references (info→routine in filter dropdown) - Fix node_id int handling in connector and channels (handle both int and string) - Add LLM-generated reports for notifications (replace raw data dumps) - Fix health.score.composite attribute path for RF reports - Add deterministic HF band conditions from SFI/Kp values - Remove max_tokens from LLM calls (character limits at delivery) - Weather feed improvements: show event_type + area, local events first - Fix is_online to use configured offline_threshold_hours in data store - Update stale defaults: offline 24→2h, battery_warning 20→30% - Add TODO comments for packet_threshold scale bug Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7a4bd4f38f
commit
95ec7d5351
13 changed files with 1145 additions and 1133 deletions
|
|
@ -1,305 +1,305 @@
|
|||
"""Notification API routes with comprehensive testing."""
|
||||
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
router = APIRouter(prefix="/notifications", tags=["notifications"])
|
||||
|
||||
|
||||
class TestRequest(BaseModel):
|
||||
"""Request body for test endpoint."""
|
||||
send: bool = False # Legacy: True = send_test
|
||||
action: str = "preview" # "preview", "send_test", "send_status", "send_live"
|
||||
|
||||
|
||||
class ChannelTestRequest(BaseModel):
|
||||
"""Request body for channel connectivity test."""
|
||||
type: str # mesh_broadcast, mesh_dm, email, webhook
|
||||
# Mesh broadcast
|
||||
channel_index: Optional[int] = 0
|
||||
# Mesh DM
|
||||
node_ids: Optional[List[str]] = []
|
||||
# Email
|
||||
smtp_host: Optional[str] = ""
|
||||
smtp_port: Optional[int] = 587
|
||||
smtp_user: Optional[str] = ""
|
||||
smtp_password: Optional[str] = ""
|
||||
smtp_tls: Optional[bool] = True
|
||||
from_address: Optional[str] = ""
|
||||
recipients: Optional[List[str]] = []
|
||||
# Webhook
|
||||
url: Optional[str] = ""
|
||||
headers: Optional[Dict[str, str]] = {}
|
||||
|
||||
|
||||
class RuleSourcesRequest(BaseModel):
|
||||
"""Request body for rule sources health check."""
|
||||
categories: List[str] = []
|
||||
|
||||
|
||||
@router.get("/categories")
|
||||
async def get_categories():
|
||||
"""Get all alert categories with descriptions."""
|
||||
try:
|
||||
from ...notifications.categories import list_categories
|
||||
return list_categories()
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
|
||||
@router.get("/rules")
|
||||
async def get_rules(request: Request):
|
||||
"""Get configured notification rules with stats."""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
return []
|
||||
|
||||
rules = notification_router.get_rules()
|
||||
|
||||
# Enhance rules with stats
|
||||
result = []
|
||||
for i, rule in enumerate(rules):
|
||||
rule_copy = dict(rule)
|
||||
stats = rule_copy.pop("_stats", {})
|
||||
rule_copy["stats"] = stats
|
||||
rule_copy["index"] = i
|
||||
result.append(rule_copy)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/rules/{rule_index}/stats")
|
||||
async def get_rule_stats(request: Request, rule_index: int):
|
||||
"""Get statistics for a specific rule."""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
rules_config = getattr(request.app.state, "config", None)
|
||||
if rules_config:
|
||||
rules_config = getattr(rules_config, "rules", [])
|
||||
if rule_index < 0 or rule_index >= len(rules_config):
|
||||
raise HTTPException(status_code=404, detail="Rule not found")
|
||||
|
||||
rule = rules_config[rule_index]
|
||||
if hasattr(rule, "__dict__"):
|
||||
rule_dict = {k: v for k, v in rule.__dict__.items() if not k.startswith("_")}
|
||||
else:
|
||||
rule_dict = dict(rule)
|
||||
|
||||
rule_name = rule_dict.get("name", f"Rule {rule_index}")
|
||||
return notification_router.get_rule_stats(rule_name)
|
||||
|
||||
return {"last_fired": None, "last_test": None, "fire_count": 0}
|
||||
|
||||
|
||||
@router.post("/channels/test")
|
||||
async def test_channel(request: Request, body: ChannelTestRequest):
|
||||
"""Test channel connectivity without sending actual alert content.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"success": bool,
|
||||
"message": str, # Human-readable result
|
||||
"error": str, # Detailed error if failed
|
||||
"details": {} # Channel-specific details
|
||||
}
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
# Build channel config from request
|
||||
channel_config = {"type": body.type}
|
||||
|
||||
if body.type == "mesh_broadcast":
|
||||
channel_config["channel_index"] = body.channel_index or 0
|
||||
elif body.type == "mesh_dm":
|
||||
channel_config["node_ids"] = body.node_ids or []
|
||||
elif body.type == "email":
|
||||
channel_config.update({
|
||||
"smtp_host": body.smtp_host or "",
|
||||
"smtp_port": body.smtp_port or 587,
|
||||
"smtp_user": body.smtp_user or "",
|
||||
"smtp_password": body.smtp_password or "",
|
||||
"smtp_tls": body.smtp_tls if body.smtp_tls is not None else True,
|
||||
"from_address": body.from_address or "",
|
||||
"recipients": body.recipients or [],
|
||||
})
|
||||
elif body.type == "webhook":
|
||||
channel_config.update({
|
||||
"url": body.url or "",
|
||||
"headers": body.headers or {},
|
||||
})
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Unknown channel type",
|
||||
"error": f"Channel type '{body.type}' is not supported",
|
||||
"details": {}
|
||||
}
|
||||
|
||||
result = await notification_router.test_channel(channel_config)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/test")
|
||||
async def test_rule(request: Request, rule_index: int, body: Optional[TestRequest] = None):
|
||||
"""Test a notification rule against current conditions.
|
||||
|
||||
Returns comprehensive test result including:
|
||||
- Live data from relevant environmental feeds
|
||||
- Matching alerts (conditions that would fire)
|
||||
- Near-misses (filtered by severity threshold)
|
||||
- Preview messages and delivery status
|
||||
- Source health (which feeds are enabled)
|
||||
- Rule statistics (last fired, fire count)
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
action = body.action if body else "preview"
|
||||
send = body.send if body else False
|
||||
|
||||
# Legacy support
|
||||
if send and action == "preview":
|
||||
action = "send_test"
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action=action,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/preview")
|
||||
async def preview_rule(request: Request, rule_index: int):
|
||||
"""Preview what a rule would match right now (without sending)."""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action="preview",
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/sources")
|
||||
async def get_rule_sources(request: Request, body: RuleSourcesRequest):
|
||||
"""Get data source health for a set of categories.
|
||||
|
||||
Returns per-category source status:
|
||||
{
|
||||
"category_id": {
|
||||
"enabled": true/false,
|
||||
"active_events": number,
|
||||
"source": "nws"/"swpc"/etc,
|
||||
"status": "ok"/"disabled"/"no_data"
|
||||
}
|
||||
}
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
|
||||
return notification_router.get_source_health(body.categories, env_store)
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/send-status")
|
||||
async def send_rule_status(request: Request, rule_index: int):
|
||||
"""Send current conditions summary through a rule's channel.
|
||||
|
||||
Formats current live data as a readable message and delivers
|
||||
through the rule's configured channel with [STATUS] prefix.
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action="send_status",
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/send-test")
|
||||
async def send_rule_test(request: Request, rule_index: int):
|
||||
"""Send example alert message through a rule's channel.
|
||||
|
||||
Sends the example_message from the rule's first category
|
||||
through the configured channel with [TEST] prefix.
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action="send_test",
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/send-live")
|
||||
async def send_rule_live(request: Request, rule_index: int):
|
||||
"""Send actual live alert through a rule's channel.
|
||||
|
||||
Only available when there are matching conditions.
|
||||
Sends one of the actual matching alerts with [LIVE TEST] prefix.
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action="send_live",
|
||||
)
|
||||
|
||||
return result
|
||||
"""Notification API routes with comprehensive testing."""
|
||||
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
router = APIRouter(prefix="/notifications", tags=["notifications"])
|
||||
|
||||
|
||||
class TestRequest(BaseModel):
|
||||
"""Request body for test endpoint."""
|
||||
send: bool = False # Legacy: True = send_test
|
||||
action: str = "preview" # "preview", "send_test", "send_status", "send_live"
|
||||
|
||||
|
||||
class ChannelTestRequest(BaseModel):
|
||||
"""Request body for channel connectivity test."""
|
||||
type: str # mesh_broadcast, mesh_dm, email, webhook
|
||||
# Mesh broadcast
|
||||
channel_index: Optional[int] = 0
|
||||
# Mesh DM
|
||||
node_ids: Optional[List[str]] = []
|
||||
# Email
|
||||
smtp_host: Optional[str] = ""
|
||||
smtp_port: Optional[int] = 587
|
||||
smtp_user: Optional[str] = ""
|
||||
smtp_password: Optional[str] = ""
|
||||
smtp_tls: Optional[bool] = True
|
||||
from_address: Optional[str] = ""
|
||||
recipients: Optional[List[str]] = []
|
||||
# Webhook
|
||||
url: Optional[str] = ""
|
||||
headers: Optional[Dict[str, str]] = {}
|
||||
|
||||
|
||||
class RuleSourcesRequest(BaseModel):
|
||||
"""Request body for rule sources health check."""
|
||||
categories: List[str] = []
|
||||
|
||||
|
||||
@router.get("/categories")
|
||||
async def get_categories():
|
||||
"""Get all alert categories with descriptions."""
|
||||
try:
|
||||
from ...notifications.categories import list_categories
|
||||
return list_categories()
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
|
||||
@router.get("/rules")
|
||||
async def get_rules(request: Request):
|
||||
"""Get configured notification rules with stats."""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
return []
|
||||
|
||||
rules = notification_router.get_rules()
|
||||
|
||||
# Enhance rules with stats
|
||||
result = []
|
||||
for i, rule in enumerate(rules):
|
||||
rule_copy = dict(rule)
|
||||
stats = rule_copy.pop("_stats", {})
|
||||
rule_copy["stats"] = stats
|
||||
rule_copy["index"] = i
|
||||
result.append(rule_copy)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/rules/{rule_index}/stats")
|
||||
async def get_rule_stats(request: Request, rule_index: int):
|
||||
"""Get statistics for a specific rule."""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
rules_config = getattr(request.app.state, "config", None)
|
||||
if rules_config:
|
||||
rules_config = getattr(rules_config, "rules", [])
|
||||
if rule_index < 0 or rule_index >= len(rules_config):
|
||||
raise HTTPException(status_code=404, detail="Rule not found")
|
||||
|
||||
rule = rules_config[rule_index]
|
||||
if hasattr(rule, "__dict__"):
|
||||
rule_dict = {k: v for k, v in rule.__dict__.items() if not k.startswith("_")}
|
||||
else:
|
||||
rule_dict = dict(rule)
|
||||
|
||||
rule_name = rule_dict.get("name", f"Rule {rule_index}")
|
||||
return notification_router.get_rule_stats(rule_name)
|
||||
|
||||
return {"last_fired": None, "last_test": None, "fire_count": 0}
|
||||
|
||||
|
||||
@router.post("/channels/test")
|
||||
async def test_channel(request: Request, body: ChannelTestRequest):
|
||||
"""Test channel connectivity without sending actual alert content.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"success": bool,
|
||||
"message": str, # Human-readable result
|
||||
"error": str, # Detailed error if failed
|
||||
"details": {} # Channel-specific details
|
||||
}
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
# Build channel config from request
|
||||
channel_config = {"type": body.type}
|
||||
|
||||
if body.type == "mesh_broadcast":
|
||||
channel_config["channel_index"] = body.channel_index or 0
|
||||
elif body.type == "mesh_dm":
|
||||
channel_config["node_ids"] = body.node_ids or []
|
||||
elif body.type == "email":
|
||||
channel_config.update({
|
||||
"smtp_host": body.smtp_host or "",
|
||||
"smtp_port": body.smtp_port or 587,
|
||||
"smtp_user": body.smtp_user or "",
|
||||
"smtp_password": body.smtp_password or "",
|
||||
"smtp_tls": body.smtp_tls if body.smtp_tls is not None else True,
|
||||
"from_address": body.from_address or "",
|
||||
"recipients": body.recipients or [],
|
||||
})
|
||||
elif body.type == "webhook":
|
||||
channel_config.update({
|
||||
"url": body.url or "",
|
||||
"headers": body.headers or {},
|
||||
})
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Unknown channel type",
|
||||
"error": f"Channel type '{body.type}' is not supported",
|
||||
"details": {}
|
||||
}
|
||||
|
||||
result = await notification_router.test_channel(channel_config)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/test")
|
||||
async def test_rule(request: Request, rule_index: int, body: Optional[TestRequest] = None):
|
||||
"""Test a notification rule against current conditions.
|
||||
|
||||
Returns comprehensive test result including:
|
||||
- Live data from relevant environmental feeds
|
||||
- Matching alerts (conditions that would fire)
|
||||
- Near-misses (filtered by severity threshold)
|
||||
- Preview messages and delivery status
|
||||
- Source health (which feeds are enabled)
|
||||
- Rule statistics (last fired, fire count)
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
action = body.action if body else "preview"
|
||||
send = body.send if body else False
|
||||
|
||||
# Legacy support
|
||||
if send and action == "preview":
|
||||
action = "send_test"
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action=action,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/preview")
|
||||
async def preview_rule(request: Request, rule_index: int):
|
||||
"""Preview what a rule would match right now (without sending)."""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action="preview",
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/sources")
|
||||
async def get_rule_sources(request: Request, body: RuleSourcesRequest):
|
||||
"""Get data source health for a set of categories.
|
||||
|
||||
Returns per-category source status:
|
||||
{
|
||||
"category_id": {
|
||||
"enabled": true/false,
|
||||
"active_events": number,
|
||||
"source": "nws"/"swpc"/etc,
|
||||
"status": "ok"/"disabled"/"no_data"
|
||||
}
|
||||
}
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
|
||||
return notification_router.get_source_health(body.categories, env_store)
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/send-status")
|
||||
async def send_rule_status(request: Request, rule_index: int):
|
||||
"""Send current conditions summary through a rule's channel.
|
||||
|
||||
Formats current live data as a readable message and delivers
|
||||
through the rule's configured channel with [STATUS] prefix.
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action="send_status",
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/send-test")
|
||||
async def send_rule_test(request: Request, rule_index: int):
|
||||
"""Send example alert message through a rule's channel.
|
||||
|
||||
Sends the example_message from the rule's first category
|
||||
through the configured channel with [TEST] prefix.
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action="send_test",
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/rules/{rule_index}/send-live")
|
||||
async def send_rule_live(request: Request, rule_index: int):
|
||||
"""Send actual live alert through a rule's channel.
|
||||
|
||||
Only available when there are matching conditions.
|
||||
Sends one of the actual matching alerts with [LIVE TEST] prefix.
|
||||
"""
|
||||
notification_router = getattr(request.app.state, "notification_router", None)
|
||||
if not notification_router:
|
||||
raise HTTPException(status_code=404, detail="Notification router not configured")
|
||||
|
||||
alert_engine = getattr(request.app.state, "alert_engine", None)
|
||||
env_store = getattr(request.app.state, "env_store", None)
|
||||
health_engine = getattr(request.app.state, "health_engine", None)
|
||||
|
||||
result = await notification_router.test_rule_with_conditions(
|
||||
rule_index,
|
||||
alert_engine=alert_engine,
|
||||
env_store=env_store,
|
||||
health_engine=health_engine,
|
||||
action="send_live",
|
||||
)
|
||||
|
||||
return result
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
523
meshai/dashboard/static/assets/index-Bildyb1E.js
Normal file
523
meshai/dashboard/static/assets/index-Bildyb1E.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
meshai/dashboard/static/assets/index-QhNRb-ap.css
Normal file
1
meshai/dashboard/static/assets/index-QhNRb-ap.css
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -8,8 +8,8 @@
|
|||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-BXyt_EfK.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CtFYHJy4.css">
|
||||
<script type="module" crossorigin src="/assets/index-Bildyb1E.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-QhNRb-ap.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue