Files
Escapepage/templates/game/admin/sessions/view.html.twig
T
FrankandClaude Sonnet 5 a00899e444 Route every Mercure publish through an event, log pushes per-player
Every $hub->publish() call site (chat, hints, security alerts, virus
alerts, game_finished, and the pre-game lobby events) now dispatches
a PushMercureMessageEvent instead of publishing directly. Two
listeners handle it: PublishMercureMessageListener does the actual
Mercure publish exactly as before (same wire format, no client
changes needed), and LogMercureMessageListener appends it to the
activity log of whichever player(s) it was actually delivered to.

A private /chat to one agent only gets logged for that agent, never
broadcast into everyone's transcript - the event carries an explicit
targetScreen (null = everyone) rather than leaving listeners to guess
from the payload shape.

The per-player log format moved from flat text to JSON Lines (one
timestamped, structured entry per line) via a new shared
SessionActivityLogger service, since a flat string can't carry an
event's type or exact payload - both needed for a future feature to
replay a player's full session history, not just their own commands,
on page reload. The admin session log viewer now parses and
formats these entries back into readable lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 19:47:53 +02:00

106 lines
4.2 KiB
Twig

{% extends 'game/admin/base.html.twig' %}
{% block title %}Session #{{ session.id }} logs — Admin{% endblock %}
{% block admin_body %}
<div style="margin-bottom: 1.5rem;">
<a href="{{ path('game_admin_sessions') }}" style="color: #64748b; text-decoration: none; font-size: 0.9rem;">← Sessions</a>
</div>
<h1 style="margin: 0 0 0.25rem; font-size: 1.5rem; color: #0f172a;">{{ session.game.name }} — Session #{{ session.id }}</h1>
<p style="margin: 0 0 1.5rem; color: #64748b; font-size: 0.9rem;">{{ session.status.value }} · {{ session.players|length }} player(s) · Created {{ session.created|date('Y-m-d H:i') }}</p>
{# Tab buttons #}
<div style="display: flex; gap: 0; margin-bottom: 0; border-bottom: 1px solid #e2e8f0; overflow-x: auto; -webkit-overflow-scrolling: touch;">
<button
data-tab-target="lobby-chat-tab"
id="tab-lobby-chat"
style="
flex-shrink: 0;
white-space: nowrap;
padding: 0.6rem 1.25rem;
border: 1px solid #3b82f6;
border-bottom: 1px solid #fff;
background: #fff;
color: #1e40af;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
border-radius: 6px 6px 0 0;
margin-bottom: -1px;
"
>Lobby Chat</button>
{% for playerLog in playersLogs %}
<button
data-tab-target="player-{{ loop.index }}"
id="tab-{{ loop.index }}"
style="
flex-shrink: 0;
white-space: nowrap;
padding: 0.6rem 1.25rem;
border: 1px solid #e2e8f0;
border-bottom: 1px solid #e2e8f0;
background: #f8fafc;
color: #64748b;
font-size: 0.9rem;
font-weight: 400;
cursor: pointer;
border-radius: 6px 6px 0 0;
margin-bottom: -1px;
"
>{{ playerLog.username }}</button>
{% endfor %}
</div>
{# Tab content #}
<div id="lobby-chat-tab" class="admin-tab-panel" style="
display: block;
background: #fff;
border: 1px solid #e2e8f0;
border-top: none;
border-radius: 0 0 8px 8px;
padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0,0,0,.07);
">
{% if lobbyMessages is empty %}
<p style="margin: 0; color: #94a3b8;">No lobby chat messages for this session.</p>
{% else %}
<div style="max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 0.6rem;">
{% for message in lobbyMessages %}
<div style="font-size: 0.9rem;">
<strong style="color: #0f172a;">{{ message.player.user.username }}</strong>
<span style="color: #94a3b8; font-size: 0.8rem;">{{ message.createdAt|date('Y-m-d H:i') }}</span>
<div style="color: #334155;">{{ message.content }}</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% for playerLog in playersLogs %}
<div id="player-{{ loop.index }}" class="admin-tab-panel" style="
display: none;
background: #fff;
border: 1px solid #e2e8f0;
border-top: none;
border-radius: 0 0 8px 8px;
padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0,0,0,.07);
">
<pre style="
background: #1e293b;
color: #e2e8f0;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
font-size: 0.85rem;
line-height: 1.5;
margin: 0;
">{% if playerLog.entries is empty %}No logs found for this player.{% else %}{% for entry in playerLog.entries %}{% if entry.time %}[{{ entry.time }}] {% endif %}{{ entry.line }}
{% endfor %}{% endif %}</pre>
</div>
{% endfor %}
{% endblock %}