- 1
//! Screen 3: Remote Administration & Settings Cockpit. - 2
//! - 3
//! All data is real: - 4
//! - Workspace from `/health` cwd - 5
//! - Permission mode from `/health` + PATCH via server API - 6
//! - MCP inventory from `GET /config/mcp` - 7
//! - Gateway/bot status from `GET /gateway/bots` + `GET /gateway/approvals` - 8
//! - Pending approval queue from SSE `ApprovalRequested` events - 9
- 10
use ratatui::buffer::Buffer; - 11
use ratatui::layout::{Constraint, Direction, Layout, Rect}; - 12
use ratatui::style::Modifier; - 13
use ratatui::text::{Line, Span}; - 14
use ratatui::widgets::{Block, Borders, Paragraph, Widget}; - 15
- 16
use crate::api::{GatewayApprovals, HealthReport, McpServerDef, SessionInfo}; - 17
use crate::app::{DeckFocus, McpRow}; - 18
use crate::theme::{Symbols, Theme}; - 19
use std::collections::HashMap; - 20
- 21
pub struct AdminView<'a> { - 22
pub theme: &'a Theme, - 23
pub selected_permission_mode: &'a str, - 24
pub approval_mode: &'a str, - 25
pub workspace_name: &'a str, - 26
pub deck_focus: DeckFocus, - 27
pub pending_chat_status: Option<bool>, - 28
pub mcp_rows: &'a [McpRow], - 29
pub mcp_servers: &'a HashMap<String, McpServerDef>, - 30
pub gateway: &'a GatewayApprovals, - 31
pub sessions: &'a [SessionInfo], - 32
pub pending_chats: &'a [crate::app::PendingChatEntry], - 33
pub health: &'a HealthReport, - 34
pub connected: bool, - 35
} - 36
- 37
impl<'a> Widget for AdminView<'a> { - 38
fn render(self, area: Rect, buf: &mut Buffer) { - 39
let left_border = if self.deck_focus == DeckFocus::Left { - 40
self.theme.style_border_focus() - 41
} else { - 42
self.theme.style_border() - 43
}; - 44
let right_border = if self.deck_focus == DeckFocus::Right { - 45
self.theme.style_border_focus() - 46
} else { - 47
self.theme.style_border() - 48
}; - 49
- 50
let deck_chunks = Layout::default() - 51
.direction(Direction::Horizontal) - 52
.constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) - 53
.split(area); - 54
- 55
// ------------------------------------------------------------- - 56
// LEFT DECK: CorePool Workspaces & Security Engine - 57
// ------------------------------------------------------------- - 58
let left_chunks = Layout::default() - 59
.direction(Direction::Vertical) - 60
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) - 61
.split(deck_chunks[0]); - 62
- 63
// CorePool Workspaces — real from sessions/health - 64
let ws_lines: Vec<Line> = { - 65
let mut lines = vec![Line::from(vec![Span::styled( - 66
format!(" {} {} ", Symbols::CARET_EXPANDED, self.workspace_name), - 67
self.theme.style_accent().add_modifier(Modifier::BOLD), - 68
)])]; - 69
let session_count = self.sessions.len(); - 70
lines.push(Line::from(vec![ - 71
Span::styled(" sessions/ ", self.theme.style_card()), - 72
Span::styled( - 73
format!("[{} active]", session_count), - 74
self.theme.style_info(), - 75
), - 76
])); - 77
for s in self.sessions.iter().take(3) { - 78
lines.push(Line::from(vec![ - 79
Span::styled( - 80
format!(" ├── {} ", s.session_id), - 81
self.theme.style_card(), - 82
), - 83
Span::styled( - 84
format!("[{}]", s.status()), - 85
if s.status() == "active" { - 86
self.theme.style_ok() - 87
} else { - 88
self.theme.style_card() - 89
}, - 90
), - 91
])); - 92
} - 93
lines - 94
}; - 95
- 96
let ws_block = Block::default() - 97
.borders(Borders::ALL) - 98
.border_style(left_border) - 99
.title(" CorePool Multi-Tenant Workspaces [Tab] ") - 100
.title_style(self.theme.style_card()); - 101
- 102
Paragraph::new(ws_lines) - 103
.block(ws_block) - 104
.render(left_chunks[0], buf); - 105
- 106
// Security Engine — real permission mode from health - 107
let perm_lines = vec![ - 108
Line::from(vec![Span::styled( - 109
"PERMISSION MODES: ", - 110
self.theme.style_card().add_modifier(Modifier::BOLD), - 111
)]), - 112
Line::from(vec![ - 113
Span::styled( - 114
format!( - 115
" {} [ WorkspaceWrite ] ", - 116
if self.selected_permission_mode == "WorkspaceWrite" { - 117
Symbols::STATUS_ACTIVE - 118
} else { - 119
Symbols::STATUS_IDLE - 120
} - 121
), - 122
if self.selected_permission_mode == "WorkspaceWrite" { - 123
self.theme.style_ok().add_modifier(Modifier::BOLD) - 124
} else { - 125
self.theme.style_card() - 126
}, - 127
), - 128
Span::styled( - 129
"(Permits workspace reads & edits; escapes denied)", - 130
self.theme.style_card().add_modifier(Modifier::DIM), - 131
), - 132
]), - 133
Line::from(vec![ - 134
Span::styled( - 135
format!( - 136
" {} [ ReadOnly ] ", - 137
if self.selected_permission_mode == "ReadOnly" { - 138
Symbols::STATUS_ACTIVE - 139
} else { - 140
Symbols::STATUS_IDLE - 141
} - 142
), - 143
self.theme.style_card(), - 144
), - 145
Span::styled( - 146
"(Inspection only; all writes fail-closed)", - 147
self.theme.style_card().add_modifier(Modifier::DIM), - 148
), - 149
]), - 150
Line::from(vec![ - 151
Span::styled( - 152
format!( - 153
" {} [ FullAccess ] ", - 154
if self.selected_permission_mode == "FullAccess" { - 155
Symbols::STATUS_ACTIVE - 156
} else { - 157
Symbols::STATUS_IDLE - 158
} - 159
), - 160
self.theme.style_card(), - 161
), - 162
Span::styled( - 163
"(Human trust decision; unconstrained execution)", - 164
self.theme.style_card().add_modifier(Modifier::DIM), - 165
), - 166
]), - 167
Line::from(vec![ - 168
Span::styled( - 169
"APPROVALS POLICY: ", - 170
self.theme.style_card().add_modifier(Modifier::BOLD), - 171
), - 172
Span::styled( - 173
format!( - 174
" [ {} Ask ] [ {} AutoApprove ]", - 175
if self.approval_mode == "Ask" { - 176
Symbols::STATUS_ACTIVE - 177
} else { - 178
Symbols::STATUS_IDLE - 179
}, - 180
if self.approval_mode == "AutoApprove" { - 181
Symbols::STATUS_ACTIVE - 182
} else { - 183
Symbols::STATUS_IDLE - 184
} - 185
), - 186
self.theme.style_accent(), - 187
), - 188
]), - 189
]; - 190
- 191
let perm_block = Block::default() - 192
.borders(Borders::ALL) - 193
.border_style(left_border) - 194
.title(format!( - 195
" {} Security & Permissions Engine ", - 196
Symbols::STATUS_ACTIVE - 197
)) - 198
.title_style(self.theme.style_accent()); - 199
- 200
Paragraph::new(perm_lines) - 201
.block(perm_block) - 202
.render(left_chunks[1], buf); - 203
- 204
// ------------------------------------------------------------- - 205
// RIGHT DECK: MCP Servers, Channel Gateways, Pending Chat Queue - 206
// ------------------------------------------------------------- - 207
let right_chunks = Layout::default() - 208
.direction(Direction::Vertical) - 209
.constraints([ - 210
Constraint::Length(7), - 211
Constraint::Length(5), - 212
Constraint::Min(6), - 213
]) - 214
.split(deck_chunks[1]); - 215
- 216
// MCP Server Inventory — real from /config/mcp - 217
let mcp_lines: Vec<Line> = if self.mcp_rows.is_empty() { - 218
vec![ - 219
Line::from(vec![Span::styled( - 220
"SERVER NAME TOOLS MEMORY STATUS", - 221
self.theme.style_card().add_modifier(Modifier::BOLD), - 222
)]), - 223
Line::from(vec![Span::styled( - 224
"(no MCP servers configured)", - 225
self.theme.style_card().add_modifier(Modifier::DIM), - 226
)]), - 227
] - 228
} else { - 229
let mut lines = vec![Line::from(vec![Span::styled( - 230
"SERVER NAME TOOLS MEMORY STATUS", - 231
self.theme.style_card().add_modifier(Modifier::BOLD), - 232
)])]; - 233
for row in self.mcp_rows { - 234
lines.push(Line::from(vec![ - 235
Span::styled(format!("{:<14} ", row.name), self.theme.style_card()), - 236
Span::styled(format!("{:<8} ", row.tool_count), self.theme.style_card()), - 237
Span::styled( - 238
format!("{:<5.1}GB ", row.memory_mb), - 239
self.theme.style_card(), - 240
), - 241
Span::styled( - 242
match row.status { - 243
crate::app::McpStatus::Ok => "OK", - 244
crate::app::McpStatus::Error => "ERR", - 245
crate::app::McpStatus::Starting => "…", - 246
}, - 247
match row.status { - 248
crate::app::McpStatus::Ok => self.theme.style_ok(), - 249
crate::app::McpStatus::Error => self.theme.style_danger(), - 250
crate::app::McpStatus::Starting => self.theme.style_warn(), - 251
}, - 252
), - 253
])); - 254
} - 255
lines - 256
}; - 257
- 258
let mcp_block = Block::default() - 259
.borders(Borders::ALL) - 260
.border_style(right_border) - 261
.title(" MCP Server Inventory ") - 262
.title_style(self.theme.style_card()); - 263
- 264
Paragraph::new(mcp_lines) - 265
.block(mcp_block) - 266
.render(right_chunks[0], buf); - 267
- 268
// Channel Gateways — real from gateway approvals/bots data - 269
let gw_lines: Vec<Line> = { - 270
let mode = if self.gateway.enabled { - 271
"ENABLED" - 272
} else { - 273
"DISABLED" - 274
}; - 275
let mode_style = if self.gateway.enabled { - 276
self.theme.style_ok() - 277
} else { - 278
self.theme.style_warn() - 279
}; - 280
vec![ - 281
Line::from(vec![ - 282
Span::styled("Mode: ", self.theme.style_card()), - 283
Span::styled(mode, mode_style.add_modifier(Modifier::BOLD)), - 284
]), - 285
Line::from(vec![ - 286
Span::styled("Approver: ", self.theme.style_card()), - 287
Span::styled( - 288
self.gateway.approver.clone().unwrap_or("(none)".into()), - 289
self.theme.style_card(), - 290
), - 291
]), - 292
Line::from(vec![ - 293
Span::styled("Forwarding: ", self.theme.style_card()), - 294
Span::styled( - 295
format!( - 296
"{} ({}s)", - 297
if self.gateway.forwarding { "ON" } else { "OFF" }, - 298
self.gateway.timeout_secs - 299
), - 300
self.theme.style_info(), - 301
), - 302
]), - 303
] - 304
}; - 305
- 306
let gw_block = Block::default() - 307
.borders(Borders::ALL) - 308
.border_style(right_border) - 309
.title(" Channel Gateways ") - 310
.title_style(self.theme.style_card()); - 311
- 312
Paragraph::new(gw_lines) - 313
.block(gw_block) - 314
.render(right_chunks[1], buf); - 315
- 316
// Pending Authorization Queue — real from pending_chats (SSE-derived) - 317
let action_pill = match self.pending_chat_status { - 318
None => Line::from(vec![ - 319
Span::styled(" [ [y] Approve ] ", self.theme.style_tab_active()), - 320
Span::raw(" "), - 321
Span::styled( - 322
" [ [d] Deny ] ", - 323
self.theme.style_card().add_modifier(Modifier::BOLD), - 324
), - 325
]), - 326
Some(true) => Line::from(vec![Span::styled( - 327
" [ ✓ APPROVED & ALLOWED ] ", - 328
self.theme.style_ok().add_modifier(Modifier::BOLD), - 329
)]), - 330
Some(false) => Line::from(vec![Span::styled( - 331
" [ ✗ DENIED & BLOCKED ] ", - 332
self.theme.style_danger().add_modifier(Modifier::BOLD), - 333
)]), - 334
}; - 335
- 336
let queue_lines: Vec<Line> = if self.pending_chats.is_empty() { - 337
vec![ - 338
Line::from(vec![Span::styled( - 339
"INBOUND USER: ", - 340
self.theme.style_card(), - 341
)]), - 342
Line::from(vec![Span::styled( - 343
"(no pending approvals)", - 344
self.theme.style_card().add_modifier(Modifier::DIM), - 345
)]), - 346
action_pill, - 347
] - 348
} else { - 349
let chat = &self.pending_chats[0]; - 350
vec![ - 351
Line::from(vec![ - 352
Span::styled("INBOUND USER: ", self.theme.style_card()), - 353
Span::styled( - 354
format!("{} ({})", chat.sender, chat.surface), - 355
self.theme.style_accent().add_modifier(Modifier::BOLD), - 356
), - 357
]), - 358
Line::from(vec![ - 359
Span::styled("PROVENANCE: ", self.theme.style_card()), - 360
Span::styled(&chat.preview, self.theme.style_card()), - 361
]), - 362
action_pill, - 363
] - 364
}; - 365
- 366
let queue_block = Block::default() - 367
.borders(Borders::ALL) - 368
.border_style(right_border) - 369
.title(" Pending Chat Authorization Queue [!] ") - 370
.title_style(self.theme.style_accent()); - 371
- 372
Paragraph::new(queue_lines) - 373
.block(queue_block) - 374
.render(right_chunks[2], buf); - 375
} - 376
} - 377
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.