fix: dayjs UTC import, API pagination, execution list endpoint

This commit is contained in:
Steven 2026-06-20 12:09:00 +08:00
parent 4cdccf904a
commit 4215137f26
4 changed files with 106 additions and 12 deletions

View File

@ -1,15 +1,25 @@
"""API router — TaskExecution (reporting + query).""" """API router — TaskExecution (reporting + query)."""
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db from app.database import get_db
from app.schemas import ExecutionOut, ExecutionReport from app.schemas import ExecutionOut, ExecutionReport
from app.services import ExecutionService, TaskService, AgentService from app.services import ExecutionService, TaskService, AgentService
from app.models import TaskExecution
router = APIRouter(prefix="/api/tasks/{task_id}/executions", tags=["executions"]) router = APIRouter(prefix="/api/tasks/{task_id}/executions", tags=["executions"])
class PaginatedExecutions(BaseModel):
items: list[ExecutionOut]
total: int
page: int
page_size: int
@router.post("", response_model=ExecutionOut, status_code=201) @router.post("", response_model=ExecutionOut, status_code=201)
async def report_execution(task_id: int, body: ExecutionReport, async def report_execution(task_id: int, body: ExecutionReport,
db: AsyncSession = Depends(get_db)): db: AsyncSession = Depends(get_db)):
@ -46,16 +56,27 @@ async def report_execution(task_id: int, body: ExecutionReport,
return ExecutionOut(**record.__dict__) return ExecutionOut(**record.__dict__)
@router.get("", response_model=list[ExecutionOut]) @router.get("", response_model=PaginatedExecutions)
async def list_executions( async def list_executions(
task_id: int, task_id: int,
limit: int = Query(50, ge=1, le=200), page: int = Query(1, ge=1),
offset: int = Query(0, ge=0), page_size: int = Query(20, ge=1, le=100),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
): ):
exec_svc = ExecutionService(db) exec_svc = ExecutionService(db)
records = await exec_svc.get_executions(task_id, limit=limit, offset=offset) offset = (page - 1) * page_size
return [ExecutionOut(**r.__dict__) for r in records] records = await exec_svc.get_executions(task_id, limit=page_size, offset=offset)
# Get total count
count_stmt = select(func.count()).select_from(TaskExecution).where(TaskExecution.task_id == task_id)
total = (await db.execute(count_stmt)).scalar() or 0
return PaginatedExecutions(
items=[ExecutionOut(**r.__dict__) for r in records],
total=total,
page=page,
page_size=page_size,
)
@router.get("/recent", response_model=list[ExecutionOut]) @router.get("/recent", response_model=list[ExecutionOut])

View File

@ -42,7 +42,7 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { beijing } from '../utils/time.js' import dayjs from 'dayjs'
import { listAlerts, acknowledgeAlert } from '../api/index.js' import { listAlerts, acknowledgeAlert } from '../api/index.js'
const alerts = ref([]) const alerts = ref([])

View File

@ -152,7 +152,7 @@
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { beijing } from '../utils/time.js' import dayjs from 'dayjs'
import { getDashboardSummary, getDashboardTasks, getSystemConfig } from '../api/index.js' import { getDashboardSummary, getDashboardTasks, getSystemConfig } from '../api/index.js'
const summary = ref({ const summary = ref({

View File

@ -72,6 +72,20 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination -->
<div class="pagination" v-if="total > pageSize && totalPages > 1">
<span class="page-info"> {{ total }} </span>
<div class="page-btns">
<button class="page-btn" :disabled="currentPage <= 1" @click="goPage(currentPage - 1)">上一页</button>
<template v-for="p in (totalPages || 1)" :key="p">
<button v-if="Math.abs(p - currentPage) <= 2 || p === 1 || p === totalPages"
class="page-btn" :class="{ active: p === currentPage }"
@click="goPage(p)">{{ p }}</button>
<span v-else-if="p === currentPage - 3 || p === currentPage + 3" class="page-ellipsis"></span>
</template>
<button class="page-btn" :disabled="currentPage >= totalPages" @click="goPage(currentPage + 1)">下一页</button>
</div>
</div>
</div> </div>
<!-- Log Dialog --> <!-- Log Dialog -->
@ -90,9 +104,9 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { beijing } from '../utils/time.js' import dayjs from 'dayjs'
import { getTask, listExecutions } from '../api/index.js' import { getTask, listExecutions } from '../api/index.js'
const route = useRoute() const route = useRoute()
@ -103,14 +117,37 @@ const logVisible = ref(false)
const currentLog = ref('') const currentLog = ref('')
const logExecution = ref(null) const logExecution = ref(null)
// Pagination
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
const totalPages = computed(() => Math.ceil(total.value / pageSize.value) || 1)
const loadExecutions = async (page) => {
try {
const res = await listExecutions(taskId, { page, page_size: pageSize.value })
const data = res.data || {}
executions.value = data.items || []
total.value = data.total || 0
currentPage.value = data.page || page
} catch (e) {
console.error(e)
executions.value = []
}
}
const goPage = (p) => {
if (p < 1 || p > totalPages.value) return
loadExecutions(p)
}
onMounted(async () => { onMounted(async () => {
try { try {
const [taskRes, execRes] = await Promise.all([ const [taskRes] = await Promise.all([
getTask(taskId), getTask(taskId),
listExecutions(taskId, { limit: 100 }),
]) ])
task.value = taskRes.data task.value = taskRes.data
executions.value = execRes.data await loadExecutions(1)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
@ -243,4 +280,40 @@ const showLog = (row) => {
word-break: break-all; word-break: break-all;
margin: 0; margin: 0;
} }
/* ── Pagination ──────────────────────────── */
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
border-top: 1px solid var(--border-color);
}
.page-info {
font-size: 12px;
color: var(--text-muted);
}
.page-btns {
display: flex;
align-items: center;
gap: 4px;
}
.page-btn {
padding: 4px 10px;
border-radius: 6px;
border: 1px solid var(--border-color);
background: transparent;
color: var(--text-secondary);
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
}
.page-btn:hover { border-color: var(--accent-1); color: var(--accent-1); }
.page-btn.active {
background: var(--accent-1);
border-color: var(--accent-1);
color: #fff;
}
.page-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.page-ellipsis { color: var(--text-muted); font-size: 12px; padding: 0 2px; }
</style> </style>