feat/retry-mechanism #1

Merged
houhuan merged 4 commits from feat/retry-mechanism into main 2025-12-24 11:07:22 +08:00
2 changed files with 64 additions and 1 deletions
Showing only changes of commit 0def77dc30 - Show all commits

View File

@ -1,4 +1,4 @@
from fastapi import APIRouter, Request, Form, Depends, HTTPException
from fastapi import APIRouter, Request, Form, Depends, HTTPException, Body
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session, joinedload, aliased
@ -7,6 +7,7 @@ import json
from app.db import SessionLocal, Target, NotificationChannel, MessageTemplate, WebhookEndpoint, RequestLog, DeliveryLog, ProcessingRule, RuleAction
from app.services.stats import stats_service
from app.services.engine import engine
from app.templates import safe_render
router = APIRouter(prefix="/admin", tags=["admin"])
templates = Jinja2Templates(directory="templates")
@ -424,6 +425,31 @@ async def delete_template(id: int = Form(...), db: Session = Depends(get_db)):
db.commit()
return RedirectResponse(url="/admin/templates", status_code=303)
@router.post("/templates/preview")
async def preview_template(data: dict = Body(...), db: Session = Depends(get_db)):
"""
Preview a template with an optional sample payload.
Request JSON: { "template_content": "...", "sample_payload": {...}, "vars": {...} }
"""
template_content = data.get("template_content")
if not template_content:
return JSONResponse({"error": "template_content is required"}, status_code=400)
sample_payload = data.get("sample_payload") or {}
# Build render context using engine's flatten helper if available
try:
render_context = engine._flatten_payload(sample_payload) if hasattr(engine, "_flatten_payload") else dict(sample_payload)
# Merge any provided template vars
extra_vars = data.get("vars") or {}
if isinstance(extra_vars, dict):
render_context.update(extra_vars)
rendered = safe_render(template_content, render_context)
return JSONResponse({"rendered": rendered})
except Exception as e:
return JSONResponse({"error": str(e)}, status_code=400)
# --- Logs ---
@router.get("/logs", response_class=HTMLResponse)
async def list_logs(request: Request, db: Session = Depends(get_db)):

View File

@ -67,9 +67,14 @@
<label class="form-label">模板内容</label>
<textarea class="form-control" name="template_content" id="templateContent" rows="3" required placeholder="收到{trans_amt}元"></textarea>
</div>
<div class="mb-3">
<label class="form-label">示例 Payload (JSON, 可选,用于预览)</label>
<textarea class="form-control font-monospace" id="templateSample" rows="4" placeholder='{"trans_amt": 100, "trans_order_info": {"remark": "abc"}}'></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-outline-secondary" onclick="previewTemplate()">预览</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</div>
@ -88,6 +93,7 @@
document.getElementById('templateId').value = "";
document.getElementById('templateName').value = "";
document.getElementById('templateContent').value = "";
document.getElementById('templateSample').value = '';
modal.show();
}
@ -97,7 +103,38 @@
document.getElementById('templateId').value = id;
document.getElementById('templateName').value = name;
document.getElementById('templateContent').value = content;
document.getElementById('templateSample').value = '';
modal.show();
}
async function previewTemplate() {
const content = document.getElementById('templateContent').value;
let sample = {};
try {
const sampleText = document.getElementById('templateSample').value;
if (sampleText && sampleText.trim()) {
sample = JSON.parse(sampleText);
}
} catch (e) {
alert('示例 payload JSON 格式错误: ' + e);
return;
}
try {
const res = await fetch('/admin/templates/preview', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ template_content: content, sample_payload: sample })
});
const data = await res.json();
if (res.ok) {
alert('渲染结果:\\n' + data.rendered);
} else {
alert('预览失败: ' + (data.error || '未知错误'));
}
} catch (e) {
alert('请求错误: ' + e);
}
}
</script>
{% endblock %}