use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
use ratatui::Frame;
use crate::app::App;
use crate::theme;
fn event_icon(event_type: &str) -> &'static str {
if event_type.starts_with("passport") {
"\u{25a0}" } else if event_type.starts_with("scan") {
"\u{25cb}" } else if event_type.starts_with("fix") {
"\u{25b6}" } else if event_type.starts_with("fria") {
"\u{25a1}" } else if event_type.starts_with("evidence") {
"\u{2261}" } else if event_type.starts_with("worker") {
"\u{25c6}" } else if event_type.starts_with("adversarial") {
"\u{26a0}" } else {
"\u{2022}" }
}
pub(super) fn render_audit_panel(frame: &mut Frame, area: Rect, app: &App) {
let t = theme::theme();
let pv = &app.passport_view;
let block = Block::default()
.borders(Borders::LEFT)
.border_style(Style::default().fg(t.border));
let inner = block.inner(area);
frame.render_widget(block, area);
let w = inner.width.saturating_sub(4) as usize;
let mut lines: Vec<Line<'_>> = Vec::new();
lines.push(Line::from(Span::styled(
" Audit Trail",
Style::default().fg(t.accent).add_modifier(Modifier::BOLD),
)));
lines.push(Line::from(Span::styled(
format!(" {}", "\u{2500}".repeat(w)),
Style::default().fg(t.border),
)));
lines.push(Line::raw(""));
if let Some(entries) = &pv.audit_entries {
if entries.is_empty() {
lines.push(Line::from(Span::styled(
" No audit events recorded.",
Style::default().fg(t.muted),
)));
} else {
lines.push(Line::from(Span::styled(
format!(" {} recent events", entries.len()),
Style::default().fg(t.muted),
)));
lines.push(Line::raw(""));
let max_rows = inner.height.saturating_sub(10) as usize;
for entry in entries.iter().rev().take(max_rows) {
let event_type = entry
.get("eventType")
.and_then(|v| v.as_str())
.unwrap_or("?");
let timestamp = entry
.get("timestamp")
.and_then(|v| v.as_str())
.unwrap_or("?");
let agent_name = entry
.get("agentName")
.and_then(|v| v.as_str())
.unwrap_or("");
let time_display = if timestamp.len() >= 16 {
×tamp[..16]
} else {
timestamp
};
let icon = event_icon(event_type);
let event_display = event_type.replace(['.', '_'], " ");
let truncated =
crate::views::truncate_str(&event_display, w.saturating_sub(22));
let mut spans = vec![
Span::styled(
format!(" {icon} "),
Style::default().fg(t.fg),
),
Span::styled(
format!("{time_display} "),
Style::default().fg(t.muted),
),
Span::styled(truncated, Style::default().fg(t.fg)),
];
if !agent_name.is_empty() {
spans.push(Span::styled(
format!(" ({agent_name})"),
Style::default().fg(t.accent),
));
}
lines.push(Line::from(spans));
}
}
} else {
lines.push(Line::from(Span::styled(
" Loading audit trail...",
Style::default().fg(t.muted),
)));
lines.push(Line::from(Span::styled(
" Press [a] to refresh",
Style::default().fg(t.muted),
)));
}
lines.push(Line::raw(""));
lines.push(Line::from(Span::styled(
format!(" {}", "\u{2500}".repeat(w)),
Style::default().fg(t.border),
)));
lines.push(Line::from(vec![
Span::styled(" [a] ", Style::default().fg(t.accent)),
Span::styled("Refresh ", Style::default().fg(t.fg)),
Span::styled("[Esc] ", Style::default().fg(t.accent)),
Span::styled("Back", Style::default().fg(t.fg)),
]));
frame.render_widget(
Paragraph::new(lines).wrap(Wrap { trim: false }),
inner,
);
}