1414import uvicorn
1515from fastapi import FastAPI , File , Form , HTTPException , UploadFile
1616from fastapi .responses import HTMLResponse , JSONResponse
17+ from pydantic import BaseModel
1718
1819from .backend_client import BackendClient
1920from .capture import validate_rtsp_stream
@@ -38,6 +39,11 @@ def parse_rtsp_urls(rtsp_text: str) -> list[str]:
3839WIZARD_ROUTE_PREFIX = "/setup"
3940
4041
42+ class WizardClaimBody (BaseModel ):
43+ setupCode : str = ""
44+ name : str = ""
45+
46+
4147def wizard_page_html (route_prefix : str = "" ) -> str :
4248 """HTML for the setup wizard; route_prefix is '' for standalone or '/setup' when mounted."""
4349 prefix = route_prefix .rstrip ("/" )
@@ -197,8 +203,13 @@ def wizard_page_html(route_prefix: str = "") -> str:
197203 headers: { 'Content-Type': 'application/json' },
198204 body: JSON.stringify({ setupCode: code, name })
199205 });
200- const data = await r.json();
201- if (!r.ok) throw new Error(data.detail || data.error || 'Claim failed');
206+ const text = await r.text();
207+ let data = {};
208+ try { data = text ? JSON.parse(text) : {}; } catch { data = { detail: text || r.statusText }; }
209+ if (!r.ok) {
210+ const err = data.detail || data.error || data.message || text || 'Claim failed';
211+ throw new Error(typeof err === 'string' ? err : JSON.stringify(err));
212+ }
202213 show(msg, true, 'Linked to store. Next: add cameras.');
203214 setTimeout(() => setStep(1), 500);
204215 } catch (e) { show(msg, false, e.message || String(e)); }
@@ -214,8 +225,13 @@ def wizard_page_html(route_prefix: str = "") -> str:
214225 fd.append('loop_file', document.getElementById('loopFile').checked ? 'true' : 'false');
215226 try {
216227 const r = await fetch('wizard/sources', { method: 'POST', body: fd });
217- const data = await r.json();
218- if (!r.ok) throw new Error(data.detail || data.error || 'Save failed');
228+ const text = await r.text();
229+ let data = {};
230+ try { data = text ? JSON.parse(text) : {}; } catch { data = { detail: text || r.statusText }; }
231+ if (!r.ok) {
232+ const err = data.detail || data.error || text || 'Save failed';
233+ throw new Error(typeof err === 'string' ? err : JSON.stringify(err));
234+ }
219235 document.getElementById('doneText').textContent =
220236 `Monitoring ${data.cameraCount} source(s). Clips upload on motion.`;
221237 setStep(2);
@@ -268,18 +284,18 @@ def wizard_status():
268284 }
269285
270286 @app .post (f"{ api_prefix } /claim" )
271- def wizard_claim (body : dict ):
272- code = (body .get ( " setupCode" ) or body . get ( "setup_code" ) or "" ) .strip ()
273- name = (body .get ( " name" ) or cfg .connector_name or "edge-connector-1" ).strip ()
287+ def wizard_claim (body : WizardClaimBody ):
288+ code = (body .setupCode or "" ) .strip (). upper ()
289+ name = (body .name or cfg .connector_name or "edge-connector-1" ).strip ()
274290 if not code :
275- raise HTTPException (400 , "setupCode is required" )
291+ raise HTTPException (status_code = 400 , detail = "setupCode is required" )
276292 try :
277293 w = load_wizard_config () or WizardConfig ()
278294 w .setup_code = code
279295 w .connector_name = name
280296 cid , store_id = claim_setup (client , store , w , cfg .version )
281297 except Exception as exc : # noqa: BLE001
282- raise HTTPException (400 , str (exc )) from exc
298+ raise HTTPException (status_code = 400 , detail = str (exc )) from exc
283299 state .connector_id = cid
284300 state .log (f"Wizard: claimed setup code → connector { cid } store { store_id } " )
285301 w = load_wizard_config () or WizardConfig ()
0 commit comments