feat(brahman-handshake): ListMatches endpoint + timeline en broker-explorer
Iter 21. Cierra el loop iniciado en iter 20: ahora se ven sesiones
+ matches actuales + cómo cambian a través del tiempo.
brahman-handshake/messages:
- Frame::ListMatches → Frame::MatchList(Vec<brahman_broker::Match>).
brahman-handshake/server:
- run_post_handshake pasa Option<&SharedBroker> a handle_inbound_frame.
- Sin broker configurado → MatchList vacía (no error).
brahman-handshake/client + brahman-sidecar:
- Client::list_matches() análogo a list_sessions, drena MatchEvents.
- list_matches / list_matches_blocking, mismo patrón.
brahman-broker-explorer:
- Poll-tick agrega list_matches_blocking además de list_sessions.
- last_match_keys: HashSet<MatchKey> para diff entre ticks.
- timeline: VecDeque<TimelineEntry> cap 50.
- diff_matches (free fn): Available para keys nuevas, Lost para
desaparecidas. Primer tick marca todo Available (boot UX).
- Render: stat_card "Timeline" con HH:MM:SS {+/-} formato compacto.
5 tests broker-explorer (3 nuevos del diff). Stack verde.
Decisión: timeline polled cada POLL_INTERVAL=5s, no push. MatchEvents
del broker son consumer-céntricos (cada session ve sólo SUS matches);
"system-wide timeline" requeriría broker subscribe-all (mucho scope).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -317,10 +317,19 @@ where
|
||||
});
|
||||
|
||||
// Reader loop principal.
|
||||
let broker_for_loop = config.broker.clone();
|
||||
let result: std::io::Result<()> = loop {
|
||||
match read_frame(&mut reader).await {
|
||||
Ok(frame) => {
|
||||
match handle_inbound_frame(session_id, frame, &writer, &sessions).await {
|
||||
match handle_inbound_frame(
|
||||
session_id,
|
||||
frame,
|
||||
&writer,
|
||||
&sessions,
|
||||
broker_for_loop.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(true) => continue,
|
||||
Ok(false) => break Ok(()),
|
||||
Err(e) => break Err(e),
|
||||
@@ -350,6 +359,7 @@ async fn handle_inbound_frame<S>(
|
||||
frame: Frame,
|
||||
writer: &Arc<Mutex<WriteHalf<S>>>,
|
||||
sessions: &SessionRegistry,
|
||||
broker_for_match: Option<&SharedBroker>,
|
||||
) -> std::io::Result<bool>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
@@ -405,6 +415,32 @@ where
|
||||
.await?;
|
||||
Ok(true)
|
||||
}
|
||||
Frame::ListMatches(crate::messages::ListMatches { session })
|
||||
if session == session_id =>
|
||||
{
|
||||
let matches = match &broker_for_match {
|
||||
Some(b) => b.lock().await.all_matches(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
let mut w = writer.lock().await;
|
||||
write_frame(
|
||||
&mut *w,
|
||||
&Frame::MatchList(crate::messages::MatchList { matches }),
|
||||
)
|
||||
.await?;
|
||||
Ok(true)
|
||||
}
|
||||
Frame::ListMatches(_) => {
|
||||
let mut w = writer.lock().await;
|
||||
write_frame(
|
||||
&mut *w,
|
||||
&Frame::Error(HandshakeError::Unauthorized(
|
||||
"session-id no coincide".into(),
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
Ok(true)
|
||||
}
|
||||
_ => {
|
||||
let mut w = writer.lock().await;
|
||||
write_frame(
|
||||
|
||||
Reference in New Issue
Block a user