- 添加FastAPI应用基础结构,包括主入口、路由和模型定义
- 实现Webhook接收端点(/webhook/{namespace})和健康检查(/health)
- 添加管理后台路由和模板,支持端点、目标、渠道和模板管理
- 包含SQLite数据库模型定义和初始化逻辑
- 添加日志记录和统计服务
- 包含Dockerfile和配置示例文件
- 添加项目文档,包括设计、流程图和验收标准
125 lines
5.6 KiB
HTML
125 lines
5.6 KiB
HTML
{% extends "admin/base.html" %}
|
||
{% block title %}端点管理 - Webhook{% endblock %}
|
||
{% block content %}
|
||
<div class="row mb-3">
|
||
<div class="col">
|
||
<h3>接收端点 (Endpoints)</h3>
|
||
</div>
|
||
<div class="col-auto">
|
||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">添加端点</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert alert-info alert-dismissible fade show" role="alert">
|
||
<strong>什么是“接收端点”?</strong>
|
||
<p class="mb-0">
|
||
接收端点(Endpoints)是<strong>外部系统向本服务发送Webhook数据的入口地址</strong>。<br>
|
||
<strong>Namespace</strong>:URL路径的唯一标识。例如创建 namespace 为 <code>client_a</code>,则外部请求地址为 <code>/webhook/client_a</code>。<br>
|
||
<strong>流程配置</strong>:点击列表中的 <span class="badge bg-info text-dark">Namespace</span> 或 <button class="btn btn-sm btn-outline-primary py-0 px-1">⚙️ 配置</button> 按钮,进入端点的规则编排页面。
|
||
</p>
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||
</div>
|
||
|
||
<table class="table table-striped">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Namespace</th>
|
||
<th>完整URL示例</th>
|
||
<th>描述</th>
|
||
<th>状态</th>
|
||
<th>创建时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for e in endpoints %}
|
||
<tr>
|
||
<td>{{ e.id }}</td>
|
||
<td><a href="/admin/endpoints/{{ e.id }}" class="text-decoration-none"><span class="badge bg-info text-dark fs-6">{{ e.namespace }}</span></a></td>
|
||
<td>
|
||
<code>/webhook/{{ e.namespace }}</code>
|
||
<button class="btn btn-sm btn-link text-decoration-none copy-btn" data-path="/webhook/{{ e.namespace }}" title="复制完整URL">📋</button>
|
||
</td>
|
||
<td>{{ e.description or '-' }}</td>
|
||
<td>
|
||
{% if e.is_active %}
|
||
<span class="badge bg-success">启用</span>
|
||
{% else %}
|
||
<span class="badge bg-secondary">禁用</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>{{ e.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||
<td>
|
||
<a href="/admin/endpoints/{{ e.id }}" class="btn btn-sm btn-outline-primary">⚙️ 配置</a>
|
||
<form action="/admin/endpoints/toggle" method="post" style="display:inline">
|
||
<input type="hidden" name="id" value="{{ e.id }}">
|
||
{% if e.is_active %}
|
||
<button type="submit" class="btn btn-sm btn-warning">禁用</button>
|
||
{% else %}
|
||
<button type="submit" class="btn btn-sm btn-success">启用</button>
|
||
{% endif %}
|
||
</form>
|
||
<form action="/admin/endpoints/delete" method="post" style="display:inline" onsubmit="return confirm('确定删除?')">
|
||
<input type="hidden" name="id" value="{{ e.id }}">
|
||
<button type="submit" class="btn btn-sm btn-danger">删除</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Add Modal -->
|
||
<div class="modal fade" id="addModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<form action="/admin/endpoints" method="post">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">创建新端点</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<label class="form-label">Namespace (URL路径的一部分)</label>
|
||
<input type="text" class="form-control" name="namespace" required placeholder="my-service-1">
|
||
<div class="form-text">只能包含字母、数字、下划线或连字符。</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">描述</label>
|
||
<input type="text" class="form-control" name="description" placeholder="用于XX业务接收">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
document.querySelectorAll('.copy-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const path = this.getAttribute('data-path');
|
||
// Construct full URL based on current window location
|
||
const fullUrl = window.location.origin + path;
|
||
|
||
navigator.clipboard.writeText(fullUrl).then(() => {
|
||
const originalContent = this.innerHTML;
|
||
this.innerHTML = '✅';
|
||
setTimeout(() => {
|
||
this.innerHTML = originalContent;
|
||
}, 1500);
|
||
}).catch(err => {
|
||
console.error('Failed to copy: ', err);
|
||
alert('复制失败,请手动复制');
|
||
});
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|