update to 0.16.0 #72

Merged
serversdown merged 32 commits from dev into main 2026-06-23 00:59:46 -04:00
2 changed files with 77 additions and 1 deletions
Showing only changes of commit 93f01be471 - Show all commits
+50
View File
@@ -296,6 +296,12 @@ async def promote_pending(
if not location: if not location:
raise HTTPException(status_code=404, detail=f"Location {location_id!r} not found.") raise HTTPException(status_code=404, detail=f"Location {location_id!r} not found.")
project_id = location.project_id project_id = location.project_id
# Backfill the captured GPS onto the existing location if it doesn't
# have coordinates yet. (Previously the captured coords were dropped on
# the assign-to-existing path, so only create-new locations got a pin.)
# Don't clobber coordinates an operator already set.
if pd.coordinates and not (location.coordinates or "").strip():
location.coordinates = pd.coordinates
else: else:
# Create-new path. Need a project (existing or new). # Create-new path. Need a project (existing or new).
project_id = payload.get("project_id") project_id = payload.get("project_id")
@@ -456,6 +462,50 @@ async def cancel_pending(
return {"success": True, "cancelled_at": pd.cancelled_at.isoformat()} return {"success": True, "cancelled_at": pd.cancelled_at.isoformat()}
@router.post("/pending/{pending_id}/resync-location")
async def resync_location(pending_id: str, db: Session = Depends(get_db)):
"""Re-push a promoted capture's GPS onto its assigned location.
Use when a capture's coordinates didn't land on the location (e.g. it was
assigned to a pre-existing location that had none). Unlike the auto-backfill
on classify, this is an explicit operator action and OVERWRITES the
location's coordinates with the captured GPS.
"""
pd = db.query(PendingDeployment).filter_by(id=pending_id).first()
if not pd:
raise HTTPException(status_code=404, detail="Pending deployment not found.")
if pd.status != "assigned" or not pd.resulting_assignment_id:
raise HTTPException(status_code=400, detail="Only a promoted (assigned) capture can be re-forwarded.")
if not (pd.coordinates or "").strip():
raise HTTPException(status_code=400, detail="This capture has no GPS coordinates to forward.")
asg = db.query(UnitAssignment).filter_by(id=pd.resulting_assignment_id).first()
if not asg:
raise HTTPException(status_code=404, detail="Resulting assignment not found.")
location = db.query(MonitoringLocation).filter_by(id=asg.location_id).first()
if not location:
raise HTTPException(status_code=404, detail="Assigned location not found.")
old = location.coordinates
location.coordinates = pd.coordinates.strip()
_record_history(
db, unit_id=pd.unit_id,
change_type="deployment_coords_reforwarded",
old_value=old,
new_value=location.coordinates,
notes=f"Re-forwarded capture GPS to location '{location.name}'",
)
db.commit()
return {
"success": True,
"location_id": location.id,
"location_name": location.name,
"coordinates": location.coordinates,
}
# ── Helpers ─────────────────────────────────────────────────────────────────── # ── Helpers ───────────────────────────────────────────────────────────────────
def _to_dict(pd: PendingDeployment, unit: Optional[RosterUnit] = None, detail: bool = False) -> dict: def _to_dict(pd: PendingDeployment, unit: Optional[RosterUnit] = None, detail: bool = False) -> dict:
+27 -1
View File
@@ -207,9 +207,19 @@ function _renderPdCard(pd) {
</button> </button>
</div>`; </div>`;
} else if (pd.status === 'assigned') { } else if (pd.status === 'assigned') {
const reforward = pd.coordinates
? `<button onclick="reforwardInfo('${_esc(pd.id)}')"
class="mt-2 inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border border-seismo-orange/40 bg-seismo-orange/10 text-seismo-orange hover:bg-seismo-orange/20 transition-colors"
title="Re-push this capture's GPS coordinates onto its assigned location">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
Reforward info
</button>`
: '';
footerActions = `<div class="mt-3 text-xs text-green-700 dark:text-green-400"> footerActions = `<div class="mt-3 text-xs text-green-700 dark:text-green-400">
Promoted ${_fmtDateTime(pd.promoted_at)} → assignment <span class="font-mono">${_esc((pd.resulting_assignment_id || '').slice(0, 8))}…</span> Promoted ${_fmtDateTime(pd.promoted_at)} → assignment <span class="font-mono">${_esc((pd.resulting_assignment_id || '').slice(0, 8))}…</span>
</div>`; </div>${reforward}`;
} else if (pd.status === 'cancelled') { } else if (pd.status === 'cancelled') {
footerActions = `<div class="mt-3 text-xs text-gray-500 dark:text-gray-400"> footerActions = `<div class="mt-3 text-xs text-gray-500 dark:text-gray-400">
Cancelled ${_fmtDateTime(pd.cancelled_at)}${pd.cancelled_reason ? `${_esc(pd.cancelled_reason)}` : ''} Cancelled ${_fmtDateTime(pd.cancelled_at)}${pd.cancelled_reason ? `${_esc(pd.cancelled_reason)}` : ''}
@@ -459,6 +469,22 @@ async function cancelPending(pendingId) {
} }
} }
// Re-push a promoted capture's GPS coordinates onto its assigned location.
async function reforwardInfo(pendingId) {
try {
const r = await fetch(`/api/deployments/pending/${pendingId}/resync-location`, {
method: 'POST',
});
const j = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(j.detail || 'HTTP ' + r.status);
const msg = `Coordinates synced to "${j.location_name}": ${j.coordinates}`;
if (window.showToast) showToast(msg, 'success'); else alert(msg);
} catch (e) {
const msg = 'Reforward failed: ' + e.message;
if (window.showToast) showToast(msg, 'error'); else alert(msg);
}
}
// Kick off the initial load. // Kick off the initial load.
loadPdList(); loadPdList();
// Refresh awaiting count every 30s for the badge. // Refresh awaiting count every 30s for the badge.