Show Git ingestion job status
This commit is contained in:
+55
-8
@@ -1,8 +1,11 @@
|
||||
"""WebUI FastAPI application."""
|
||||
import asyncio
|
||||
import html
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import FastAPI, File, Form, Request, UploadFile
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
@@ -23,6 +26,8 @@ templates.env.globals["escapeHtml"] = lambda value: html.escape(str(value or "")
|
||||
app.mount("/static", StaticFiles(directory=os.path.join(os.path.dirname(__file__), "static")), name="static")
|
||||
|
||||
_client: Optional[DocsAPIClient] = None
|
||||
_sync_jobs: Dict[str, Dict[str, Any]] = {}
|
||||
_sync_tasks: set[asyncio.Task] = set()
|
||||
|
||||
|
||||
def get_client() -> DocsAPIClient:
|
||||
@@ -35,6 +40,25 @@ def get_client() -> DocsAPIClient:
|
||||
return _client
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
async def run_sync_job(job_id: str, override: bool) -> None:
|
||||
job = _sync_jobs[job_id]
|
||||
job["status"] = "running"
|
||||
job["started_at"] = utc_now()
|
||||
try:
|
||||
result = await get_client().post("/sources/sync", json={"override": override})
|
||||
job["result"] = result
|
||||
job["status"] = "succeeded" if result.get("success") else "failed"
|
||||
except Exception as exc:
|
||||
job["status"] = "failed"
|
||||
job["error"] = str(exc)
|
||||
finally:
|
||||
job["finished_at"] = utc_now()
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown() -> None:
|
||||
if _client is not None:
|
||||
@@ -300,10 +324,33 @@ async def add_source(
|
||||
|
||||
@app.post("/sources/sync")
|
||||
async def sync_sources(override: bool = Form(False)):
|
||||
client = get_client()
|
||||
try:
|
||||
result = await client.post("/sources/sync", json={"override": override})
|
||||
body = f"<h1>Git Sync Complete</h1><pre>{html.escape(str(result))}</pre><a href='/sources'>Back</a>"
|
||||
except Exception as e:
|
||||
body = f"<h1>Git Sync Failed</h1><pre>{html.escape(str(e))}</pre><a href='/sources'>Back</a>"
|
||||
return page("Git Sync", body)
|
||||
job_id = uuid.uuid4().hex
|
||||
_sync_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"status": "queued",
|
||||
"created_at": utc_now(),
|
||||
"started_at": None,
|
||||
"finished_at": None,
|
||||
"result": None,
|
||||
"error": None,
|
||||
}
|
||||
task = asyncio.create_task(run_sync_job(job_id, override))
|
||||
_sync_tasks.add(task)
|
||||
task.add_done_callback(_sync_tasks.discard)
|
||||
return RedirectResponse(url=f"/sources/jobs/{job_id}", status_code=303)
|
||||
|
||||
|
||||
@app.get("/sources/jobs/{job_id}")
|
||||
async def sync_job_page(request: Request, job_id: str):
|
||||
job = _sync_jobs.get(job_id)
|
||||
if job is None:
|
||||
return page("Git Sync Not Found", "<h1>Git Sync Not Found</h1><a href='/sources'>Back</a>")
|
||||
return templates.TemplateResponse("sync_job.html", {"request": request, "job": job})
|
||||
|
||||
|
||||
@app.get("/sources/jobs/{job_id}/status")
|
||||
async def sync_job_status(job_id: str):
|
||||
job = _sync_jobs.get(job_id)
|
||||
if job is None:
|
||||
return JSONResponse(status_code=404, content={"error": "Sync job not found"})
|
||||
return job
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Git Sync Status - Context7 Docs{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Git Sync Status</h2>
|
||||
|
||||
<div class="status-message">
|
||||
Status: <strong id="job-status">{{ job.status }}</strong>
|
||||
</div>
|
||||
|
||||
{% if job.status in ['queued', 'running'] %}
|
||||
<p>Cloning, reading, embedding, and indexing documents. Large repositories can take several minutes.</p>
|
||||
<p>This page updates automatically. You can leave it open or return later using the same URL.</p>
|
||||
{% elif job.error %}
|
||||
<h3>Sync Failed</h3>
|
||||
<pre>{{ job.error }}</pre>
|
||||
{% elif job.result %}
|
||||
<h3>Summary</h3>
|
||||
<ul>
|
||||
<li>Sources attempted: {{ job.result.total_sources | default(0) }}</li>
|
||||
<li>Successful: {{ job.result.successful | default(0) }}</li>
|
||||
<li>Failed: {{ job.result.failed | default(0) }}</li>
|
||||
</ul>
|
||||
|
||||
{% for result in job.result.results | default([]) %}
|
||||
<div class="source-card">
|
||||
<strong>{{ result.library_id | default('unknown') }}</strong><br>
|
||||
Status: {{ 'succeeded' if result.success else 'failed' }}<br>
|
||||
{% if result.success %}
|
||||
Files discovered: {{ result.files_discovered | default(0) }}<br>
|
||||
Chunks created: {{ result.chunks_created | default(0) }}<br>
|
||||
Vectors added: {{ result.vectors_added | default(0) }}<br>
|
||||
<a href="/libraries/{{ result.library_id }}/docs">View indexed documents</a>
|
||||
{% else %}
|
||||
Error: {{ result.error | default('Unknown ingestion error') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<p><a href="/sources">Back to sources</a> | <a href="/libraries">View libraries</a> | <a href="/search">Test search</a></p>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% if job.status in ['queued', 'running'] %}
|
||||
<script>
|
||||
setTimeout(() => window.location.reload(), 3000);
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user