@@ -1973,6 +1973,156 @@ async def api_health(request):
19731973 logger .error (f"Health API error: { str (e )} " )
19741974 return web .json_response ({"success" : False , "error" : str (e )}, status = 500 )
19751975
1976+ # ---- S3: Telemetry API Endpoints ----
1977+ from telemetry import get_telemetry_store
1978+
1979+ # Initialize telemetry with config setting
1980+ _telemetry_store = get_telemetry_store ()
1981+ _telemetry_store .enabled = CONFIG .telemetry_enabled
1982+
1983+ @server .PromptServer .instance .routes .get ("/doctor/telemetry/status" )
1984+ async def api_telemetry_status (request ):
1985+ """
1986+ Get telemetry status and buffer stats.
1987+ Returns: {"success": bool, "enabled": bool, "stats": {...}}
1988+ """
1989+ try :
1990+ store = get_telemetry_store ()
1991+ stats = store .get_stats ()
1992+ return web .json_response ({
1993+ "success" : True ,
1994+ "enabled" : store .enabled ,
1995+ "stats" : stats ,
1996+ "upload_destination" : None , # Phase 1-3: local only
1997+ })
1998+ except Exception as e :
1999+ logger .error (f"Telemetry status API error: { str (e )} " )
2000+ return web .json_response ({"success" : False , "error" : str (e )}, status = 500 )
2001+
2002+ @server .PromptServer .instance .routes .get ("/doctor/telemetry/buffer" )
2003+ async def api_telemetry_buffer (request ):
2004+ """
2005+ Get buffered telemetry events.
2006+ Returns: {"success": bool, "events": [...]}
2007+ """
2008+ try :
2009+ store = get_telemetry_store ()
2010+ events = store .get_buffer ()
2011+ return web .json_response ({
2012+ "success" : True ,
2013+ "events" : events ,
2014+ "count" : len (events ),
2015+ })
2016+ except Exception as e :
2017+ logger .error (f"Telemetry buffer API error: { str (e )} " )
2018+ return web .json_response ({"success" : False , "error" : str (e )}, status = 500 )
2019+
2020+ @server .PromptServer .instance .routes .post ("/doctor/telemetry/track" )
2021+ async def api_telemetry_track (request ):
2022+ """
2023+ Record a telemetry event.
2024+ Body: {"category": str, "action": str, "label"?: str, "value"?: int}
2025+ Returns: {"success": bool, "message": str}
2026+ """
2027+ try :
2028+ # Security: Same-origin check (reject cross-origin requests)
2029+ origin = request .headers .get ("Origin" , "" )
2030+ host = request .headers .get ("Host" , "" )
2031+ if origin :
2032+ # Extract host from origin (e.g., "http://localhost:8188" -> "localhost:8188")
2033+ from urllib .parse import urlparse
2034+ origin_host = urlparse (origin ).netloc
2035+ if origin_host and host and origin_host != host :
2036+ return web .Response (status = 403 , text = "Cross-origin request rejected" )
2037+
2038+ # Security: Check Content-Type
2039+ content_type = request .headers .get ("Content-Type" , "" )
2040+ if "application/json" not in content_type :
2041+ return web .Response (status = 400 , text = "Content-Type must be application/json" )
2042+
2043+ # Security: Payload size limit (1KB)
2044+ content_length = request .content_length or 0
2045+ if content_length > 1024 :
2046+ return web .Response (status = 413 , text = "Payload too large" )
2047+
2048+ # Parse JSON
2049+ try :
2050+ data = await request .json ()
2051+ except Exception :
2052+ return web .Response (status = 400 , text = "Invalid JSON" )
2053+
2054+ # Security: Reject unexpected fields
2055+ allowed_fields = {"category" , "action" , "label" , "value" }
2056+ if set (data .keys ()) - allowed_fields :
2057+ return web .Response (status = 400 , text = "Unexpected fields" )
2058+
2059+ # Track event
2060+ store = get_telemetry_store ()
2061+ success , message = store .track (data )
2062+
2063+ return web .json_response ({"success" : success , "message" : message })
2064+ except Exception as e :
2065+ logger .error (f"Telemetry track API error: { str (e )} " )
2066+ return web .json_response ({"success" : False , "message" : str (e )}, status = 500 )
2067+
2068+ @server .PromptServer .instance .routes .post ("/doctor/telemetry/clear" )
2069+ async def api_telemetry_clear (request ):
2070+ """
2071+ Clear all buffered telemetry events.
2072+ Returns: {"success": bool, "message": str}
2073+ """
2074+ try :
2075+ store = get_telemetry_store ()
2076+ store .clear ()
2077+ return web .json_response ({"success" : True , "message" : "Buffer cleared" })
2078+ except Exception as e :
2079+ logger .error (f"Telemetry clear API error: { str (e )} " )
2080+ return web .json_response ({"success" : False , "message" : str (e )}, status = 500 )
2081+
2082+ @server .PromptServer .instance .routes .get ("/doctor/telemetry/export" )
2083+ async def api_telemetry_export (request ):
2084+ """
2085+ Export telemetry buffer as downloadable JSON file.
2086+ Returns: JSON file download
2087+ """
2088+ try :
2089+ store = get_telemetry_store ()
2090+ json_data = store .export_json ()
2091+
2092+ return web .Response (
2093+ body = json_data ,
2094+ content_type = "application/json" ,
2095+ headers = {
2096+ "Content-Disposition" : "attachment; filename=telemetry_export.json"
2097+ }
2098+ )
2099+ except Exception as e :
2100+ logger .error (f"Telemetry export API error: { str (e )} " )
2101+ return web .json_response ({"success" : False , "error" : str (e )}, status = 500 )
2102+
2103+ @server .PromptServer .instance .routes .post ("/doctor/telemetry/toggle" )
2104+ async def api_telemetry_toggle (request ):
2105+ """
2106+ Toggle telemetry enabled/disabled state.
2107+ Body: {"enabled": bool}
2108+ Returns: {"success": bool, "enabled": bool}
2109+ """
2110+ try :
2111+ data = await request .json ()
2112+ enabled = data .get ("enabled" , False )
2113+
2114+ store = get_telemetry_store ()
2115+ store .enabled = bool (enabled )
2116+
2117+ return web .json_response ({
2118+ "success" : True ,
2119+ "enabled" : store .enabled ,
2120+ "message" : "Telemetry enabled" if store .enabled else "Telemetry disabled"
2121+ })
2122+ except Exception as e :
2123+ logger .error (f"Telemetry toggle API error: { str (e )} " )
2124+ return web .json_response ({"success" : False , "message" : str (e )}, status = 500 )
2125+
19762126 @server .PromptServer .instance .routes .get ("/doctor/plugins" )
19772127 async def api_plugins (request ):
19782128 """
@@ -2048,6 +2198,12 @@ def sanitize_manifest(manifest):
20482198 print (" - POST /doctor/mark_resolved (F4)" )
20492199 print (" - GET /doctor/health" )
20502200 print (" - GET /doctor/plugins" )
2201+ print (" - GET /doctor/telemetry/status (S3)" )
2202+ print (" - GET /doctor/telemetry/buffer (S3)" )
2203+ print (" - POST /doctor/telemetry/track (S3)" )
2204+ print (" - POST /doctor/telemetry/clear (S3)" )
2205+ print (" - GET /doctor/telemetry/export (S3)" )
2206+ print (" - POST /doctor/telemetry/toggle (S3)" )
20512207 print ("\n " )
20522208 print ("💬 Questions? Updates? Suggestions and Contributions are welcome!" )
20532209 print ("⭐ Give us a Star on GitHub - it's always good for the Doctor's health! 💝" )
0 commit comments