4runr-os 2.10.74 → 2.10.76
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/package-lock.json +55 -55
- package/dist/tui-handlers.d.ts.map +1 -1
- package/dist/tui-handlers.js +36 -11
- package/dist/tui-handlers.js.map +1 -1
- package/mk3-tui/binaries/win32-x64/mk3-tui.exe +0 -0
- package/mk3-tui/src/app.rs +188 -49
- package/mk3-tui/src/main.rs +107 -0
- package/mk3-tui/src/screens/mod.rs +3 -1
- package/mk3-tui/src/ui/mod.rs +1 -0
- package/mk3-tui/src/ui/shield_dashboard.rs +723 -0
- package/package.json +2 -2
package/mk3-tui/src/app.rs
CHANGED
|
@@ -5,6 +5,7 @@ use crate::storage::Cache;
|
|
|
5
5
|
use crate::ui::agent_builder::AgentBuilderState;
|
|
6
6
|
use crate::ui::run_manager::RunManagerState;
|
|
7
7
|
use crate::ui::sentinel_config::SentinelConfigState;
|
|
8
|
+
use crate::ui::shield_dashboard::ShieldDashboardState;
|
|
8
9
|
use crate::ui::settings::SettingsState;
|
|
9
10
|
use crate::websocket::WebSocketClient;
|
|
10
11
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
|
|
@@ -431,6 +432,7 @@ pub struct AppState {
|
|
|
431
432
|
pub agent_builder: AgentBuilderState,
|
|
432
433
|
pub run_manager: RunManagerState,
|
|
433
434
|
pub sentinel_config: SentinelConfigState,
|
|
435
|
+
pub shield_dashboard: ShieldDashboardState,
|
|
434
436
|
pub settings: SettingsState,
|
|
435
437
|
pub agent_list: AgentListState,
|
|
436
438
|
pub connection_portal: ConnectionPortalState,
|
|
@@ -463,6 +465,7 @@ pub struct AppState {
|
|
|
463
465
|
|
|
464
466
|
pub pending_sentinel_load_id: Option<String>,
|
|
465
467
|
pub pending_sentinel_apply_id: Option<String>,
|
|
468
|
+
pub pending_shield_load_id: Option<String>,
|
|
466
469
|
|
|
467
470
|
// Deletion confirmation (Step 5.5)
|
|
468
471
|
pub agent_delete_requested: bool,
|
|
@@ -530,6 +533,7 @@ impl Default for AppState {
|
|
|
530
533
|
agent_builder: AgentBuilderState::default(),
|
|
531
534
|
run_manager: RunManagerState::default(),
|
|
532
535
|
sentinel_config: SentinelConfigState::default(),
|
|
536
|
+
shield_dashboard: ShieldDashboardState::default(),
|
|
533
537
|
settings: SettingsState::default(),
|
|
534
538
|
agent_list: AgentListState::default(),
|
|
535
539
|
connection_portal: ConnectionPortalState::default(),
|
|
@@ -554,6 +558,7 @@ impl Default for AppState {
|
|
|
554
558
|
pending_run_quick_id: None,
|
|
555
559
|
pending_sentinel_load_id: None,
|
|
556
560
|
pending_sentinel_apply_id: None,
|
|
561
|
+
pending_shield_load_id: None,
|
|
557
562
|
agent_delete_requested: false,
|
|
558
563
|
operation_mode: OperationMode::Local,
|
|
559
564
|
cache: Cache::new().ok(),
|
|
@@ -832,6 +837,59 @@ impl App {
|
|
|
832
837
|
}
|
|
833
838
|
}
|
|
834
839
|
|
|
840
|
+
pub fn open_shield_dashboard(&mut self, ws: Option<&WebSocketClient>) {
|
|
841
|
+
self.push_overlay(Screen::ShieldDashboard);
|
|
842
|
+
self.state
|
|
843
|
+
.logs
|
|
844
|
+
.push_back("[NAV] Opening Shield (production safety layer)...".into());
|
|
845
|
+
if self.state.operation_mode == OperationMode::Connected {
|
|
846
|
+
if let Some(ws) = ws {
|
|
847
|
+
self.begin_shield_load_request(ws, false);
|
|
848
|
+
}
|
|
849
|
+
} else {
|
|
850
|
+
self.state.shield_dashboard.loading = false;
|
|
851
|
+
self.state.shield_dashboard.error = Some(
|
|
852
|
+
"Connect to Gateway first (connect portal).".to_string(),
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
self.request_immediate_render("open_shield_dashboard");
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
pub fn begin_shield_load_request(&mut self, ws: &WebSocketClient, force: bool) {
|
|
859
|
+
if self.state.operation_mode != OperationMode::Connected {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if !force && self.state.pending_shield_load_id.is_some() {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if force {
|
|
866
|
+
self.state.pending_shield_load_id = None;
|
|
867
|
+
}
|
|
868
|
+
self.state.shield_dashboard.mark_loading();
|
|
869
|
+
self.state.shield_dashboard.error = None;
|
|
870
|
+
if let Ok(id) = ws.send_command("shield.load", None) {
|
|
871
|
+
self.state.pending_shield_load_id = Some(id);
|
|
872
|
+
} else {
|
|
873
|
+
self.state.shield_dashboard.fail_loading(
|
|
874
|
+
"Failed to send shield.load to CLI.".to_string(),
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
pub fn open_run_from_shield_block(&mut self, ws: Option<&WebSocketClient>, run_id: &str) {
|
|
880
|
+
self.push_overlay(Screen::RunManager);
|
|
881
|
+
self.state.run_manager.detail_run_id = Some(run_id.to_string());
|
|
882
|
+
self.add_log(format!(
|
|
883
|
+
"[SHIELD] Opening blocked run {} in Run Manager",
|
|
884
|
+
&run_id[run_id.len().saturating_sub(8)..]
|
|
885
|
+
));
|
|
886
|
+
if let Some(ws) = ws {
|
|
887
|
+
self.begin_run_list_request(ws, false);
|
|
888
|
+
self.begin_run_get_request(ws, run_id);
|
|
889
|
+
}
|
|
890
|
+
self.request_immediate_render("shield_open_run");
|
|
891
|
+
}
|
|
892
|
+
|
|
835
893
|
pub fn open_sentinel_config(&mut self, ws: Option<&WebSocketClient>) {
|
|
836
894
|
self.state.pending_sentinel_load_id = None;
|
|
837
895
|
self.state.pending_sentinel_apply_id = None;
|
|
@@ -1320,6 +1378,11 @@ impl App {
|
|
|
1320
1378
|
return self.handle_sentinel_config_input(key, ws_client);
|
|
1321
1379
|
}
|
|
1322
1380
|
|
|
1381
|
+
// === SHIELD DASHBOARD INPUT HANDLING ===
|
|
1382
|
+
if self.state.navigation.current_screen() == &Screen::ShieldDashboard {
|
|
1383
|
+
return self.handle_shield_dashboard_input(key, ws_client);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1323
1386
|
// === SETTINGS INPUT HANDLING ===
|
|
1324
1387
|
if self.state.navigation.current_screen() == &Screen::Settings {
|
|
1325
1388
|
return self.handle_settings_input(key);
|
|
@@ -1493,6 +1556,10 @@ impl App {
|
|
|
1493
1556
|
" sentinel - Configure Sentinel policies (templates)"
|
|
1494
1557
|
.to_string(),
|
|
1495
1558
|
);
|
|
1559
|
+
self.state.logs.push_back(
|
|
1560
|
+
" shield - Shield safety dashboard (live metrics + enforcement)"
|
|
1561
|
+
.to_string(),
|
|
1562
|
+
);
|
|
1496
1563
|
self.state.logs.push_back(
|
|
1497
1564
|
" build - Open Agent Builder (6-step wizard)"
|
|
1498
1565
|
.to_string(),
|
|
@@ -1574,55 +1641,8 @@ impl App {
|
|
|
1574
1641
|
"sentinel" | "sentinel config" | "sentinel policies" => {
|
|
1575
1642
|
self.open_sentinel_config(ws_client);
|
|
1576
1643
|
}
|
|
1577
|
-
"shield" | "shield status" => {
|
|
1578
|
-
|
|
1579
|
-
if let Some(ws) = ws_client {
|
|
1580
|
-
if let Ok(id) = ws.send_command("shield.status", None) {
|
|
1581
|
-
self.add_log(format!(
|
|
1582
|
-
"[SHIELD] Fetching live status (id: {})",
|
|
1583
|
-
&id[id.len().saturating_sub(8)..]
|
|
1584
|
-
));
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
} else {
|
|
1588
|
-
self.add_log(
|
|
1589
|
-
"[SHIELD] Connect to Gateway first.".to_string(),
|
|
1590
|
-
);
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
"shield probe" => {
|
|
1594
|
-
if self.state.operation_mode == OperationMode::Connected {
|
|
1595
|
-
if let Some(ws) = ws_client {
|
|
1596
|
-
if let Ok(id) = ws.send_command("shield.probe", None) {
|
|
1597
|
-
self.add_log(format!(
|
|
1598
|
-
"[SHIELD] Probing injection detector (id: {})",
|
|
1599
|
-
&id[id.len().saturating_sub(8)..]
|
|
1600
|
-
));
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
} else {
|
|
1604
|
-
self.add_log(
|
|
1605
|
-
"[SHIELD] Connect to Gateway first.".to_string(),
|
|
1606
|
-
);
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
"shield demo" => {
|
|
1610
|
-
if self.state.operation_mode == OperationMode::Connected {
|
|
1611
|
-
if let Some(ws) = ws_client {
|
|
1612
|
-
if let Ok(id) = ws.send_command("shield.demo", None) {
|
|
1613
|
-
self.add_log(format!(
|
|
1614
|
-
"[SHIELD] Starting demo run — check Run Manager (id: {})",
|
|
1615
|
-
&id[id.len().saturating_sub(8)..]
|
|
1616
|
-
));
|
|
1617
|
-
}
|
|
1618
|
-
self.push_overlay(Screen::RunManager);
|
|
1619
|
-
self.begin_run_list_request(ws, false);
|
|
1620
|
-
}
|
|
1621
|
-
} else {
|
|
1622
|
-
self.add_log(
|
|
1623
|
-
"[SHIELD] Connect to Gateway first.".to_string(),
|
|
1624
|
-
);
|
|
1625
|
-
}
|
|
1644
|
+
"shield" | "shield status" | "shield dashboard" => {
|
|
1645
|
+
self.open_shield_dashboard(ws_client);
|
|
1626
1646
|
}
|
|
1627
1647
|
"config" | "settings" => {
|
|
1628
1648
|
self.push_overlay(Screen::Settings);
|
|
@@ -1908,6 +1928,10 @@ impl App {
|
|
|
1908
1928
|
use crate::ui::sentinel_config;
|
|
1909
1929
|
sentinel_config::render(f, &self.state);
|
|
1910
1930
|
}
|
|
1931
|
+
Screen::ShieldDashboard => {
|
|
1932
|
+
use crate::ui::shield_dashboard;
|
|
1933
|
+
shield_dashboard::render(f, &self.state);
|
|
1934
|
+
}
|
|
1911
1935
|
Screen::Settings => {
|
|
1912
1936
|
use crate::ui::settings;
|
|
1913
1937
|
settings::render(f, &self.state);
|
|
@@ -2642,6 +2666,121 @@ impl App {
|
|
|
2642
2666
|
Ok(false)
|
|
2643
2667
|
}
|
|
2644
2668
|
|
|
2669
|
+
fn handle_shield_dashboard_input(
|
|
2670
|
+
&mut self,
|
|
2671
|
+
key: KeyEvent,
|
|
2672
|
+
ws_client: Option<&WebSocketClient>,
|
|
2673
|
+
) -> anyhow::Result<bool> {
|
|
2674
|
+
use crate::ui::shield_dashboard::ShieldTab;
|
|
2675
|
+
use crossterm::event::KeyModifiers;
|
|
2676
|
+
|
|
2677
|
+
if key.modifiers.contains(KeyModifiers::CONTROL) {
|
|
2678
|
+
match key.code {
|
|
2679
|
+
KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
|
|
2680
|
+
_ => return Ok(false),
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
match key.code {
|
|
2685
|
+
KeyCode::Tab => {
|
|
2686
|
+
self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.next();
|
|
2687
|
+
self.request_render("shield_tab");
|
|
2688
|
+
}
|
|
2689
|
+
KeyCode::BackTab => {
|
|
2690
|
+
self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.prev();
|
|
2691
|
+
self.request_render("shield_tab_back");
|
|
2692
|
+
}
|
|
2693
|
+
KeyCode::Left => {
|
|
2694
|
+
self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.prev();
|
|
2695
|
+
self.request_render("shield_tab_left");
|
|
2696
|
+
}
|
|
2697
|
+
KeyCode::Right => {
|
|
2698
|
+
self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.next();
|
|
2699
|
+
self.request_render("shield_tab_right");
|
|
2700
|
+
}
|
|
2701
|
+
KeyCode::Up => {
|
|
2702
|
+
if self.state.shield_dashboard.tab == ShieldTab::Enforcement
|
|
2703
|
+
&& self.state.shield_dashboard.selected_block_index > 0
|
|
2704
|
+
{
|
|
2705
|
+
self.state.shield_dashboard.selected_block_index -= 1;
|
|
2706
|
+
self.request_render("shield_block_up");
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
KeyCode::Down => {
|
|
2710
|
+
if self.state.shield_dashboard.tab == ShieldTab::Enforcement {
|
|
2711
|
+
let max = self.state.shield_dashboard.recent_blocks.len();
|
|
2712
|
+
if max > 0 && self.state.shield_dashboard.selected_block_index + 1 < max {
|
|
2713
|
+
self.state.shield_dashboard.selected_block_index += 1;
|
|
2714
|
+
self.request_render("shield_block_down");
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
KeyCode::Enter => {
|
|
2719
|
+
if self.state.shield_dashboard.tab == ShieldTab::Enforcement {
|
|
2720
|
+
if let Some(row) = self
|
|
2721
|
+
.state
|
|
2722
|
+
.shield_dashboard
|
|
2723
|
+
.recent_blocks
|
|
2724
|
+
.get(self.state.shield_dashboard.selected_block_index)
|
|
2725
|
+
{
|
|
2726
|
+
let run_id = row.id.clone();
|
|
2727
|
+
self.open_run_from_shield_block(ws_client, &run_id);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
KeyCode::Char('r') | KeyCode::Char('R') => {
|
|
2732
|
+
if self.state.operation_mode == OperationMode::Connected {
|
|
2733
|
+
if let Some(ws) = ws_client {
|
|
2734
|
+
self.begin_shield_load_request(ws, true);
|
|
2735
|
+
}
|
|
2736
|
+
} else {
|
|
2737
|
+
self.state.shield_dashboard.fail_loading(
|
|
2738
|
+
"Connect to Gateway first (connect portal).".to_string(),
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
self.request_render("shield_refresh");
|
|
2742
|
+
}
|
|
2743
|
+
KeyCode::Char('o') | KeyCode::Char('O') => {
|
|
2744
|
+
self.push_overlay(Screen::RunManager);
|
|
2745
|
+
if let Some(ws) = ws_client {
|
|
2746
|
+
self.begin_run_list_request(ws, false);
|
|
2747
|
+
}
|
|
2748
|
+
self.add_log("[SHIELD] Opened Run Manager".to_string());
|
|
2749
|
+
self.request_render("shield_open_runs");
|
|
2750
|
+
}
|
|
2751
|
+
KeyCode::Char('p') | KeyCode::Char('P') => {
|
|
2752
|
+
if self.state.operation_mode == OperationMode::Connected {
|
|
2753
|
+
self.state.navigation.navigate_to_base(Screen::PortalMonitoring);
|
|
2754
|
+
if let Some(ws) = ws_client {
|
|
2755
|
+
self.begin_portal_observability_request(ws);
|
|
2756
|
+
}
|
|
2757
|
+
self.add_log("[SHIELD] Opened Portal Monitoring".to_string());
|
|
2758
|
+
} else {
|
|
2759
|
+
self.add_log("[SHIELD] Connect to Gateway first.".to_string());
|
|
2760
|
+
}
|
|
2761
|
+
self.request_render("shield_open_monitoring");
|
|
2762
|
+
}
|
|
2763
|
+
KeyCode::Char('a') | KeyCode::Char('A') => {
|
|
2764
|
+
self.state.shield_dashboard.auto_refresh_enabled =
|
|
2765
|
+
!self.state.shield_dashboard.auto_refresh_enabled;
|
|
2766
|
+
let status = if self.state.shield_dashboard.auto_refresh_enabled {
|
|
2767
|
+
"ON"
|
|
2768
|
+
} else {
|
|
2769
|
+
"OFF"
|
|
2770
|
+
};
|
|
2771
|
+
self.add_log(format!("[SHIELD] Live refresh {}", status));
|
|
2772
|
+
self.request_render("shield_live_toggle");
|
|
2773
|
+
}
|
|
2774
|
+
KeyCode::Esc => {
|
|
2775
|
+
self.pop_overlay();
|
|
2776
|
+
self.request_render("shield_close");
|
|
2777
|
+
}
|
|
2778
|
+
_ => {}
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
Ok(false)
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2645
2784
|
// ============================================================
|
|
2646
2785
|
// SETTINGS INPUT HANDLING (Step 4.8)
|
|
2647
2786
|
// ============================================================
|
package/mk3-tui/src/main.rs
CHANGED
|
@@ -189,6 +189,44 @@ fn main() -> Result<()> {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
// Shield dashboard: live poll shield.load every ~5s when enabled
|
|
193
|
+
if matches!(current_screen, Screen::ShieldDashboard)
|
|
194
|
+
&& app.state.operation_mode == OperationMode::Connected
|
|
195
|
+
{
|
|
196
|
+
let shield_poll = {
|
|
197
|
+
let sd = &app.state.shield_dashboard;
|
|
198
|
+
if !sd.auto_refresh_enabled || sd.loading {
|
|
199
|
+
false
|
|
200
|
+
} else {
|
|
201
|
+
sd.last_refresh
|
|
202
|
+
.map(|lr| lr.elapsed() >= sd.auto_refresh_interval)
|
|
203
|
+
.unwrap_or(true)
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
if shield_poll {
|
|
207
|
+
if let Some(ws) = &ws_client {
|
|
208
|
+
app.begin_shield_load_request(ws, false);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Loading timeout — never spin forever if CLI deduped or dropped a response
|
|
213
|
+
let loading_stuck = {
|
|
214
|
+
let sd = &app.state.shield_dashboard;
|
|
215
|
+
sd.loading
|
|
216
|
+
&& sd
|
|
217
|
+
.loading_started
|
|
218
|
+
.map(|t| t.elapsed() >= std::time::Duration::from_secs(12))
|
|
219
|
+
.unwrap_or(false)
|
|
220
|
+
};
|
|
221
|
+
if loading_stuck {
|
|
222
|
+
app.state.shield_dashboard.fail_loading(
|
|
223
|
+
"Shield load timed out — press R to refresh.".to_string(),
|
|
224
|
+
);
|
|
225
|
+
app.state.pending_shield_load_id = None;
|
|
226
|
+
app.request_render("shield_load_timeout");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
192
230
|
// Run Manager: live poll run.list every ~3s when enabled (no manual R)
|
|
193
231
|
if matches!(current_screen, Screen::RunManager)
|
|
194
232
|
&& app.state.operation_mode == OperationMode::Connected
|
|
@@ -669,6 +707,66 @@ fn main() -> Result<()> {
|
|
|
669
707
|
}
|
|
670
708
|
app.request_render("run_quick_ok");
|
|
671
709
|
}
|
|
710
|
+
// Shield dashboard: load health + config + metrics + history
|
|
711
|
+
else if Some(&resp.id)
|
|
712
|
+
== app.state.pending_shield_load_id.as_ref()
|
|
713
|
+
{
|
|
714
|
+
app.state.pending_shield_load_id = None;
|
|
715
|
+
if obj.get("shieldLoad").and_then(|v| v.as_bool()) == Some(true)
|
|
716
|
+
{
|
|
717
|
+
use crate::ui::shield_dashboard::parse_shield_load;
|
|
718
|
+
let health_val = obj
|
|
719
|
+
.get("health")
|
|
720
|
+
.cloned()
|
|
721
|
+
.unwrap_or(serde_json::Value::Null);
|
|
722
|
+
let config_val = obj
|
|
723
|
+
.get("config")
|
|
724
|
+
.cloned()
|
|
725
|
+
.unwrap_or(serde_json::Value::Null);
|
|
726
|
+
let metrics_val = obj
|
|
727
|
+
.get("metrics")
|
|
728
|
+
.cloned()
|
|
729
|
+
.unwrap_or(serde_json::Value::Null);
|
|
730
|
+
let gateway_health_val = obj
|
|
731
|
+
.get("gatewayHealth")
|
|
732
|
+
.cloned()
|
|
733
|
+
.unwrap_or(serde_json::Value::Null);
|
|
734
|
+
let runs_val = obj
|
|
735
|
+
.get("recentRuns")
|
|
736
|
+
.cloned()
|
|
737
|
+
.unwrap_or(serde_json::Value::Null);
|
|
738
|
+
let warnings_vec: Vec<String> = obj
|
|
739
|
+
.get("warnings")
|
|
740
|
+
.and_then(|v| v.as_array())
|
|
741
|
+
.map(|a| {
|
|
742
|
+
a.iter()
|
|
743
|
+
.filter_map(|w| {
|
|
744
|
+
w.as_str().map(|s| s.to_string())
|
|
745
|
+
})
|
|
746
|
+
.collect()
|
|
747
|
+
})
|
|
748
|
+
.unwrap_or_default();
|
|
749
|
+
let (h, c, m, mut w, recent) = parse_shield_load(
|
|
750
|
+
&health_val,
|
|
751
|
+
&config_val,
|
|
752
|
+
&metrics_val,
|
|
753
|
+
&gateway_health_val,
|
|
754
|
+
&runs_val,
|
|
755
|
+
);
|
|
756
|
+
w.extend(warnings_vec);
|
|
757
|
+
app.state.shield_dashboard.apply_load(
|
|
758
|
+
&h, &c, &m, w, recent,
|
|
759
|
+
);
|
|
760
|
+
app.state.shield_mode = h.mode.clone();
|
|
761
|
+
app.state.shield_blocks_total = m.blocks;
|
|
762
|
+
app.state.shield_masks_total = m.masks;
|
|
763
|
+
app.add_log(format!(
|
|
764
|
+
"✓ [{}] Shield loaded — {} block(s), {} mask(s)",
|
|
765
|
+
short_id, m.blocks, m.masks
|
|
766
|
+
));
|
|
767
|
+
}
|
|
768
|
+
app.request_render("shield_loaded");
|
|
769
|
+
}
|
|
672
770
|
// Sentinel config: load templates + current + health
|
|
673
771
|
else if Some(&resp.id)
|
|
674
772
|
== app.state.pending_sentinel_load_id.as_ref()
|
|
@@ -1542,6 +1640,15 @@ fn main() -> Result<()> {
|
|
|
1542
1640
|
"✗ [{}] run.quick failed: {}",
|
|
1543
1641
|
short_id, error_msg
|
|
1544
1642
|
));
|
|
1643
|
+
} else if Some(&resp.id) == app.state.pending_shield_load_id.as_ref() {
|
|
1644
|
+
app.state.pending_shield_load_id = None;
|
|
1645
|
+
app.state
|
|
1646
|
+
.shield_dashboard
|
|
1647
|
+
.fail_loading(error_msg.clone());
|
|
1648
|
+
app.add_log(format!(
|
|
1649
|
+
"✗ [{}] shield.load failed: {}",
|
|
1650
|
+
short_id, error_msg
|
|
1651
|
+
));
|
|
1545
1652
|
} else if Some(&resp.id) == app.state.pending_sentinel_load_id.as_ref()
|
|
1546
1653
|
{
|
|
1547
1654
|
app.state.pending_sentinel_load_id = None;
|
|
@@ -18,6 +18,7 @@ pub enum Screen {
|
|
|
18
18
|
AgentBuilder,
|
|
19
19
|
RunManager,
|
|
20
20
|
SentinelConfig,
|
|
21
|
+
ShieldDashboard,
|
|
21
22
|
Settings,
|
|
22
23
|
AgentList,
|
|
23
24
|
ConnectionPortal,
|
|
@@ -40,7 +41,7 @@ impl Screen {
|
|
|
40
41
|
pub fn is_overlay(&self) -> bool {
|
|
41
42
|
matches!(
|
|
42
43
|
self,
|
|
43
|
-
Screen::AgentBuilder | Screen::RunManager | Screen::SentinelConfig | Screen::Settings | Screen::AgentList
|
|
44
|
+
Screen::AgentBuilder | Screen::RunManager | Screen::SentinelConfig | Screen::ShieldDashboard | Screen::Settings | Screen::AgentList
|
|
44
45
|
)
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -70,6 +71,7 @@ impl Screen {
|
|
|
70
71
|
Screen::AgentBuilder => "Agent Builder",
|
|
71
72
|
Screen::RunManager => "Run Manager",
|
|
72
73
|
Screen::SentinelConfig => "Sentinel Config",
|
|
74
|
+
Screen::ShieldDashboard => "Shield",
|
|
73
75
|
Screen::Settings => "Settings",
|
|
74
76
|
Screen::AgentList => "Agent List",
|
|
75
77
|
Screen::ConnectionPortal => "Connection Portal",
|