1use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11use crate::amalgamation;
12use crate::common::ApiState;
13use crate::common::{ApiError, ApiResult, Json, State};
14
15#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
21pub struct FatigueInfo {
22 pub fatigue_index: Option<u8>,
24 pub fatigue_active: Option<bool>,
26 pub regular_neuron_util: Option<u8>,
28 pub memory_neuron_util: Option<u8>,
30 pub synapse_util: Option<u8>,
32}
33
34#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
36pub struct HealthCheckResponse {
37 pub burst_engine: bool,
38 pub connected_agents: Option<i32>,
39 pub influxdb_availability: bool,
40 pub neuron_count_max: i64,
41 pub synapse_count_max: i64,
42 pub latest_changes_saved_externally: bool,
43 pub genome_availability: bool,
44 pub genome_validity: Option<bool>,
45 pub brain_readiness: bool,
46 pub genome_loading: bool,
48 pub genome_state: String,
50 pub feagi_session: Option<i64>,
51 pub fitness: Option<f64>,
52 pub cortical_area_count: Option<i32>,
53 pub neuron_count: Option<i64>,
54 pub memory_neuron_count: Option<i64>,
55 pub regular_neuron_count: Option<i64>,
56 pub synapse_count: Option<i64>,
57 pub estimated_brain_size_in_MB: Option<f64>,
58 pub genome_num: Option<i32>,
59 pub genome_timestamp: Option<i64>,
60 pub simulation_timestep: Option<f64>,
61 pub memory_area_stats: Option<HashMap<String, HashMap<String, serde_json::Value>>>,
62 pub amalgamation_pending: Option<HashMap<String, serde_json::Value>>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub brain_regions_hash: Option<u64>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub cortical_areas_hash: Option<u64>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub brain_geometry_hash: Option<u64>,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub morphologies_hash: Option<u64>,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub cortical_mappings_hash: Option<u64>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub agent_data_hash: Option<u64>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub brain_regions_root: Option<String>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub fatigue: Option<FatigueInfo>,
87}
88
89#[utoipa::path(
95 get,
96 path = "/v1/system/health_check",
97 responses(
98 (status = 200, description = "System health retrieved successfully", body = HealthCheckResponse),
99 (status = 500, description = "Internal server error")
100 ),
101 tag = "system"
102)]
103pub async fn get_health_check(
104 State(state): State<ApiState>,
105) -> ApiResult<Json<HealthCheckResponse>> {
106 let analytics_service = state.analytics_service.as_ref();
107
108 let health = analytics_service
110 .get_system_health()
111 .await
112 .map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
113
114 let runtime_status = state.runtime_service.get_status().await.ok();
116 let burst_engine_active = runtime_status
117 .as_ref()
118 .map(|status| status.is_running)
119 .unwrap_or(false);
120
121 let _burst_count = state.runtime_service.get_burst_count().await.ok();
122
123 let connected_agents = if let Some(agent_service) = state.agent_service.as_ref() {
125 agent_service
126 .list_agents()
127 .await
128 .ok()
129 .map(|agents| agents.len() as i32)
130 } else {
131 None
132 };
133
134 let synapse_count = analytics_service
136 .get_total_synapse_count()
137 .await
138 .ok()
139 .map(|count| count as i64);
140
141 let regular_neuron_count = analytics_service
143 .get_regular_neuron_count()
144 .await
145 .ok()
146 .map(|count| count as i64);
147
148 let memory_neuron_count = analytics_service
149 .get_memory_neuron_count()
150 .await
151 .ok()
152 .map(|count| count as i64);
153
154 let genome_info = state.genome_service.get_genome_info().await.ok();
156
157 let runtime_timestep = runtime_status.as_ref().map(|status| {
159 if status.frequency_hz > 0.0 {
160 1.0 / status.frequency_hz
161 } else {
162 0.0
163 }
164 });
165 let simulation_timestep =
166 runtime_timestep.or_else(|| genome_info.as_ref().map(|info| info.simulation_timestep));
167 let genome_num = genome_info.as_ref().and_then(|info| info.genome_num);
168 let genome_timestamp = genome_info.as_ref().and_then(|info| info.genome_timestamp);
169
170 #[allow(non_snake_case)] let estimated_brain_size_in_MB = {
174 let neuron_bytes = health.neuron_count * 64;
175 let synapse_bytes = synapse_count.unwrap_or(0) as usize * 16;
176 let metadata_bytes = health.cortical_area_count * 512; let total_bytes = neuron_bytes + synapse_bytes + metadata_bytes;
178 Some((total_bytes as f64) / (1024.0 * 1024.0))
179 };
180
181 let neuron_count_max = health.neuron_capacity as i64;
183 let synapse_count_max = health.synapse_capacity as i64;
184
185 let influxdb_availability = false; let latest_changes_saved_externally = false; let genome_availability = health.cortical_area_count > 0;
189 let genome_validity = health.genome_validity;
193
194 let feagi_session = Some(state.feagi_session_timestamp);
196
197 let fitness = None; let (memory_area_stats, memory_neuron_count_from_cache) = state
206 .memory_stats_cache
207 .as_ref()
208 .map(|cache| {
209 let snapshot = feagi_npu_plasticity::memory_stats_cache::get_stats_snapshot(cache);
210 let total = snapshot
211 .values()
212 .map(|s| s.neuron_count as i64)
213 .sum::<i64>();
214 let per_area = snapshot
215 .into_iter()
216 .map(|(name, stats)| {
217 let mut inner_map = HashMap::new();
218 inner_map.insert(
219 "neuron_count".to_string(),
220 serde_json::json!(stats.neuron_count),
221 );
222 inner_map.insert(
223 "created_total".to_string(),
224 serde_json::json!(stats.created_total),
225 );
226 inner_map.insert(
227 "deleted_total".to_string(),
228 serde_json::json!(stats.deleted_total),
229 );
230 inner_map.insert(
231 "last_updated".to_string(),
232 serde_json::json!(stats.last_updated),
233 );
234 (name, inner_map)
235 })
236 .collect::<HashMap<String, HashMap<String, serde_json::Value>>>();
237 (Some(per_area), Some(total))
238 })
239 .unwrap_or((None, None));
240
241 let memory_neuron_count = memory_neuron_count_from_cache.or(memory_neuron_count);
243
244 let amalgamation_pending = state.amalgamation_state.read().pending.as_ref().map(|p| {
249 let v = amalgamation::pending_summary_to_health_json(&p.summary);
250 v.as_object()
251 .cloned()
252 .unwrap_or_default()
253 .into_iter()
254 .collect::<HashMap<String, serde_json::Value>>()
255 });
256
257 #[cfg(feature = "services")]
259 let brain_regions_root = feagi_brain_development::ConnectomeManager::instance()
260 .read()
261 .get_root_region_id();
262 #[cfg(not(feature = "services"))]
263 let brain_regions_root = None; #[cfg(feature = "services")]
268 let fatigue = {
269 use feagi_state_manager::StateManager;
270 match StateManager::instance().try_read() {
272 Some(state_manager) => {
273 let core_state = state_manager.get_core_state();
274 Some(FatigueInfo {
275 fatigue_index: Some(core_state.get_fatigue_index()),
276 fatigue_active: Some(core_state.is_fatigue_active()),
277 regular_neuron_util: Some(core_state.get_regular_neuron_util()),
278 memory_neuron_util: Some(core_state.get_memory_neuron_util()),
279 synapse_util: Some(core_state.get_synapse_util()),
280 })
281 }
282 None => {
283 tracing::warn!(target: "feagi-api", "StateManager is locked, cannot read fatigue data");
285 None
286 }
287 }
288 };
289 #[cfg(not(feature = "services"))]
290 let fatigue = {
291 tracing::debug!(target: "feagi-api", "Services feature not enabled, fatigue data unavailable");
292 None
293 };
294
295 let (
296 brain_regions_hash,
297 cortical_areas_hash,
298 brain_geometry_hash,
299 morphologies_hash,
300 cortical_mappings_hash,
301 agent_data_hash,
302 genome_loading,
303 genome_state,
304 ) = {
305 use feagi_state_manager::GenomeState;
306 let state_manager = feagi_state_manager::StateManager::instance();
307 let state_manager = state_manager.read();
308 let gs = state_manager.get_genome_state();
309 let genome_state = match gs {
310 GenomeState::Missing => "missing",
311 GenomeState::Loading => "loading",
312 GenomeState::Loaded => "loaded",
313 GenomeState::Saving => "saving",
314 GenomeState::Error => "error",
315 }
316 .to_string();
317 let genome_loading = gs == GenomeState::Loading;
318 (
319 Some(state_manager.get_brain_regions_hash()),
320 Some(state_manager.get_cortical_areas_hash()),
321 Some(state_manager.get_brain_geometry_hash()),
322 Some(state_manager.get_morphologies_hash()),
323 Some(state_manager.get_cortical_mappings_hash()),
324 Some(state_manager.get_agent_data_hash()),
325 genome_loading,
326 genome_state,
327 )
328 };
329
330 Ok(Json(HealthCheckResponse {
331 burst_engine: burst_engine_active,
332 connected_agents,
333 influxdb_availability,
334 neuron_count_max,
335 synapse_count_max,
336 latest_changes_saved_externally,
337 genome_availability,
338 genome_validity,
339 brain_readiness: health.brain_readiness,
340 genome_loading,
341 genome_state,
342 feagi_session,
343 fitness,
344 cortical_area_count: Some(health.cortical_area_count as i32),
345 neuron_count: Some(health.neuron_count as i64),
346 memory_neuron_count,
347 regular_neuron_count,
348 synapse_count,
349 estimated_brain_size_in_MB,
350 genome_num,
351 genome_timestamp,
352 simulation_timestep,
353 memory_area_stats,
354 amalgamation_pending,
355 brain_regions_hash,
356 cortical_areas_hash,
357 brain_geometry_hash,
358 morphologies_hash,
359 cortical_mappings_hash,
360 agent_data_hash,
361 brain_regions_root, fatigue,
363 }))
364}
365
366#[utoipa::path(
368 get,
369 path = "/v1/system/cortical_area_visualization_skip_rate",
370 responses(
371 (status = 200, description = "Skip rate retrieved successfully", body = i32),
372 (status = 500, description = "Internal server error")
373 ),
374 tag = "system"
375)]
376pub async fn get_cortical_area_visualization_skip_rate(
377 State(_state): State<ApiState>,
378) -> ApiResult<Json<i32>> {
379 Ok(Json(1))
382}
383
384#[utoipa::path(
386 put,
387 path = "/v1/system/cortical_area_visualization_skip_rate",
388 request_body = i32,
389 responses(
390 (status = 200, description = "Skip rate updated successfully"),
391 (status = 500, description = "Internal server error")
392 ),
393 tag = "system"
394)]
395pub async fn set_cortical_area_visualization_skip_rate(
396 State(_state): State<ApiState>,
397 Json(skip_rate): Json<i32>,
398) -> ApiResult<Json<serde_json::Value>> {
399 Ok(Json(serde_json::json!({
401 "message": format!("Skip rate set to {}", skip_rate)
402 })))
403}
404
405#[utoipa::path(
407 get,
408 path = "/v1/system/cortical_area_visualization_suppression_threshold",
409 responses(
410 (status = 200, description = "Threshold retrieved successfully", body = i32),
411 (status = 500, description = "Internal server error")
412 ),
413 tag = "system"
414)]
415pub async fn get_cortical_area_visualization_suppression_threshold(
416 State(_state): State<ApiState>,
417) -> ApiResult<Json<i32>> {
418 Ok(Json(0))
421}
422
423#[utoipa::path(
425 put,
426 path = "/v1/system/cortical_area_visualization_suppression_threshold",
427 request_body = i32,
428 responses(
429 (status = 200, description = "Threshold updated successfully"),
430 (status = 500, description = "Internal server error")
431 ),
432 tag = "system"
433)]
434pub async fn set_cortical_area_visualization_suppression_threshold(
435 State(_state): State<ApiState>,
436 Json(threshold): Json<i32>,
437) -> ApiResult<Json<serde_json::Value>> {
438 Ok(Json(serde_json::json!({
440 "message": format!("Suppression threshold set to {}", threshold)
441 })))
442}
443
444#[utoipa::path(
450 get,
451 path = "/v1/system/version",
452 tag = "system",
453 responses(
454 (status = 200, description = "Version string", body = String)
455 )
456)]
457pub async fn get_version(State(_state): State<ApiState>) -> ApiResult<Json<String>> {
458 Ok(Json(env!("CARGO_PKG_VERSION").to_string()))
459}
460
461#[utoipa::path(
463 get,
464 path = "/v1/system/versions",
465 tag = "system",
466 responses(
467 (status = 200, description = "Version information", body = HashMap<String, String>)
468 )
469)]
470pub async fn get_versions(
471 State(state): State<ApiState>,
472) -> ApiResult<Json<HashMap<String, String>>> {
473 match state.system_service.get_version().await {
476 Ok(version_info) => {
477 let mut versions = version_info.crates.clone();
478
479 versions.insert("rust".to_string(), version_info.rust_version);
481 versions.insert("build_timestamp".to_string(), version_info.build_timestamp);
482
483 Ok(Json(versions))
484 }
485 Err(e) => {
486 tracing::warn!(
488 "Failed to get version from system service: {}, using fallback",
489 e
490 );
491 let mut versions = HashMap::new();
492 versions.insert(
493 "error".to_string(),
494 "system service unavailable".to_string(),
495 );
496 Ok(Json(versions))
497 }
498 }
499}
500
501#[utoipa::path(
503 get,
504 path = "/v1/system/configuration",
505 tag = "system",
506 responses(
507 (status = 200, description = "System configuration", body = HashMap<String, serde_json::Value>)
508 )
509)]
510pub async fn get_configuration(
511 State(state): State<ApiState>,
512) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
513 let health = state
515 .analytics_service
516 .get_system_health()
517 .await
518 .map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
519
520 let mut config = HashMap::new();
521 config.insert("api_host".to_string(), serde_json::json!("0.0.0.0"));
522 config.insert("api_port".to_string(), serde_json::json!(8000));
523 config.insert(
525 "max_neurons".to_string(),
526 serde_json::json!(health.neuron_capacity),
527 );
528 config.insert(
529 "max_synapses".to_string(),
530 serde_json::json!(health.synapse_capacity),
531 );
532
533 Ok(Json(config))
534}
535
536#[utoipa::path(
538 get,
539 path = "/v1/system/user_preferences",
540 tag = "system",
541 responses(
542 (status = 200, description = "User preferences", body = HashMap<String, serde_json::Value>)
543 )
544)]
545pub async fn get_user_preferences(
546 State(_state): State<ApiState>,
547) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
548 let mut prefs = HashMap::new();
549 prefs.insert("adv_mode".to_string(), serde_json::json!(false));
550 prefs.insert("ui_magnification".to_string(), serde_json::json!(1.0));
551 prefs.insert(
552 "auto_pns_area_creation".to_string(),
553 serde_json::json!(true),
554 );
555
556 Ok(Json(prefs))
557}
558
559#[utoipa::path(
561 put,
562 path = "/v1/system/user_preferences",
563 tag = "system",
564 responses(
565 (status = 200, description = "Preferences updated", body = HashMap<String, String>)
566 )
567)]
568pub async fn put_user_preferences(
569 State(_state): State<ApiState>,
570 Json(_prefs): Json<HashMap<String, serde_json::Value>>,
571) -> ApiResult<Json<HashMap<String, String>>> {
572 Ok(Json(HashMap::from([(
573 "message".to_string(),
574 "User preferences updated successfully".to_string(),
575 )])))
576}
577
578#[utoipa::path(
580 get,
581 path = "/v1/system/cortical_area_types",
582 tag = "system",
583 responses(
584 (status = 200, description = "Cortical area types", body = Vec<String>)
585 )
586)]
587pub async fn get_cortical_area_types_list(
588 State(_state): State<ApiState>,
589) -> ApiResult<Json<Vec<String>>> {
590 Ok(Json(vec![
591 "Sensory".to_string(),
592 "Motor".to_string(),
593 "Custom".to_string(),
594 "Memory".to_string(),
595 "Core".to_string(),
596 ]))
597}
598
599#[utoipa::path(
601 post,
602 path = "/v1/system/enable_visualization_fq_sampler",
603 tag = "system",
604 responses(
605 (status = 200, description = "FQ sampler enabled", body = HashMap<String, String>)
606 )
607)]
608pub async fn post_enable_visualization_fq_sampler(
609 State(state): State<ApiState>,
610) -> ApiResult<Json<HashMap<String, String>>> {
611 let runtime_service = state.runtime_service.as_ref();
612
613 runtime_service
614 .set_fcl_sampler_config(None, Some(1))
615 .await
616 .map_err(|e| ApiError::internal(format!("Failed to enable FQ sampler: {}", e)))?;
617
618 Ok(Json(HashMap::from([(
619 "message".to_string(),
620 "Visualization FQ sampler enabled".to_string(),
621 )])))
622}
623
624#[utoipa::path(
626 post,
627 path = "/v1/system/disable_visualization_fq_sampler",
628 tag = "system",
629 responses(
630 (status = 200, description = "FQ sampler disabled", body = HashMap<String, String>)
631 )
632)]
633pub async fn post_disable_visualization_fq_sampler(
634 State(state): State<ApiState>,
635) -> ApiResult<Json<HashMap<String, String>>> {
636 let runtime_service = state.runtime_service.as_ref();
637
638 runtime_service
639 .set_fcl_sampler_config(None, Some(0))
640 .await
641 .map_err(|e| ApiError::internal(format!("Failed to disable FQ sampler: {}", e)))?;
642
643 Ok(Json(HashMap::from([(
644 "message".to_string(),
645 "Visualization FQ sampler disabled".to_string(),
646 )])))
647}
648
649#[utoipa::path(
651 get,
652 path = "/v1/system/fcl_status",
653 tag = "system",
654 responses(
655 (status = 200, description = "FCL status", body = HashMap<String, serde_json::Value>)
656 )
657)]
658pub async fn get_fcl_status_system(
659 State(state): State<ApiState>,
660) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
661 let runtime_service = state.runtime_service.as_ref();
662
663 let (frequency, consumer) = runtime_service
664 .get_fcl_sampler_config()
665 .await
666 .map_err(|e| ApiError::internal(format!("Failed to get FCL status: {}", e)))?;
667
668 let mut response = HashMap::new();
669 response.insert("available".to_string(), serde_json::json!(true));
670 response.insert("frequency".to_string(), serde_json::json!(frequency));
671 response.insert("consumer".to_string(), serde_json::json!(consumer));
672 response.insert("enabled".to_string(), serde_json::json!(consumer > 0));
673
674 Ok(Json(response))
675}
676
677#[utoipa::path(
679 post,
680 path = "/v1/system/fcl_reset",
681 tag = "system",
682 responses(
683 (status = 200, description = "FCL reset", body = HashMap<String, String>)
684 )
685)]
686pub async fn post_fcl_reset_system(
687 State(_state): State<ApiState>,
688) -> ApiResult<Json<HashMap<String, String>>> {
689 tracing::info!(target: "feagi-api", "FCL reset requested");
690
691 Ok(Json(HashMap::from([(
692 "message".to_string(),
693 "FCL reset successfully".to_string(),
694 )])))
695}
696
697#[utoipa::path(
699 get,
700 path = "/v1/system/processes",
701 tag = "system",
702 responses(
703 (status = 200, description = "Active processes", body = HashMap<String, serde_json::Value>)
704 )
705)]
706pub async fn get_processes(
707 State(state): State<ApiState>,
708) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
709 let runtime_service = state.runtime_service.as_ref();
710
711 let status = runtime_service
712 .get_status()
713 .await
714 .map_err(|e| ApiError::internal(format!("Failed to get processes: {}", e)))?;
715
716 let mut processes = HashMap::new();
717 processes.insert(
718 "burst_engine".to_string(),
719 serde_json::json!({
720 "active": status.is_running,
721 "paused": status.is_paused
722 }),
723 );
724 processes.insert(
725 "api_server".to_string(),
726 serde_json::json!({"active": true}),
727 );
728
729 Ok(Json(processes))
730}
731
732#[utoipa::path(
734 get,
735 path = "/v1/system/unique_logs",
736 tag = "system",
737 responses(
738 (status = 200, description = "Unique logs", body = HashMap<String, Vec<String>>)
739 )
740)]
741pub async fn get_unique_logs(
742 State(_state): State<ApiState>,
743) -> ApiResult<Json<HashMap<String, Vec<String>>>> {
744 let mut response = HashMap::new();
745 response.insert("logs".to_string(), Vec::new());
746
747 Ok(Json(response))
748}
749
750#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
756pub struct LogTailRecord {
757 pub timestamp_ms: i64,
758 pub level: String,
759 pub target: String,
760 #[serde(skip_serializing_if = "String::is_empty")]
761 pub file: String,
762 pub line: u32,
763 pub message: String,
764 #[serde(skip_serializing_if = "Option::is_none")]
765 pub fields: Option<serde_json::Value>,
766}
767
768#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
770pub struct LogTailResponse {
771 pub enabled: bool,
775 pub capacity: usize,
777 pub records: Vec<LogTailRecord>,
779 pub returned: usize,
781}
782
783#[utoipa::path(
795 get,
796 path = "/v1/system/log_tail",
797 tag = "system",
798 params(
799 ("since_ts_ms" = Option<i64>, Query, description = "Only return records emitted at or after this Unix timestamp (ms)"),
800 ("level" = Option<String>, Query, description = "Minimum severity (TRACE/DEBUG/INFO/WARN/ERROR)"),
801 ("target_prefix" = Option<String>, Query, description = "Restrict to tracing targets starting with this prefix"),
802 ("limit" = Option<usize>, Query, description = "Maximum number of records to return")
803 ),
804 responses(
805 (status = 200, description = "Recent log records", body = LogTailResponse)
806 )
807)]
808pub async fn get_log_tail(
809 State(_state): State<ApiState>,
810 axum::extract::Query(query): axum::extract::Query<HashMap<String, String>>,
811) -> ApiResult<Json<LogTailResponse>> {
812 let since_ts_ms = query.get("since_ts_ms").and_then(|v| v.parse::<i64>().ok());
813 let level = query.get("level").map(|v| v.as_str());
814 let target_prefix = query.get("target_prefix").map(|v| v.as_str());
815 let limit = query.get("limit").and_then(|v| v.parse::<usize>().ok());
816
817 let Some(ring) = feagi_observability::global_ring() else {
818 return Ok(Json(LogTailResponse {
819 enabled: false,
820 capacity: 0,
821 records: Vec::new(),
822 returned: 0,
823 }));
824 };
825
826 let snap = ring.snapshot(since_ts_ms, level, target_prefix, limit);
827 let records: Vec<LogTailRecord> = snap
828 .into_iter()
829 .map(|r| LogTailRecord {
830 timestamp_ms: r.timestamp_ms,
831 level: r.level,
832 target: r.target,
833 file: r.file,
834 line: r.line,
835 message: r.message,
836 fields: r.fields,
837 })
838 .collect();
839 let returned = records.len();
840
841 Ok(Json(LogTailResponse {
842 enabled: true,
843 capacity: ring.capacity(),
844 records,
845 returned,
846 }))
847}
848
849#[utoipa::path(
851 post,
852 path = "/v1/system/logs",
853 tag = "system",
854 responses(
855 (status = 200, description = "Log config updated", body = HashMap<String, String>)
856 )
857)]
858pub async fn post_logs(
859 State(_state): State<ApiState>,
860 Json(_config): Json<HashMap<String, serde_json::Value>>,
861) -> ApiResult<Json<HashMap<String, String>>> {
862 Ok(Json(HashMap::from([(
863 "message".to_string(),
864 "Log configuration updated".to_string(),
865 )])))
866}
867
868#[utoipa::path(
870 get,
871 path = "/v1/system/beacon/subscribers",
872 tag = "system",
873 responses(
874 (status = 200, description = "Beacon subscribers", body = Vec<String>)
875 )
876)]
877pub async fn get_beacon_subscribers(
878 State(_state): State<ApiState>,
879) -> ApiResult<Json<Vec<String>>> {
880 Ok(Json(Vec::new()))
881}
882
883#[utoipa::path(
885 post,
886 path = "/v1/system/beacon/subscribe",
887 tag = "system",
888 responses(
889 (status = 200, description = "Subscribed", body = HashMap<String, String>)
890 )
891)]
892pub async fn post_beacon_subscribe(
893 State(_state): State<ApiState>,
894 Json(_request): Json<HashMap<String, String>>,
895) -> ApiResult<Json<HashMap<String, String>>> {
896 Ok(Json(HashMap::from([(
897 "message".to_string(),
898 "Subscribed to beacon".to_string(),
899 )])))
900}
901
902#[utoipa::path(
904 delete,
905 path = "/v1/system/beacon/unsubscribe",
906 tag = "system",
907 responses(
908 (status = 200, description = "Unsubscribed", body = HashMap<String, String>)
909 )
910)]
911pub async fn delete_beacon_unsubscribe(
912 State(_state): State<ApiState>,
913 Json(_request): Json<HashMap<String, String>>,
914) -> ApiResult<Json<HashMap<String, String>>> {
915 Ok(Json(HashMap::from([(
916 "message".to_string(),
917 "Unsubscribed from beacon".to_string(),
918 )])))
919}
920
921#[utoipa::path(
923 get,
924 path = "/v1/system/global_activity_visualization",
925 tag = "system",
926 responses(
927 (status = 200, description = "Global activity viz status", body = HashMap<String, serde_json::Value>)
928 )
929)]
930pub async fn get_global_activity_visualization(
931 State(_state): State<ApiState>,
932) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
933 let mut response = HashMap::new();
934 response.insert("enabled".to_string(), serde_json::json!(false));
935 response.insert("frequency_hz".to_string(), serde_json::json!(30.0));
936
937 Ok(Json(response))
938}
939
940#[utoipa::path(
942 put,
943 path = "/v1/system/global_activity_visualization",
944 tag = "system",
945 responses(
946 (status = 200, description = "Configured", body = HashMap<String, String>)
947 )
948)]
949pub async fn put_global_activity_visualization(
950 State(_state): State<ApiState>,
951 Json(_config): Json<HashMap<String, serde_json::Value>>,
952) -> ApiResult<Json<HashMap<String, String>>> {
953 Ok(Json(HashMap::from([(
954 "message".to_string(),
955 "Global activity visualization configured".to_string(),
956 )])))
957}
958
959#[utoipa::path(
961 post,
962 path = "/v1/system/circuit_library_path",
963 tag = "system",
964 responses(
965 (status = 200, description = "Path set", body = HashMap<String, String>)
966 )
967)]
968pub async fn post_circuit_library_path(
969 State(_state): State<ApiState>,
970 Json(_request): Json<HashMap<String, String>>,
971) -> ApiResult<Json<HashMap<String, String>>> {
972 Ok(Json(HashMap::from([(
973 "message".to_string(),
974 "Circuit library path updated".to_string(),
975 )])))
976}
977
978#[utoipa::path(
980 get,
981 path = "/v1/system/db/influxdb/test",
982 tag = "system",
983 responses(
984 (status = 200, description = "Test result", body = HashMap<String, bool>)
985 )
986)]
987pub async fn get_influxdb_test(
988 State(_state): State<ApiState>,
989) -> ApiResult<Json<HashMap<String, bool>>> {
990 let mut response = HashMap::new();
991 response.insert("connected".to_string(), false);
992 response.insert("available".to_string(), false);
993
994 Ok(Json(response))
995}
996
997#[utoipa::path(
999 post,
1000 path = "/v1/system/register",
1001 tag = "system",
1002 responses(
1003 (status = 200, description = "Registered", body = HashMap<String, String>)
1004 )
1005)]
1006pub async fn post_register_system(
1007 State(_state): State<ApiState>,
1008 Json(_request): Json<HashMap<String, serde_json::Value>>,
1009) -> ApiResult<Json<HashMap<String, String>>> {
1010 Ok(Json(HashMap::from([(
1011 "message".to_string(),
1012 "System component registered".to_string(),
1013 )])))
1014}