4runr-os 2.10.39 → 2.10.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/gateway/dist/apps/gateway/src/index.js +14 -4
- package/apps/gateway/dist/apps/gateway/src/index.js.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/metrics/monitoring-detail.d.ts +18 -0
- package/apps/gateway/dist/apps/gateway/src/metrics/monitoring-detail.d.ts.map +1 -0
- package/apps/gateway/dist/apps/gateway/src/metrics/monitoring-detail.js +117 -0
- package/apps/gateway/dist/apps/gateway/src/metrics/monitoring-detail.js.map +1 -0
- package/apps/gateway/dist/apps/gateway/src/middleware/log-capture.d.ts +2 -0
- package/apps/gateway/dist/apps/gateway/src/middleware/log-capture.d.ts.map +1 -0
- package/apps/gateway/dist/apps/gateway/src/middleware/log-capture.js +54 -0
- package/apps/gateway/dist/apps/gateway/src/middleware/log-capture.js.map +1 -0
- package/apps/gateway/dist/apps/gateway/src/routes/monitoring.d.ts +15 -0
- package/apps/gateway/dist/apps/gateway/src/routes/monitoring.d.ts.map +1 -0
- package/apps/gateway/dist/apps/gateway/src/routes/monitoring.js +164 -0
- package/apps/gateway/dist/apps/gateway/src/routes/monitoring.js.map +1 -0
- package/apps/gateway/package-lock.json +204 -353
- package/apps/gateway/src/index.ts +27 -8
- package/apps/gateway/src/metrics/monitoring-detail.ts +162 -0
- package/apps/gateway/src/middleware/log-capture.ts +70 -0
- package/apps/gateway/src/routes/monitoring.ts +298 -0
- package/dist/gateway-client.d.ts +2 -0
- package/dist/gateway-client.d.ts.map +1 -1
- package/dist/gateway-client.js +22 -0
- package/dist/gateway-client.js.map +1 -1
- package/dist/tui-handlers.js +498 -0
- package/dist/tui-handlers.js.map +1 -1
- package/mk3-tui/src/app.rs +569 -8
- package/mk3-tui/src/main.rs +248 -0
- package/mk3-tui/src/monitoring/mod.rs +428 -0
- package/mk3-tui/src/ui/portal_monitoring.rs +1018 -146
- package/package.json +2 -2
package/mk3-tui/src/main.rs
CHANGED
|
@@ -11,6 +11,7 @@ use std::time::{Duration, Instant};
|
|
|
11
11
|
mod app;
|
|
12
12
|
mod debug_log;
|
|
13
13
|
mod io;
|
|
14
|
+
mod monitoring;
|
|
14
15
|
mod screens;
|
|
15
16
|
mod storage;
|
|
16
17
|
mod ui;
|
|
@@ -407,6 +408,113 @@ fn main() -> Result<()> {
|
|
|
407
408
|
}
|
|
408
409
|
app.request_render("run_quick_ok");
|
|
409
410
|
}
|
|
411
|
+
// Portal Monitoring Phase 2: monitoring.refresh (section slice)
|
|
412
|
+
else if Some(&resp.id) == app.state.pending_monitoring_refresh_id.as_ref() {
|
|
413
|
+
app.state.pending_monitoring_refresh_id = None;
|
|
414
|
+
app.state.portal_monitoring.section_refresh_loading = None;
|
|
415
|
+
if obj.get("monitoringRefresh").and_then(|v| v.as_bool()) == Some(true) {
|
|
416
|
+
let section_str =
|
|
417
|
+
obj.get("section").and_then(|v| v.as_str()).unwrap_or("");
|
|
418
|
+
use crate::monitoring::MonitoringSection;
|
|
419
|
+
if let Some(sec) = MonitoringSection::from_cli_slug(section_str)
|
|
420
|
+
{
|
|
421
|
+
let lines: Vec<String> = obj
|
|
422
|
+
.get("lines")
|
|
423
|
+
.and_then(|a| a.as_array())
|
|
424
|
+
.map(|arr| {
|
|
425
|
+
arr.iter()
|
|
426
|
+
.filter_map(|v| {
|
|
427
|
+
v.as_str().map(|s| s.to_string())
|
|
428
|
+
})
|
|
429
|
+
.collect()
|
|
430
|
+
})
|
|
431
|
+
.unwrap_or_default();
|
|
432
|
+
app.state
|
|
433
|
+
.portal_monitoring
|
|
434
|
+
.section_overrides
|
|
435
|
+
.insert(sec, lines);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
app.request_render("monitoring_refresh_ok");
|
|
439
|
+
}
|
|
440
|
+
// Portal Monitoring Phase 2: monitoring.logs
|
|
441
|
+
else if Some(&resp.id) == app.state.pending_monitoring_logs_id.as_ref() {
|
|
442
|
+
app.state.pending_monitoring_logs_id = None;
|
|
443
|
+
app.state.portal_monitoring.logs_fetch_loading = false;
|
|
444
|
+
if obj.get("monitoringLogs").and_then(|v| v.as_bool()) == Some(true) {
|
|
445
|
+
let lines: Vec<String> = obj
|
|
446
|
+
.get("logs")
|
|
447
|
+
.and_then(|a| a.as_array())
|
|
448
|
+
.map(|arr| {
|
|
449
|
+
arr.iter()
|
|
450
|
+
.filter_map(|entry| {
|
|
451
|
+
let e = entry.as_object()?;
|
|
452
|
+
let ts = e
|
|
453
|
+
.get("timestamp")
|
|
454
|
+
.and_then(|v| v.as_str())
|
|
455
|
+
.unwrap_or("");
|
|
456
|
+
let lvl = e
|
|
457
|
+
.get("level")
|
|
458
|
+
.and_then(|v| v.as_str())
|
|
459
|
+
.unwrap_or("");
|
|
460
|
+
let msg = e
|
|
461
|
+
.get("message")
|
|
462
|
+
.and_then(|v| v.as_str())
|
|
463
|
+
.unwrap_or("");
|
|
464
|
+
if ts.is_empty()
|
|
465
|
+
&& lvl.is_empty()
|
|
466
|
+
&& msg.is_empty()
|
|
467
|
+
{
|
|
468
|
+
return None;
|
|
469
|
+
}
|
|
470
|
+
Some(format!("{} [{}] {}", ts, lvl, msg))
|
|
471
|
+
})
|
|
472
|
+
.collect()
|
|
473
|
+
})
|
|
474
|
+
.unwrap_or_default();
|
|
475
|
+
use crate::monitoring::MonitoringSection;
|
|
476
|
+
app.state.portal_monitoring.section_overrides.insert(
|
|
477
|
+
MonitoringSection::Logs,
|
|
478
|
+
lines,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
app.request_render("monitoring_logs_ok");
|
|
482
|
+
}
|
|
483
|
+
// Portal Monitoring Phase 3: monitoring.drill (metrics sub-panels / dependency detail)
|
|
484
|
+
else if Some(&resp.id) == app.state.pending_monitoring_drill_id.as_ref() {
|
|
485
|
+
app.state.pending_monitoring_drill_id = None;
|
|
486
|
+
app.state.portal_monitoring.metrics_drill_loading = false;
|
|
487
|
+
app.state.portal_monitoring.section_refresh_loading = None;
|
|
488
|
+
if obj.get("monitoringDrill").and_then(|v| v.as_bool()) == Some(true) {
|
|
489
|
+
let target = obj.get("target").and_then(|v| v.as_str()).unwrap_or("");
|
|
490
|
+
let lines: Vec<String> = obj
|
|
491
|
+
.get("lines")
|
|
492
|
+
.and_then(|a| a.as_array())
|
|
493
|
+
.map(|arr| {
|
|
494
|
+
arr.iter()
|
|
495
|
+
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
|
496
|
+
.collect()
|
|
497
|
+
})
|
|
498
|
+
.unwrap_or_default();
|
|
499
|
+
use crate::monitoring::{MetricsDrillPanel, MonitoringSection};
|
|
500
|
+
if target == "dependencies_detail" {
|
|
501
|
+
app.state.portal_monitoring.section_overrides.insert(
|
|
502
|
+
MonitoringSection::Dependencies,
|
|
503
|
+
lines,
|
|
504
|
+
);
|
|
505
|
+
} else if target == "system_diagnostics" {
|
|
506
|
+
app.state.portal_monitoring.section_overrides.insert(
|
|
507
|
+
MonitoringSection::System,
|
|
508
|
+
lines,
|
|
509
|
+
);
|
|
510
|
+
} else if let Some(panel) = MetricsDrillPanel::from_cli_target(target)
|
|
511
|
+
{
|
|
512
|
+
app.state.portal_monitoring.metrics_drill = Some(panel);
|
|
513
|
+
app.state.portal_monitoring.metrics_drill_lines = lines;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
app.request_render("monitoring_drill_ok");
|
|
517
|
+
}
|
|
410
518
|
// Portal Monitoring: gateway.observability (/metrics snapshot)
|
|
411
519
|
else if Some(&resp.id) == app.state.pending_gateway_observability_id.as_ref() {
|
|
412
520
|
app.state.pending_gateway_observability_id = None;
|
|
@@ -418,6 +526,8 @@ fn main() -> Result<()> {
|
|
|
418
526
|
.and_then(|v| v.as_str())
|
|
419
527
|
.unwrap_or("")
|
|
420
528
|
.to_string();
|
|
529
|
+
// Snapshot markers — keep substring order compatible with
|
|
530
|
+
// `portal_monitoring::SNAPSHOT_MARKER_*` (CLI healthLines + layout below).
|
|
421
531
|
let mut lines: Vec<String> = vec![
|
|
422
532
|
"Portal snapshot (health + metrics on each refresh / auto poll)."
|
|
423
533
|
.to_string(),
|
|
@@ -447,8 +557,92 @@ fn main() -> Result<()> {
|
|
|
447
557
|
app.state.portal_monitoring.last_http_for_delta = Some(c);
|
|
448
558
|
app.state.portal_monitoring.last_instant_for_delta =
|
|
449
559
|
Some(std::time::Instant::now());
|
|
560
|
+
app.state
|
|
561
|
+
.portal_monitoring
|
|
562
|
+
.http_total_sparkline
|
|
563
|
+
.push_back(c);
|
|
564
|
+
while app.state.portal_monitoring.http_total_sparkline.len() > 48 {
|
|
565
|
+
app.state
|
|
566
|
+
.portal_monitoring
|
|
567
|
+
.http_total_sparkline
|
|
568
|
+
.pop_front();
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if let Some(totals) = obj.get("totals").and_then(|v| v.as_object()) {
|
|
573
|
+
app.state.portal_monitoring.metric_history.push_back(
|
|
574
|
+
crate::app::metric_history_entry_from_totals(
|
|
575
|
+
if snapshot.is_empty() {
|
|
576
|
+
"—".to_string()
|
|
577
|
+
} else {
|
|
578
|
+
snapshot.clone()
|
|
579
|
+
},
|
|
580
|
+
totals,
|
|
581
|
+
),
|
|
582
|
+
);
|
|
583
|
+
while app.state.portal_monitoring.metric_history.len() > 720 {
|
|
584
|
+
app.state.portal_monitoring.metric_history.pop_front();
|
|
450
585
|
}
|
|
451
586
|
}
|
|
587
|
+
// Phase 5 v1: dependency alerts are text-derived from
|
|
588
|
+
// observability health lines, not a structured readiness enum.
|
|
589
|
+
let dependency_status = {
|
|
590
|
+
let mut status = if obj
|
|
591
|
+
.get("healthOk")
|
|
592
|
+
.and_then(|v| v.as_bool())
|
|
593
|
+
== Some(false)
|
|
594
|
+
{
|
|
595
|
+
"unhealthy"
|
|
596
|
+
} else {
|
|
597
|
+
"healthy"
|
|
598
|
+
};
|
|
599
|
+
if let Some(arr) = obj.get("healthLines").and_then(|a| a.as_array()) {
|
|
600
|
+
for v in arr {
|
|
601
|
+
if let Some(s) = v.as_str() {
|
|
602
|
+
let l = s.to_lowercase();
|
|
603
|
+
if l.contains("✗")
|
|
604
|
+
|| l.contains(": down")
|
|
605
|
+
|| l.contains("not ready")
|
|
606
|
+
{
|
|
607
|
+
status = "unhealthy";
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
if l.contains("⚠")
|
|
611
|
+
|| l.contains("degraded")
|
|
612
|
+
|| l.contains("warning")
|
|
613
|
+
{
|
|
614
|
+
status = "degraded";
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
status.to_string()
|
|
620
|
+
};
|
|
621
|
+
if let Some(prev) = app
|
|
622
|
+
.state
|
|
623
|
+
.portal_monitoring
|
|
624
|
+
.last_dependency_status
|
|
625
|
+
.clone()
|
|
626
|
+
{
|
|
627
|
+
if prev != dependency_status {
|
|
628
|
+
app.state.portal_monitoring.dependency_alerts.push_back(
|
|
629
|
+
crate::app::DependencyAlertEntry {
|
|
630
|
+
timestamp: if snapshot.is_empty() {
|
|
631
|
+
"—".to_string()
|
|
632
|
+
} else {
|
|
633
|
+
snapshot.clone()
|
|
634
|
+
},
|
|
635
|
+
previous: prev,
|
|
636
|
+
current: dependency_status.clone(),
|
|
637
|
+
},
|
|
638
|
+
);
|
|
639
|
+
while app.state.portal_monitoring.dependency_alerts.len() > 10 {
|
|
640
|
+
app.state.portal_monitoring.dependency_alerts.pop_front();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
app.state.portal_monitoring.last_dependency_status =
|
|
645
|
+
Some(dependency_status);
|
|
452
646
|
lines.push(String::new());
|
|
453
647
|
if let Some(arr) = obj.get("healthLines").and_then(|a| a.as_array()) {
|
|
454
648
|
for v in arr {
|
|
@@ -495,6 +689,12 @@ fn main() -> Result<()> {
|
|
|
495
689
|
}
|
|
496
690
|
app.state.portal_monitoring.last_updated = Some(snapshot);
|
|
497
691
|
app.state.portal_monitoring.content_lines = lines;
|
|
692
|
+
app.state.portal_monitoring.section_overrides.clear();
|
|
693
|
+
app.state.portal_monitoring.section_refresh_loading = None;
|
|
694
|
+
app.state.portal_monitoring.logs_fetch_loading = false;
|
|
695
|
+
app.state.portal_monitoring.metrics_drill = None;
|
|
696
|
+
app.state.portal_monitoring.metrics_drill_lines.clear();
|
|
697
|
+
app.state.portal_monitoring.metrics_drill_loading = false;
|
|
498
698
|
app.state.portal_monitoring.error = None;
|
|
499
699
|
app.state.portal_monitoring.scroll_offset = 0;
|
|
500
700
|
} else {
|
|
@@ -593,8 +793,13 @@ fn main() -> Result<()> {
|
|
|
593
793
|
app.add_log(format!("✓ [{}] Disconnected from Gateway", short_id));
|
|
594
794
|
app.add_log(format!("[MODE] Switched to LOCAL mode"));
|
|
595
795
|
app.state.pending_gateway_observability_id = None;
|
|
796
|
+
app.state.pending_monitoring_refresh_id = None;
|
|
797
|
+
app.state.pending_monitoring_logs_id = None;
|
|
798
|
+
app.state.pending_monitoring_drill_id = None;
|
|
596
799
|
app.state.portal_observability_last_poll = None;
|
|
597
800
|
app.state.portal_monitoring = crate::app::PortalMonitoringState::default();
|
|
801
|
+
app.state.advanced_monitoring =
|
|
802
|
+
crate::app::AdvancedMonitoringState::default();
|
|
598
803
|
if let Some(ref ws) = ws_client {
|
|
599
804
|
let _ = ws.send_command("system.status", None);
|
|
600
805
|
}
|
|
@@ -809,6 +1014,46 @@ fn main() -> Result<()> {
|
|
|
809
1014
|
app.state.portal_monitoring.error = Some(error_msg.clone());
|
|
810
1015
|
app.request_render("portal_observability_err");
|
|
811
1016
|
}
|
|
1017
|
+
else if Some(&resp.id) == app.state.pending_monitoring_refresh_id.as_ref() {
|
|
1018
|
+
app.state.pending_monitoring_refresh_id = None;
|
|
1019
|
+
app.state.portal_monitoring.section_refresh_loading = None;
|
|
1020
|
+
app.state.portal_monitoring.error = Some(format!(
|
|
1021
|
+
"monitoring.refresh failed: {}",
|
|
1022
|
+
error_msg
|
|
1023
|
+
));
|
|
1024
|
+
app.add_log(format!(
|
|
1025
|
+
"✗ [{}] monitoring.refresh failed: {}",
|
|
1026
|
+
short_id, error_msg
|
|
1027
|
+
));
|
|
1028
|
+
app.request_render("monitoring_refresh_err");
|
|
1029
|
+
}
|
|
1030
|
+
else if Some(&resp.id) == app.state.pending_monitoring_logs_id.as_ref() {
|
|
1031
|
+
app.state.pending_monitoring_logs_id = None;
|
|
1032
|
+
app.state.portal_monitoring.logs_fetch_loading = false;
|
|
1033
|
+
app.state.portal_monitoring.error = Some(format!(
|
|
1034
|
+
"monitoring.logs failed: {}",
|
|
1035
|
+
error_msg
|
|
1036
|
+
));
|
|
1037
|
+
app.add_log(format!(
|
|
1038
|
+
"✗ [{}] monitoring.logs failed: {}",
|
|
1039
|
+
short_id, error_msg
|
|
1040
|
+
));
|
|
1041
|
+
app.request_render("monitoring_logs_err");
|
|
1042
|
+
}
|
|
1043
|
+
else if Some(&resp.id) == app.state.pending_monitoring_drill_id.as_ref() {
|
|
1044
|
+
app.state.pending_monitoring_drill_id = None;
|
|
1045
|
+
app.state.portal_monitoring.metrics_drill_loading = false;
|
|
1046
|
+
app.state.portal_monitoring.section_refresh_loading = None;
|
|
1047
|
+
app.state.portal_monitoring.error = Some(format!(
|
|
1048
|
+
"monitoring.drill failed: {}",
|
|
1049
|
+
error_msg
|
|
1050
|
+
));
|
|
1051
|
+
app.add_log(format!(
|
|
1052
|
+
"✗ [{}] monitoring.drill failed: {}",
|
|
1053
|
+
short_id, error_msg
|
|
1054
|
+
));
|
|
1055
|
+
app.request_render("monitoring_drill_err");
|
|
1056
|
+
}
|
|
812
1057
|
// Generic error handling
|
|
813
1058
|
else {
|
|
814
1059
|
// Try to extract command name from response for better error messages
|
|
@@ -856,6 +1101,9 @@ fn main() -> Result<()> {
|
|
|
856
1101
|
if app.state.navigation.current_screen() == &Screen::PortalMonitoring
|
|
857
1102
|
&& app.state.operation_mode == OperationMode::Connected
|
|
858
1103
|
&& app.state.pending_gateway_observability_id.is_none()
|
|
1104
|
+
&& app.state.pending_monitoring_refresh_id.is_none()
|
|
1105
|
+
&& app.state.pending_monitoring_logs_id.is_none()
|
|
1106
|
+
&& app.state.pending_monitoring_drill_id.is_none()
|
|
859
1107
|
{
|
|
860
1108
|
let interval = Duration::from_secs(5);
|
|
861
1109
|
let due = app
|