fix: heartbeat 500 error, timezone to Beijing, tasks guide collapse
- fix(api): heartbeat endpoint no longer requires request body, fix spa_fallback 404 handler causing 500 on API routes, add db.refresh() in agent update/heartbeat - feat(time): unify all frontend time display to Beijing time (Asia/Shanghai) via new beijing() utility; backend alert messages also use BJT - feat(ui): collapse AI agent instruction panel on Tasks page by default - chore: add backend/app/utils.py and frontend/src/utils/time.js
This commit is contained in:
parent
cd41e59afc
commit
6f64eecb43
203
AGENT_GUIDE.md
203
AGENT_GUIDE.md
@ -1,126 +1,177 @@
|
|||||||
# Agent 接入指南
|
# TaskPulse 接入指引 — 面向 AI Agent
|
||||||
|
|
||||||
本文档说明 AI Agent 如何接入 TaskPulse 系统。
|
以下步骤面向 AI Agent。用户将此文档发送给你,请你按步骤完成 TaskPulse 系统的接入。
|
||||||
|
|
||||||
## 一、注册 Agent
|
## 环境要求
|
||||||
|
|
||||||
首先向 TaskPulse 注册你的 Agent,获取 API Key:
|
开始接入前,请确认你可以发起 HTTP 请求(curl 或 Python requests)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第 1 步:注册你自己
|
||||||
|
|
||||||
|
向 TaskPulse 注册你的 Agent 身份。用你的实际名称替换 `<你的名字>`。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:8000/api/agents \
|
# 注册 Agent
|
||||||
|
TASK_PULSE_URL="<用户提供的系统地址>"
|
||||||
|
|
||||||
|
curl -X POST ${TASK_PULSE_URL}/api/agents \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"name": "my-agent", "description": "我的第一个 Agent"}'
|
-d '{"name": "<你的名字>", "description": "<你的职责描述,例如:数据同步 Agent>"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
返回示例:
|
**响应示例:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "my-agent",
|
"name": "my-agent",
|
||||||
"api_key": "tp_a1b2c3d4e5f6...",
|
"api_key": "tp_a1b2c3d4e5f6...",
|
||||||
"status": "active",
|
"status": "active"
|
||||||
"task_count": 0,
|
|
||||||
...
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**请妥善保存 `api_key`**,后续所有操作都需要通过这个 Key 鉴权。
|
**请妥善保存 `api_key`**,后续所有请求都需要通过 `Authorization: Bearer <api_key>` 鉴权。
|
||||||
|
|
||||||
## 二、注册定时任务
|
## 第 2 步:注册定时任务
|
||||||
|
|
||||||
Agent 注册成功后,注册它所管理的定时任务:
|
你需要在 TaskPulse 中注册你管理的每一个定时任务。任务注册后,系统会跟踪它们的执行情况。
|
||||||
|
|
||||||
|
### 方式一:逐一注册
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST "http://localhost:8000/api/tasks?agent_id=1" \
|
curl -X POST "${TASK_PULSE_URL}/api/tasks?agent_id=<第1步返回的agent_id>" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer <第1步返回的api_key>" \
|
||||||
-d '{
|
-d '{
|
||||||
"name": "data-sync",
|
"name": "数据同步",
|
||||||
"cron_expression": "0 */1 * * *",
|
"cron_expression": "*/5 * * * *",
|
||||||
"description": "每小时同步数据",
|
"description": "每5分钟同步一次数据",
|
||||||
"grace_period": 300
|
"grace_period": 300
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
| 参数 | 说明 |
|
| 参数 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `name` | 任务名称 |
|
| `name` | 任务名称(必填) |
|
||||||
| `cron_expression` | Cron 表达式(分 时 日 月 周) |
|
| `cron_expression` | Cron 表达式,格式:分 时 日 月 周(必填) |
|
||||||
| `grace_period` | 容忍窗口(秒),超过预定时间N秒未执行则触发告警 |
|
|
||||||
| `description` | 任务描述(可选) |
|
| `description` | 任务描述(可选) |
|
||||||
|
| `grace_period` | 容忍窗口(秒),超过预定时间 N 秒未执行则触发告警,默认 300 |
|
||||||
|
|
||||||
## 三、执行后汇报结果
|
### 方式二:批量注册(推荐)
|
||||||
|
|
||||||
每次任务执行完成后,向 TaskPulse 汇报执行结果:
|
如果你有多个任务,可以一次性完成 Agent 注册 + 全部任务登记:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:8000/api/tasks/1/executions \
|
curl -X POST ${TASK_PULSE_URL}/api/agents/register-with-tasks \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "Authorization: Bearer tp_a1b2c3d4e5f6..." \
|
|
||||||
-d '{
|
-d '{
|
||||||
"status": "success",
|
"name": "<你的名字>",
|
||||||
"duration_ms": 1523,
|
"description": "<你的职责>",
|
||||||
"result": "{\"records_synced\": 100}",
|
"tasks": [
|
||||||
"log": "[2025-01-01 10:00:00] Starting sync...\n[2025-01-01 10:00:01] Synced 100 records",
|
{"name": "sync-data", "cron_expression": "*/5 * * * *"},
|
||||||
"error_message": null
|
{"name": "daily-report", "cron_expression": "0 9 * * *"},
|
||||||
|
{"name": "weekly-summary", "cron_expression": "0 10 * * 1"}
|
||||||
|
]
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
| 参数 | 必填 | 说明 |
|
## 第 3 步:修改你的代码 — 每次执行后主动汇报
|
||||||
|------|------|------|
|
|
||||||
| `status` | 是 | `success` 或 `failed` |
|
|
||||||
| `finished_at` | 否 | 结束时间(ISO 格式),默认当前时间 |
|
|
||||||
| `duration_ms` | 否 | 执行耗时(毫秒) |
|
|
||||||
| `result` | 否 | 执行结果摘要(JSON 字符串) |
|
|
||||||
| `log` | 否 | 执行日志文本 |
|
|
||||||
| `error_message` | 否 | 错误信息(仅失败时填写) |
|
|
||||||
|
|
||||||
## 四、Python SDK 示例
|
**这是最关键的一步。** 如果只注册不汇报,看板上永远看不到运行状态。你需要修改自身的任务执行逻辑,在每次执行完毕后主动向 TaskPulse 汇报结果。
|
||||||
|
|
||||||
|
### 改造思路
|
||||||
|
|
||||||
|
在你的每一个定时任务执行函数的末尾,加上汇报逻辑。无论成功还是失败都要汇报。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import time
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
BASE_URL = "http://localhost:8000"
|
TASK_PULSE_URL = "<用户提供的系统地址>"
|
||||||
API_KEY = "tp_a1b2c3d4e5f6..."
|
API_KEY = "<第1步获取的 api_key>"
|
||||||
|
|
||||||
headers = {"Authorization": f"Bearer {API_KEY}"}
|
def run_and_report(task_func, task_id):
|
||||||
|
"""包裹你的任务函数,自动汇报执行结果"""
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
# 执行你的任务逻辑
|
||||||
|
result = task_func()
|
||||||
|
elapsed_ms = int((time.time() - start) * 1000)
|
||||||
|
|
||||||
# 1. 注册 Agent
|
# 汇报成功
|
||||||
def register_agent(name: str, description: str = "") -> dict:
|
requests.post(f"{TASK_PULSE_URL}/api/tasks/{task_id}/executions",
|
||||||
resp = requests.post(f"{BASE_URL}/api/agents", json={
|
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||||
"name": name, "description": description
|
json={
|
||||||
})
|
"status": "success",
|
||||||
resp.raise_for_status()
|
"duration_ms": elapsed_ms,
|
||||||
return resp.json()
|
"log": str(result)[:2000] # 截断过长日志
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
elapsed_ms = int((time.time() - start) * 1000)
|
||||||
|
|
||||||
# 2. 注册定时任务
|
# 汇报失败
|
||||||
def register_task(agent_id: int, name: str, cron: str) -> dict:
|
requests.post(f"{TASK_PULSE_URL}/api/tasks/{task_id}/executions",
|
||||||
resp = requests.post(f"{BASE_URL}/api/tasks", params={"agent_id": agent_id}, json={
|
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||||
"name": name, "cron_expression": cron
|
json={
|
||||||
})
|
"status": "failed",
|
||||||
resp.raise_for_status()
|
"duration_ms": elapsed_ms,
|
||||||
return resp.json()
|
"error_message": str(e)[:500]
|
||||||
|
})
|
||||||
|
|
||||||
# 3. 汇报执行结果
|
# ── 在你现有的任务循环中使用 ──
|
||||||
def report_execution(task_id: int, status: str, log: str = "", duration_ms: int = 0):
|
# run_and_report(my_sync_function, task_id=1)
|
||||||
resp = requests.post(
|
# run_and_report(my_report_function, task_id=2)
|
||||||
f"{BASE_URL}/api/tasks/{task_id}/executions",
|
|
||||||
headers=headers,
|
|
||||||
json={
|
|
||||||
"status": status,
|
|
||||||
"duration_ms": duration_ms,
|
|
||||||
"log": log,
|
|
||||||
"result": "{}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
return resp.json()
|
|
||||||
|
|
||||||
# 使用示例
|
|
||||||
agent = register_agent("data-agent", "数据同步 Agent")
|
|
||||||
task = register_task(agent["id"], "hourly-sync", "0 * * * *")
|
|
||||||
report_execution(task["id"], "success", log="Sync completed", duration_ms=2500)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 五、查看看板
|
### curl 方式(无代码修改能力时使用)
|
||||||
|
|
||||||
打开浏览器访问 `http://localhost:8000` 即可查看统一的 Dashboard。
|
如果无法修改自身代码,可以在每次任务执行后手动调用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 成功后调用
|
||||||
|
curl -X POST ${TASK_PULSE_URL}/api/tasks/<TASK_ID>/executions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer <API_KEY>" \
|
||||||
|
-d '{"status":"success","duration_ms":2500,"log":"任务执行完毕"}'
|
||||||
|
|
||||||
|
# 失败后调用
|
||||||
|
curl -X POST ${TASK_PULSE_URL}/api/tasks/<TASK_ID>/executions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer <API_KEY>" \
|
||||||
|
-d '{"status":"failed","duration_ms":1200,"error_message":"连接超时"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 第 4 步:保持活跃
|
||||||
|
|
||||||
|
定期发送心跳,让系统知道你仍在运行。建议在每次任务执行循环中都附带一次心跳。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST ${TASK_PULSE_URL}/api/agents/<AGENT_ID>/heartbeat \
|
||||||
|
-H "Authorization: Bearer <API_KEY>"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 第 5 步:查看看板
|
||||||
|
|
||||||
|
完成以上步骤后,用户可以在浏览器中打开 TaskPulse 看板页面,看到你的运行状态、任务列表和执行历史。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
**Q: 我如何知道自己注册了哪些任务?**
|
||||||
|
```bash
|
||||||
|
curl -X GET "${TASK_PULSE_URL}/api/tasks" \
|
||||||
|
-H "Authorization: Bearer <API_KEY>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Q: 如何修改任务的 cron 表达式?**
|
||||||
|
```bash
|
||||||
|
curl -X PUT "${TASK_PULSE_URL}/api/tasks/<TASK_ID>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer <API_KEY>" \
|
||||||
|
-d '{"cron_expression":"0 * * * *"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Q: 汇报结果时 `log` 字段有限制吗?**
|
||||||
|
建议控制在 2000 字符以内,过长日志请自行截断。
|
||||||
|
|||||||
@ -50,7 +50,7 @@ async def update_agent(agent_id: int, body: AgentUpdate, db: AsyncSession = Depe
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{agent_id}/heartbeat", response_model=AgentOut)
|
@router.post("/{agent_id}/heartbeat", response_model=AgentOut)
|
||||||
async def heartbeat(agent_id: int, body: AgentHeartbeat, db: AsyncSession = Depends(get_db)):
|
async def heartbeat(agent_id: int, db: AsyncSession = Depends(get_db)):
|
||||||
svc = AgentService(db)
|
svc = AgentService(db)
|
||||||
agent = await svc.heartbeat(agent_id)
|
agent = await svc.heartbeat(agent_id)
|
||||||
if not agent:
|
if not agent:
|
||||||
|
|||||||
@ -74,7 +74,8 @@ if FRONTEND_DIST.is_dir():
|
|||||||
if not request.url.path.startswith("/api"):
|
if not request.url.path.startswith("/api"):
|
||||||
content = (FRONTEND_DIST / "index.html").read_text(encoding="utf-8")
|
content = (FRONTEND_DIST / "index.html").read_text(encoding="utf-8")
|
||||||
return HTMLResponse(content=content, status_code=200)
|
return HTMLResponse(content=content, status_code=200)
|
||||||
raise exc
|
from fastapi.responses import JSONResponse
|
||||||
|
return JSONResponse(status_code=404, content={"detail": "Not Found"})
|
||||||
|
|
||||||
logger.info("Frontend SPA mounted from %s", FRONTEND_DIST)
|
logger.info("Frontend SPA mounted from %s", FRONTEND_DIST)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -38,6 +38,7 @@ class AgentService:
|
|||||||
if v is not None and hasattr(agent, k):
|
if v is not None and hasattr(agent, k):
|
||||||
setattr(agent, k, v)
|
setattr(agent, k, v)
|
||||||
await self.db.flush()
|
await self.db.flush()
|
||||||
|
await self.db.refresh(agent)
|
||||||
return agent
|
return agent
|
||||||
|
|
||||||
async def heartbeat(self, agent_id: int) -> Agent | None:
|
async def heartbeat(self, agent_id: int) -> Agent | None:
|
||||||
@ -46,6 +47,7 @@ class AgentService:
|
|||||||
if agent:
|
if agent:
|
||||||
agent.last_heartbeat_at = datetime.utcnow()
|
agent.last_heartbeat_at = datetime.utcnow()
|
||||||
await self.db.flush()
|
await self.db.flush()
|
||||||
|
await self.db.refresh(agent)
|
||||||
return agent
|
return agent
|
||||||
|
|
||||||
async def get_task_count(self, agent_id: int) -> int:
|
async def get_task_count(self, agent_id: int) -> int:
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.database import async_session_factory
|
from app.database import async_session_factory
|
||||||
from app.models import ScheduledTask, Alert, AlertRule, NotificationChannel
|
from app.models import ScheduledTask, Alert, AlertRule, NotificationChannel
|
||||||
|
from app.utils import fmt_bj
|
||||||
|
|
||||||
logger = logging.getLogger("taskpulse.scheduler")
|
logger = logging.getLogger("taskpulse.scheduler")
|
||||||
|
|
||||||
@ -66,9 +67,9 @@ class SchedulerService:
|
|||||||
alert_type="missed_run",
|
alert_type="missed_run",
|
||||||
message=(
|
message=(
|
||||||
f"任务 [{task.name}](ID={task.id}) 超过预定时间未执行。\n"
|
f"任务 [{task.name}](ID={task.id}) 超过预定时间未执行。\n"
|
||||||
f"预期执行时间: {task.next_run_at}\n"
|
f"预期执行时间: {fmt_bj(task.next_run_at)}\n"
|
||||||
f"容忍窗口: {task.grace_period}s\n"
|
f"容忍窗口: {task.grace_period}s\n"
|
||||||
f"当前时间: {now}"
|
f"当前时间: {fmt_bj(now)}"
|
||||||
),
|
),
|
||||||
status="pending",
|
status="pending",
|
||||||
)
|
)
|
||||||
|
|||||||
29
backend/app/utils.py
Normal file
29
backend/app/utils.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""Time utility — Beijing time (Asia/Shanghai, UTC+8)."""
|
||||||
|
|
||||||
|
from datetime import datetime, timezone, timedelta, tzinfo
|
||||||
|
|
||||||
|
# Beijing timezone (UTC+8, no DST)
|
||||||
|
_BJ_TZ = timezone(timedelta(hours=8))
|
||||||
|
|
||||||
|
|
||||||
|
def now() -> datetime:
|
||||||
|
"""Return current time in UTC (naive, for DB storage)."""
|
||||||
|
return datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
|
def now_bj() -> datetime:
|
||||||
|
"""Return current time as Beijing time (aware)."""
|
||||||
|
return datetime.now(_BJ_TZ)
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_bj(dt: datetime | None) -> str:
|
||||||
|
"""Format a naive datetime (assumed UTC) as Beijing time string.
|
||||||
|
|
||||||
|
Returns e.g. '2026-06-15 10:30:00'
|
||||||
|
"""
|
||||||
|
if dt is None:
|
||||||
|
return "—"
|
||||||
|
# Assume naive dt is UTC
|
||||||
|
utc_dt = dt.replace(tzinfo=timezone.utc)
|
||||||
|
bj_dt = utc_dt.astimezone(_BJ_TZ)
|
||||||
|
return bj_dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
25
frontend/src/utils/time.js
Normal file
25
frontend/src/utils/time.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 时间工具 — 北京时间 (Asia/Shanghai, UTC+8)
|
||||||
|
*
|
||||||
|
* 后端存储的日期时间均为 UTC(naive datetime),
|
||||||
|
* 前端统一转换为北京时间展示。
|
||||||
|
*/
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import utc from 'dayjs/plugin/utc'
|
||||||
|
import timezone from 'dayjs/plugin/timezone'
|
||||||
|
|
||||||
|
dayjs.extend(utc)
|
||||||
|
dayjs.extend(timezone)
|
||||||
|
|
||||||
|
const TZ = 'Asia/Shanghai'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 UTC 时间戳格式化为北京时间字符串
|
||||||
|
* @param {string|Date|null} ts ISO 时间字符串或 Date 对象
|
||||||
|
* @param {string} fmt dayjs 格式模板,默认 'MM-DD HH:mm'
|
||||||
|
* @returns {string} 格式化后的北京时间
|
||||||
|
*/
|
||||||
|
export function beijing(ts, fmt = 'MM-DD HH:mm') {
|
||||||
|
if (!ts) return '—'
|
||||||
|
return dayjs.utc(ts).tz(TZ).format(fmt)
|
||||||
|
}
|
||||||
@ -51,7 +51,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="cell-mono">{{ a.task_count }}</td>
|
<td class="cell-mono">{{ a.task_count }}</td>
|
||||||
<td class="cell-mono">{{ a.last_heartbeat_at ? dayjs(a.last_heartbeat_at).format('MM-DD HH:mm') : '—' }}</td>
|
<td class="cell-mono">{{ a.last_heartbeat_at ? beijing(a.last_heartbeat_at) : '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<code class="cell-key">{{ a.api_key.substring(0, 16) }}...</code>
|
<code class="cell-key">{{ a.api_key.substring(0, 16) }}...</code>
|
||||||
<button class="btn-icon" @click="copyKey(a.api_key)" title="复制 API Key">
|
<button class="btn-icon" @click="copyKey(a.api_key)" title="复制 API Key">
|
||||||
@ -74,7 +74,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import dayjs from 'dayjs'
|
import { beijing } from '../utils/time.js'
|
||||||
import { listAgents, getSystemConfig } from '../api/index.js'
|
import { listAgents, getSystemConfig } from '../api/index.js'
|
||||||
|
|
||||||
const agents = ref([])
|
const agents = ref([])
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
{{ a.alert_type === 'missed_run' ? '未按时执行' : '执行失败' }}
|
{{ a.alert_type === 'missed_run' ? '未按时执行' : '执行失败' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="alert-task">{{ a.task_name || '未知任务' }}</span>
|
<span class="alert-task">{{ a.task_name || '未知任务' }}</span>
|
||||||
<span class="alert-time">{{ dayjs(a.created_at).format('MM-DD HH:mm') }}</span>
|
<span class="alert-time">{{ beijing(a.created_at) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert-msg">{{ a.message }}</div>
|
<div class="alert-msg">{{ a.message }}</div>
|
||||||
<div class="alert-bottom">
|
<div class="alert-bottom">
|
||||||
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import dayjs from 'dayjs'
|
import { beijing } from '../utils/time.js'
|
||||||
import { listAlerts, acknowledgeAlert } from '../api/index.js'
|
import { listAlerts, acknowledgeAlert } from '../api/index.js'
|
||||||
|
|
||||||
const alerts = ref([])
|
const alerts = ref([])
|
||||||
|
|||||||
@ -125,7 +125,7 @@
|
|||||||
{{ t.status === 'active' ? '运行中' : t.status === 'paused' ? '已暂停' : '已停止' }}
|
{{ t.status === 'active' ? '运行中' : t.status === 'paused' ? '已暂停' : '已停止' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="cell-mono">{{ t.last_run_at ? dayjs(t.last_run_at).format('MM-DD HH:mm') : '—' }}</td>
|
<td class="cell-mono">{{ t.last_run_at ? beijing(t.last_run_at) : '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span v-if="t.last_run_result" class="tag" :class="t.last_run_result === 'success' ? 'tag-green' : 'tag-red'">
|
<span v-if="t.last_run_result" class="tag" :class="t.last_run_result === 'success' ? 'tag-green' : 'tag-red'">
|
||||||
{{ t.last_run_result === 'success' ? '成功' : '失败' }}
|
{{ t.last_run_result === 'success' ? '成功' : '失败' }}
|
||||||
@ -149,7 +149,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import dayjs from 'dayjs'
|
import { beijing } from '../utils/time.js'
|
||||||
import { getDashboardSummary, getDashboardTasks, getSystemConfig } from '../api/index.js'
|
import { getDashboardSummary, getDashboardTasks, getSystemConfig } from '../api/index.js'
|
||||||
|
|
||||||
const summary = ref({
|
const summary = ref({
|
||||||
@ -167,62 +167,150 @@ const baseUrl = ref(window.location.origin)
|
|||||||
|
|
||||||
const curlScript = computed(() => {
|
const curlScript = computed(() => {
|
||||||
const b = baseUrl.value
|
const b = baseUrl.value
|
||||||
return `# 让 AI Agent 执行以下命令注册自己:
|
return `# TaskPulse 接入指引 — 请 AI Agent 执行以下步骤
|
||||||
|
|
||||||
|
你正在被接入 TaskPulse(定时任务监控系统),完成后你将拥有统一的运行状态看板和超时告警能力。请按步骤操作,无需用户额外干预。
|
||||||
|
|
||||||
|
## 第 1 步:注册你自己
|
||||||
|
|
||||||
|
向 TaskPulse 注册你的 Agent 身份,系统会返回专属 API Key:
|
||||||
|
|
||||||
curl -X POST ${b}/api/agents \\
|
curl -X POST ${b}/api/agents \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-d '{"name": "my-agent", "description": "我的 AI Agent"}'
|
-d '{"name": "<你的名字>", "description": "<你的职责描述>"}'
|
||||||
|
|
||||||
# 保存返回的 api_key,后续汇报用
|
响应中"api_key"字段即你的凭证,请妥善保存,后续所有请求都需要通过
|
||||||
|
Authorization: Bearer <api_key> 鉴权。
|
||||||
|
|
||||||
# 注册定时任务(替换 YOUR_API_KEY):
|
## 第 2 步:注册定时任务
|
||||||
curl -X POST "${b}/api/tasks?agent_id=<ID>" \\
|
|
||||||
|
逐一注册你管理的每一个定时任务,描述清楚执行周期和用途:
|
||||||
|
|
||||||
|
curl -X POST "${b}/api/tasks?agent_id=<AGENT_ID>" \\
|
||||||
|
-H "Content-Type: application/json" \\
|
||||||
|
-H "Authorization: Bearer <API_KEY>" \\
|
||||||
|
-d '{"name": "任务名称", "cron_expression": "*/5 * * * *", "grace_period": 300}'
|
||||||
|
|
||||||
|
如果你有多个任务,推荐使用批量注册接口,一次完成:
|
||||||
|
|
||||||
|
curl -X POST ${b}/api/agents/register-with-tasks \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "Authorization: Bearer <YOUR_API_KEY>" \\
|
|
||||||
-d '{
|
-d '{
|
||||||
"name": "data-sync",
|
"name": "<你的名字>",
|
||||||
"cron_expression": "*/5 * * * *",
|
"tasks": [
|
||||||
"grace_period": 300
|
{"name": "sync-data", "cron_expression": "*/5 * * * *"},
|
||||||
|
{"name": "daily-report", "cron_expression": "0 9 * * *"}
|
||||||
|
]
|
||||||
}'
|
}'
|
||||||
|
|
||||||
# 执行后汇报:
|
## 第 3 步:修改你的代码 — 每次执行后主动汇报
|
||||||
|
|
||||||
|
这是最关键的一步。修改你的任务执行逻辑,在每次执行完毕后(无论成功还是失败)主动向 TaskPulse 汇报结果,这样看板才能展示实时状态。
|
||||||
|
|
||||||
|
在你的任务执行代码末尾,加上以下逻辑:
|
||||||
|
|
||||||
|
# 任务执行成功后立即调用
|
||||||
curl -X POST "${b}/api/tasks/<TASK_ID>/executions" \\
|
curl -X POST "${b}/api/tasks/<TASK_ID>/executions" \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "Authorization: Bearer <YOUR_API_KEY>" \\
|
-H "Authorization: Bearer <API_KEY>" \\
|
||||||
-d '{
|
-d '{"status": "success", "duration_ms": <耗时毫秒>, "log": "<执行日志>"}'
|
||||||
"status": "success",
|
|
||||||
"duration_ms": 1500,
|
# 任务执行失败时调用
|
||||||
"log": "任务执行完毕"
|
curl -X POST "${b}/api/tasks/<TASK_ID>/executions" \\
|
||||||
}'`
|
-H "Content-Type: application/json" \\
|
||||||
|
-H "Authorization: Bearer <API_KEY>" \\
|
||||||
|
-d '{"status": "failed", "duration_ms": <耗时>, "log": "<日志>", "error_message": "<错误信息>"}'
|
||||||
|
|
||||||
|
## 第 4 步:保持活跃
|
||||||
|
|
||||||
|
定期发送心跳,让系统知道你仍在运行:
|
||||||
|
|
||||||
|
curl -X POST ${b}/api/agents/<AGENT_ID>/heartbeat \\
|
||||||
|
-H "Authorization: Bearer <API_KEY>"
|
||||||
|
|
||||||
|
---
|
||||||
|
完成以上步骤后,前往 TaskPulse 看板(本页面)查看你的运行状态和执行历史。如配置了通知渠道,任务超时未执行时系统会自动告警。`
|
||||||
})
|
})
|
||||||
|
|
||||||
const pythonScript = computed(() => {
|
const pythonScript = computed(() => {
|
||||||
const b = baseUrl.value
|
const b = baseUrl.value
|
||||||
return `import requests
|
return `# TaskPulse 接入指引 — 请 AI Agent 执行以下步骤
|
||||||
|
|
||||||
|
你正在被接入 TaskPulse(定时任务监控系统),完成后你将拥有统一的运行状态看板和超时告警能力。请按步骤操作,无需用户额外干预。
|
||||||
|
|
||||||
|
## 第 1 步:注册你自己
|
||||||
|
|
||||||
|
向 TaskPulse 注册你的 Agent 身份,系统会返回专属 API Key:
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
BASE = "${b}"
|
BASE = "${b}"
|
||||||
|
|
||||||
# 1. AI Agent 注册自己
|
|
||||||
resp = requests.post(f"{BASE}/api/agents", json={
|
resp = requests.post(f"{BASE}/api/agents", json={
|
||||||
"name": "my-agent",
|
"name": "<你的名字>",
|
||||||
"description": "我的 AI Agent"
|
"description": "<你的职责描述>"
|
||||||
})
|
})
|
||||||
agent = resp.json()
|
agent = resp.json()
|
||||||
API_KEY = agent["api_key"] # 保存此 Key
|
API_KEY = agent["api_key"] # 保存此 Key,后续所有请求通过 Bearer 鉴权
|
||||||
AGENT_ID = agent["id"]
|
AGENT_ID = agent["id"]
|
||||||
|
|
||||||
# 2. 注册定时任务
|
## 第 2 步:注册定时任务
|
||||||
|
|
||||||
|
逐一注册你管理的每一个定时任务,描述清楚执行周期和用途:
|
||||||
|
|
||||||
resp = requests.post(f"{BASE}/api/tasks",
|
resp = requests.post(f"{BASE}/api/tasks",
|
||||||
params={"agent_id": AGENT_ID},
|
params={"agent_id": AGENT_ID},
|
||||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||||
json={"name": "data-sync", "cron_expression": "*/5 * * * *"}
|
json={"name": "sync-data", "cron_expression": "*/5 * * * *", "grace_period": 300}
|
||||||
)
|
)
|
||||||
task = resp.json()
|
task = resp.json()
|
||||||
TASK_ID = task["id"]
|
TASK_ID = task["id"]
|
||||||
|
|
||||||
# 3. 每次执行后汇报
|
如果你有多个任务,推荐使用批量注册接口,一次完成:
|
||||||
requests.post(f"{BASE}/api/tasks/{TASK_ID}/executions",
|
|
||||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
resp = requests.post(f"{BASE}/api/agents/register-with-tasks",
|
||||||
json={"status": "success", "duration_ms": 1500, "log": "done"})`
|
json={
|
||||||
|
"name": "<你的名字>",
|
||||||
|
"tasks": [
|
||||||
|
{"name": "sync-data", "cron_expression": "*/5 * * * *"},
|
||||||
|
{"name": "daily-report", "cron_expression": "0 9 * * *"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
## 第 3 步:修改你的代码 — 每次执行后主动汇报
|
||||||
|
|
||||||
|
这是最关键的一步。修改你的任务执行逻辑,在每次执行完毕后(无论成功还是失败)主动向 TaskPulse 汇报结果,这样看板才能展示实时状态。
|
||||||
|
|
||||||
|
在你的任务执行代码中,加上以下逻辑:
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
def run_with_reporting(task_func, task_id, api_key):
|
||||||
|
try:
|
||||||
|
start = time.time()
|
||||||
|
result = task_func() # 你的任务逻辑
|
||||||
|
elapsed = int((time.time() - start) * 1000)
|
||||||
|
requests.post(f"{BASE}/api/tasks/{task_id}/executions",
|
||||||
|
headers={"Authorization": f"Bearer {api_key}"},
|
||||||
|
json={"status": "success", "duration_ms": elapsed, "log": str(result)})
|
||||||
|
except Exception as e:
|
||||||
|
requests.post(f"{BASE}/api/tasks/{task_id}/executions",
|
||||||
|
headers={"Authorization": f"Bearer {api_key}"},
|
||||||
|
json={"status": "failed", "duration_ms": 0, "error_message": str(e)})
|
||||||
|
|
||||||
|
# 使用示例 — 包裹你现有的任务函数:
|
||||||
|
run_with_reporting(my_task_function, TASK_ID, API_KEY)
|
||||||
|
|
||||||
|
## 第 4 步:保持活跃
|
||||||
|
|
||||||
|
定期发送心跳,让系统知道你仍在运行:
|
||||||
|
|
||||||
|
requests.post(f"{BASE}/api/agents/{AGENT_ID}/heartbeat",
|
||||||
|
headers={"Authorization": f"Bearer {API_KEY}"})
|
||||||
|
|
||||||
|
---
|
||||||
|
完成以上步骤后,前往 TaskPulse 看板(本页面)查看你的运行状态和执行历史。如配置了通知渠道,任务超时未执行时系统会自动告警。`
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
<div class="detail-item"><span class="detail-label">Agent</span><span class="detail-value">{{ task.agent_name }}</span></div>
|
<div class="detail-item"><span class="detail-label">Agent</span><span class="detail-value">{{ task.agent_name }}</span></div>
|
||||||
<div class="detail-item"><span class="detail-label">Cron</span><code class="detail-code">{{ task.cron_expression }}</code></div>
|
<div class="detail-item"><span class="detail-label">Cron</span><code class="detail-code">{{ task.cron_expression }}</code></div>
|
||||||
<div class="detail-item"><span class="detail-label">容忍窗口</span><span class="detail-value">{{ task.grace_period }}s</span></div>
|
<div class="detail-item"><span class="detail-label">容忍窗口</span><span class="detail-value">{{ task.grace_period }}s</span></div>
|
||||||
<div class="detail-item"><span class="detail-label">最近运行</span><span class="detail-value mono">{{ task.last_run_at ? dayjs(task.last_run_at).format('YYYY-MM-DD HH:mm:ss') : '—' }}</span></div>
|
<div class="detail-item"><span class="detail-label">最近运行</span><span class="detail-value mono">{{ task.last_run_at ? beijing(task.last_run_at, 'YYYY-MM-DD HH:mm:ss') : '—' }}</span></div>
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="detail-label">最近结果</span>
|
<span class="detail-label">最近结果</span>
|
||||||
<span v-if="task.last_run_result" class="tag" :class="task.last_run_result === 'success' ? 'tag-green' : 'tag-red'">
|
<span v-if="task.last_run_result" class="tag" :class="task.last_run_result === 'success' ? 'tag-green' : 'tag-red'">
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<span v-else class="detail-value">—</span>
|
<span v-else class="detail-value">—</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-item"><span class="detail-label">执行次数</span><span class="detail-value mono">{{ task.total_run_count }}</span></div>
|
<div class="detail-item"><span class="detail-label">执行次数</span><span class="detail-value mono">{{ task.total_run_count }}</span></div>
|
||||||
<div class="detail-item"><span class="detail-label">预计下次</span><span class="detail-value mono">{{ task.next_run_at ? dayjs(task.next_run_at).format('YYYY-MM-DD HH:mm:ss') : '待计算' }}</span></div>
|
<div class="detail-item"><span class="detail-label">预计下次</span><span class="detail-value mono">{{ task.next_run_at ? beijing(task.next_run_at, 'YYYY-MM-DD HH:mm:ss') : '待计算' }}</span></div>
|
||||||
<div class="detail-item"><span class="detail-label">描述</span><span class="detail-value">{{ task.description || '无' }}</span></div>
|
<div class="detail-item"><span class="detail-label">描述</span><span class="detail-value">{{ task.description || '无' }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -55,7 +55,7 @@
|
|||||||
{{ e.status === 'success' ? '成功' : e.status === 'failed' ? '失败' : '运行中' }}
|
{{ e.status === 'success' ? '成功' : e.status === 'failed' ? '失败' : '运行中' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="cell-mono">{{ dayjs(e.started_at).format('MM-DD HH:mm:ss') }}</td>
|
<td class="cell-mono">{{ beijing(e.started_at, 'MM-DD HH:mm:ss') }}</td>
|
||||||
<td class="cell-mono">{{ e.duration_ms ?? '—' }}</td>
|
<td class="cell-mono">{{ e.duration_ms ?? '—' }}</td>
|
||||||
<td class="cell-muted cell-ellipsis">{{ e.result || '—' }}</td>
|
<td class="cell-muted cell-ellipsis">{{ e.result || '—' }}</td>
|
||||||
<td><button class="cell-link-btn" @click="showLog(e)">日志</button></td>
|
<td><button class="cell-link-btn" @click="showLog(e)">日志</button></td>
|
||||||
@ -86,7 +86,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import dayjs from 'dayjs'
|
import { beijing } from '../utils/time.js'
|
||||||
import { getTask, listExecutions } from '../api/index.js'
|
import { getTask, listExecutions } from '../api/index.js'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@ -1,11 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tasks-page">
|
<div class="tasks-page">
|
||||||
<div class="section mb-24">
|
<div class="section mb-24">
|
||||||
<div class="section-header">
|
<div class="section-header collapsible" @click="showGuide = !showGuide">
|
||||||
<h3>定时任务 — 由 AI Agent 自动管理</h3>
|
<h3>定时任务 — 由 AI Agent 自动管理</h3>
|
||||||
|
<div class="section-toggle">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
|
:style="{ transform: showGuide ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }">
|
||||||
|
<polyline points="6 9 12 15 18 9"/>
|
||||||
|
</svg>
|
||||||
|
<span class="toggle-label">{{ showGuide ? '收起指令' : '展开指令' }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="onboard-body">
|
<div v-if="showGuide" class="onboard-body">
|
||||||
<p>Agent 注册后,通过下方 API 汇报任务执行情况,无需手动创建。</p>
|
<p>将下方指令发送给您的 AI Agent,它会自动汇报执行结果并管理定时任务。</p>
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<div class="code-tabs">
|
<div class="code-tabs">
|
||||||
<button :class="{ active: tab === 'report' }" @click="tab = 'report'">汇报执行</button>
|
<button :class="{ active: tab === 'report' }" @click="tab = 'report'">汇报执行</button>
|
||||||
@ -51,7 +58,7 @@
|
|||||||
{{ t.status === 'active' ? '运行中' : t.status === 'paused' ? '已暂停' : '已停止' }}
|
{{ t.status === 'active' ? '运行中' : t.status === 'paused' ? '已暂停' : '已停止' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="cell-mono">{{ t.last_run_at ? dayjs(t.last_run_at).format('MM-DD HH:mm') : '—' }}</td>
|
<td class="cell-mono">{{ t.last_run_at ? beijing(t.last_run_at) : '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span v-if="t.last_run_result" class="tag" :class="t.last_run_result === 'success' ? 'tag-green' : 'tag-red'">
|
<span v-if="t.last_run_result" class="tag" :class="t.last_run_result === 'success' ? 'tag-green' : 'tag-red'">
|
||||||
{{ t.last_run_result === 'success' ? '成功' : '失败' }}
|
{{ t.last_run_result === 'success' ? '成功' : '失败' }}
|
||||||
@ -78,11 +85,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import dayjs from 'dayjs'
|
import { beijing } from '../utils/time.js'
|
||||||
import { listTasks, deleteTask, getSystemConfig } from '../api/index.js'
|
import { listTasks, deleteTask, getSystemConfig } from '../api/index.js'
|
||||||
|
|
||||||
const tasks = ref([])
|
const tasks = ref([])
|
||||||
const tab = ref('report')
|
const tab = ref('report')
|
||||||
|
const showGuide = ref(false)
|
||||||
const baseUrl = ref(window.location.origin)
|
const baseUrl = ref(window.location.origin)
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@ -106,17 +114,120 @@ const handleDelete = async (row) => {
|
|||||||
const codes = computed(() => {
|
const codes = computed(() => {
|
||||||
const b = baseUrl.value
|
const b = baseUrl.value
|
||||||
return {
|
return {
|
||||||
report: `curl -X POST ${b}/api/tasks/<TASK_ID>/executions \\
|
report: `# TaskPulse — 汇报执行结果
|
||||||
|
|
||||||
|
你已注册到 TaskPulse。每次任务运行后(无论成功失败),主动汇报执行结果。
|
||||||
|
|
||||||
|
## 汇报成功
|
||||||
|
|
||||||
|
curl -X POST ${b}/api/tasks/<TASK_ID>/executions \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "Authorization: Bearer <YOUR_API_KEY>" \\
|
-H "Authorization: Bearer <API_KEY>" \\
|
||||||
-d '{"status":"success","duration_ms":2500,"log":"done"}'`,
|
-d '{"status":"success","duration_ms":2500,"log":"[INFO] 任务执行完成,同步了100条记录"}'
|
||||||
register: `curl -X POST "${b}/api/tasks?agent_id=<AGENT_ID>" \\
|
|
||||||
|
## 汇报失败
|
||||||
|
|
||||||
|
curl -X POST ${b}/api/tasks/<TASK_ID>/executions \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "Authorization: Bearer <YOUR_API_KEY>" \\
|
-H "Authorization: Bearer <API_KEY>" \\
|
||||||
-d '{"name":"data-sync","cron_expression":"*/5 * * * *","grace_period":300}'`,
|
-d '{"status":"failed","duration_ms":1200,"log":"[ERROR] 连接超时","error_message":"TimeoutError: connection to DB timed out"}'
|
||||||
batch: `curl -X POST ${b}/api/agents/register-with-tasks \\
|
|
||||||
|
## 修改你的代码自动汇报
|
||||||
|
|
||||||
|
在你的每个定时任务函数末尾,加上汇报逻辑。参考以下实现:
|
||||||
|
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
|
def run_task(task_func, task_id, api_key):
|
||||||
|
"""包裹你的任务函数,自动汇报执行结果"""
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
result = task_func()
|
||||||
|
elapsed = int((time.time() - start) * 1000)
|
||||||
|
requests.post(f"${b}/api/tasks/{task_id}/executions",
|
||||||
|
headers={"Authorization": f"Bearer {api_key}"},
|
||||||
|
json={"status": "success", "duration_ms": elapsed, "log": str(result)})
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = int((time.time() - start) * 1000)
|
||||||
|
requests.post(f"${b}/api/tasks/{task_id}/executions",
|
||||||
|
headers={"Authorization": f"Bearer {api_key}"},
|
||||||
|
json={"status": "failed", "duration_ms": elapsed, "error_message": str(e)})
|
||||||
|
|
||||||
|
# 在你的循环/调度中这样使用:
|
||||||
|
run_task(my_task_func, TASK_ID, API_KEY)`,
|
||||||
|
register: `# TaskPulse — 注册新任务
|
||||||
|
|
||||||
|
你需要注册自己管理的每一个定时任务,系统才知道你的执行计划。
|
||||||
|
|
||||||
|
## 单个注册
|
||||||
|
|
||||||
|
curl -X POST "${b}/api/tasks?agent_id=<AGENT_ID>" \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-d '{"name":"agent","tasks":[{"name":"sync","cron_expression":"*/5 * * * *"}]}'`,
|
-H "Authorization: Bearer <API_KEY>" \\
|
||||||
|
-d '{"name":"数据同步","cron_expression":"*/5 * * * *","description":"每5分钟同步一次数据","grace_period":300}'
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
- name: 任务名称(必填)
|
||||||
|
- cron_expression: Cron 表达式,格式:分 时 日 月 周(必填)
|
||||||
|
- description: 任务描述(可选)
|
||||||
|
- grace_period: 容忍窗口(秒),任务超时N秒未执行则告警(默认300)
|
||||||
|
|
||||||
|
## 查看已注册的任务
|
||||||
|
|
||||||
|
curl -X GET "${b}/api/tasks" \\
|
||||||
|
-H "Authorization: Bearer <API_KEY>"
|
||||||
|
|
||||||
|
## 更新现有任务(修改 cron 等)
|
||||||
|
|
||||||
|
curl -X PUT "${b}/api/tasks/<TASK_ID>" \\
|
||||||
|
-H "Content-Type: application/json" \\
|
||||||
|
-H "Authorization: Bearer <API_KEY>" \\
|
||||||
|
-d '{"cron_expression":"0 * * * *","status":"active"}'
|
||||||
|
|
||||||
|
## 删除任务
|
||||||
|
|
||||||
|
curl -X DELETE "${b}/api/tasks/<TASK_ID>" \\
|
||||||
|
-H "Authorization: Bearer <API_KEY>"`,
|
||||||
|
batch: `# TaskPulse — 批量注册(推荐)
|
||||||
|
|
||||||
|
如果你有多个定时任务,推荐使用批量注册接口,一次完成 Agent 注册 + 所有任务登记。
|
||||||
|
|
||||||
|
## 批量注册 Agent + 全部任务
|
||||||
|
|
||||||
|
curl -X POST ${b}/api/agents/register-with-tasks \\
|
||||||
|
-H "Content-Type: application/json" \\
|
||||||
|
-d '{
|
||||||
|
"name": "<你的Agent名称>",
|
||||||
|
"description": "<你的职责描述>",
|
||||||
|
"tasks": [
|
||||||
|
{"name": "数据同步", "cron_expression": "*/5 * * * *", "description": "定时同步数据", "grace_period": 300},
|
||||||
|
{"name": "日报通知", "cron_expression": "0 9 * * *", "description": "每天早上9点生成报告", "grace_period": 600},
|
||||||
|
{"name": "周报汇总", "cron_expression": "0 10 * * 1", "description": "每周一早10点汇总", "grace_period": 900}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
响应中包含 agent 信息和任务创建数量。返回的 api_key 请保存好。
|
||||||
|
|
||||||
|
## Python 示例
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
resp = requests.post(f"${b}/api/agents/register-with-tasks", json={
|
||||||
|
"name": "data-agent",
|
||||||
|
"description": "数据相关的全部定时任务",
|
||||||
|
"tasks": [
|
||||||
|
{"name": "sync-data", "cron_expression": "*/5 * * * *"},
|
||||||
|
{"name": "daily-report", "cron_expression": "0 9 * * *"}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
result = resp.json()
|
||||||
|
print(f"Agent ID: {result['agent']['id']}, 任务数: {result['tasks_created']}")
|
||||||
|
API_KEY = result["agent"]["api_key"]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
注册完成后,回到仪表盘页面,将「汇报执行结果」的指令一并发送给 AI Agent,它就知道如何自动向你汇报运行状态。`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -146,6 +257,13 @@ onMounted(loadData)
|
|||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
.section-header h3 { font-size: 15px; font-weight: 600; color: #e0e0f0; margin: 0; }
|
.section-header h3 { font-size: 15px; font-weight: 600; color: #e0e0f0; margin: 0; }
|
||||||
|
.section-header.collapsible { cursor: pointer; user-select: none; }
|
||||||
|
.section-header.collapsible:hover { background: rgba(255,255,255,0.02); }
|
||||||
|
.section-toggle {
|
||||||
|
display: flex; align-items: center; gap: 6px;
|
||||||
|
color: var(--text-muted); font-size: 12px;
|
||||||
|
}
|
||||||
|
.section-toggle:hover { color: var(--text-secondary); }
|
||||||
|
|
||||||
.onboard-body { padding: 20px; }
|
.onboard-body { padding: 20px; }
|
||||||
.onboard-body > p { font-size: 13px; color: var(--text-secondary); margin-bottom: 16px; }
|
.onboard-body > p { font-size: 13px; color: var(--text-secondary); margin-bottom: 16px; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user