1use chrono::{DateTime, Utc};
2use std::collections::VecDeque;
3use theater::ChainEvent;
4use theater_server::ManagementResponse;
5
6#[derive(Debug, Clone)]
7pub struct TuiApp {
8 pub actor_id: String,
10 pub manifest_path: String,
11 pub start_time: DateTime<Utc>,
12
13 pub events: VecDeque<DisplayEvent>,
15 pub max_events: usize,
16 pub auto_scroll: bool,
17
18 pub lifecycle_events: Vec<LifecycleEvent>,
20 pub current_status: ActorStatus,
21 pub error_count: usize,
22 pub event_count: usize,
23
24 pub should_quit: bool,
26 pub paused: bool,
27}
28
29#[derive(Debug, Clone)]
30pub struct DisplayEvent {
31 pub timestamp: DateTime<Utc>,
32 pub event_type: String,
33 pub message: String,
34 pub details: Option<String>,
35 pub level: EventLevel,
36}
37
38#[derive(Debug, Clone)]
39pub struct LifecycleEvent {
40 pub timestamp: DateTime<Utc>,
41 pub event_type: LifecycleEventType,
42 pub message: String,
43}
44
45#[derive(Debug, Clone)]
46pub enum LifecycleEventType {
47 ActorStarted,
48 ActorStopped,
49 ActorError,
50 ActorResult,
51 StatusUpdate,
52}
53
54#[derive(Debug, Clone)]
55pub enum EventLevel {
56 Info,
57 Warning,
58 Error,
59}
60
61#[derive(Debug, Clone)]
62pub enum ActorStatus {
63 Starting,
64 Running,
65 Paused,
66 Stopped,
67 Error,
68}
69
70impl TuiApp {
71 pub fn new(actor_id: String, manifest_path: String) -> Self {
72 Self {
73 actor_id,
74 manifest_path,
75 start_time: Utc::now(),
76 events: VecDeque::new(),
77 max_events: 1000,
78 auto_scroll: true,
79 lifecycle_events: Vec::new(),
80 current_status: ActorStatus::Starting,
81 error_count: 0,
82 event_count: 0,
83 should_quit: false,
84 paused: false,
85 }
86 }
87
88 pub fn add_event(&mut self, event: DisplayEvent) {
89 if !self.paused {
90 self.event_count += 1;
91
92 if event.level == EventLevel::Error {
93 self.error_count += 1;
94 }
95
96 self.events.push_back(event);
97
98 while self.events.len() > self.max_events {
100 self.events.pop_front();
101 }
102 }
103 }
104
105 pub fn add_lifecycle_event(&mut self, event: LifecycleEvent) {
106 match &event.event_type {
107 LifecycleEventType::ActorStarted => {
108 self.current_status = ActorStatus::Running;
109 }
110 LifecycleEventType::ActorStopped => {
111 self.current_status = ActorStatus::Stopped;
112 }
113 LifecycleEventType::ActorError => {
114 self.current_status = ActorStatus::Error;
115 self.error_count += 1;
116 }
117 _ => {}
118 }
119
120 self.lifecycle_events.push(event);
121
122 if self.lifecycle_events.len() > 50 {
124 self.lifecycle_events.remove(0);
125 }
126 }
127
128 pub fn handle_management_response(&mut self, response: ManagementResponse) {
129 let timestamp = Utc::now();
130
131 match response {
132 ManagementResponse::ActorStarted { id } => {
133 let lifecycle_event = LifecycleEvent {
134 timestamp,
135 event_type: LifecycleEventType::ActorStarted,
136 message: format!("Actor started with ID: {}", id),
137 };
138 self.add_lifecycle_event(lifecycle_event);
139 }
140 ManagementResponse::ActorEvent { event } => {
141 let display_event = self.chain_event_to_display_event(event, timestamp);
142 self.add_event(display_event);
143 }
144 ManagementResponse::ActorError { error } => {
145 let lifecycle_event = LifecycleEvent {
146 timestamp,
147 event_type: LifecycleEventType::ActorError,
148 message: format!("Actor error: {}", error),
149 };
150 self.add_lifecycle_event(lifecycle_event);
151 }
152 ManagementResponse::ActorStopped { id } => {
153 let lifecycle_event = LifecycleEvent {
154 timestamp,
155 event_type: LifecycleEventType::ActorStopped,
156 message: format!("Actor stopped: {}", id),
157 };
158 self.add_lifecycle_event(lifecycle_event);
159 }
160 ManagementResponse::ActorResult(result) => {
161 let lifecycle_event = LifecycleEvent {
162 timestamp,
163 event_type: LifecycleEventType::ActorResult,
164 message: format!("Actor result: {}", result),
165 };
166 self.add_lifecycle_event(lifecycle_event);
167 }
168 _ => {
169 }
171 }
172 }
173
174 fn chain_event_to_display_event(
175 &self,
176 event: ChainEvent,
177 timestamp: DateTime<Utc>,
178 ) -> DisplayEvent {
179 let description = event.description.as_deref().unwrap_or("Unknown event");
180 let level = if description.contains("error") {
181 EventLevel::Error
182 } else if description.contains("warn") {
183 EventLevel::Warning
184 } else {
185 EventLevel::Info
186 };
187
188 DisplayEvent {
189 timestamp,
190 event_type: event.event_type.clone(),
191 message: description.to_string(),
192 details: Some(format!("{:?}", event.data)),
193 level,
194 }
195 }
196
197 pub fn toggle_pause(&mut self) {
198 self.paused = !self.paused;
199 }
200
201 pub fn toggle_auto_scroll(&mut self) {
202 self.auto_scroll = !self.auto_scroll;
203 }
204
205 pub fn quit(&mut self) {
206 self.should_quit = true;
207 }
208
209 pub fn reset_events(&mut self) {
210 self.events.clear();
211 self.event_count = 0;
212 }
213}
214
215impl PartialEq for EventLevel {
216 fn eq(&self, other: &Self) -> bool {
217 matches!(
218 (self, other),
219 (EventLevel::Info, EventLevel::Info)
220 | (EventLevel::Warning, EventLevel::Warning)
221 | (EventLevel::Error, EventLevel::Error)
222 )
223 }
224}