feat(live-state): compact 4-col current + 5-col recent chips + N Recent trigger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9554661e5a
commit
20fb564246
2 changed files with 56 additions and 19 deletions
|
|
@ -1,4 +1,9 @@
|
|||
<h2 class="section-title">Live state</h2>
|
||||
{# Live-state partial — HTMX-polled into the state cluster on server_detail.html.
|
||||
The parent .state-cluster section provides the heading context, so there is
|
||||
no <h2> here. Current players have no sub-header; they sit directly under
|
||||
the summary line. Recent players' header is "N Recent" and doubles as the
|
||||
modal trigger when N > 10. #}
|
||||
|
||||
{% if not snapshot or not snapshot_fresh %}
|
||||
<p class="muted">No data — server is not currently reporting.</p>
|
||||
{% else %}
|
||||
|
|
@ -6,17 +11,14 @@
|
|||
{{ snapshot.players }}/{{ snapshot.max_players }}
|
||||
{% if snapshot.hibernating %}· idle{% endif %}
|
||||
· {{ snapshot.map }}
|
||||
<small class="muted">
|
||||
polled {{ snapshot.last_seen_at | timeago }}
|
||||
</small>
|
||||
<small class="muted">polled {{ snapshot.last_seen_at | timeago }}</small>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if current_players %}
|
||||
<h3 class="section-subtitle">Current players</h3>
|
||||
<ul class="player-grid">
|
||||
<ul class="player-grid current">
|
||||
{% for session, profile in current_players %}
|
||||
<li class="player-card">
|
||||
<li class="player-card current-card">
|
||||
<a class="player-link"
|
||||
href="https://steamcommunity.com/profiles/{{ session.steam_id_64 }}"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
|
|
@ -25,22 +27,30 @@
|
|||
{% else %}
|
||||
<span class="avatar placeholder" aria-hidden="true"></span>
|
||||
{% endif %}
|
||||
<span class="name">{{ (profile and profile.persona_name) or session.name_at_join }}</span>
|
||||
<span class="name" title="{{ (profile and profile.persona_name) or session.name_at_join }}">{{ (profile and profile.persona_name) or session.name_at_join }}</span>
|
||||
</a>
|
||||
<span class="meta">
|
||||
joined {{ session.joined_at | timeago }}
|
||||
· ping {{ session.min_ping }}-{{ session.max_ping }}ms
|
||||
{{ session.joined_at | timeago }} · {{ session.min_ping }}-{{ session.max_ping }}ms
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if recent_players %}
|
||||
<h3 class="section-subtitle">Recent players</h3>
|
||||
{% if recent_players_overview %}
|
||||
<h3 class="recent-header">
|
||||
{% if recent_players_total_count > 10 %}
|
||||
<button type="button" class="recent-header-trigger"
|
||||
data-inline-modal-open="recent-players-modal">
|
||||
{{ recent_players_total_count }} Recent
|
||||
</button>
|
||||
{% else %}
|
||||
{{ recent_players_total_count }} Recent
|
||||
{% endif %}
|
||||
</h3>
|
||||
<ul class="player-grid recent">
|
||||
{% for row in recent_players %}
|
||||
<li class="player-card">
|
||||
{% for row in recent_players_overview %}
|
||||
<li class="player-card recent-chip">
|
||||
<a class="player-link"
|
||||
href="https://steamcommunity.com/profiles/{{ row.steam_id_64 }}"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
|
|
@ -49,11 +59,9 @@
|
|||
{% else %}
|
||||
<span class="avatar placeholder" aria-hidden="true"></span>
|
||||
{% endif %}
|
||||
<span class="name">{{ row.persona_name or row.name_at_join }}</span>
|
||||
<span class="name" title="{{ row.persona_name or row.name_at_join }}">{{ row.persona_name or row.name_at_join }}</span>
|
||||
</a>
|
||||
<span class="meta">
|
||||
last seen {{ row.last_seen | timeago }}
|
||||
</span>
|
||||
<span class="meta">· {{ row.last_seen | timeago }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ def test_status_precedence() -> None:
|
|||
assert compute_display_state("start", "stopped") == "starting"
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="template change in Task 3 will satisfy this", strict=True)
|
||||
def test_live_state_exposes_recent_overview_and_total_count(owner_client_with_server) -> None:
|
||||
"""Route must expose recent_players_overview (≤10) and recent_players_total_count.
|
||||
|
||||
|
|
@ -107,3 +106,33 @@ def test_live_state_exposes_recent_overview_and_total_count(owner_client_with_se
|
|||
# These assertions depend on the Task 3 template changes.
|
||||
assert "13 Recent" in html
|
||||
assert html.count("recent-chip") <= 10
|
||||
|
||||
|
||||
def test_live_state_recent_header_not_clickable_when_le_10(owner_client_with_server) -> None:
|
||||
"""When recent player count is ≤ 10, the header must be plain text, not a modal trigger."""
|
||||
from datetime import timedelta
|
||||
from l4d2web.models import ServerPlayerSession
|
||||
|
||||
client, server_id = owner_client_with_server
|
||||
|
||||
now = datetime.now(UTC)
|
||||
total = 4
|
||||
|
||||
with session_scope() as db:
|
||||
for i in range(total):
|
||||
db.add(ServerPlayerSession(
|
||||
server_id=server_id,
|
||||
steam_id_64=str(76561190000000000 + i),
|
||||
joined_at=now - timedelta(hours=i + 2),
|
||||
left_at=now - timedelta(hours=i + 1),
|
||||
name_at_join=f"Player{i}",
|
||||
min_ping=10,
|
||||
max_ping=50,
|
||||
))
|
||||
|
||||
res = client.get(f"/servers/{server_id}/live-state")
|
||||
assert res.status_code == 200
|
||||
html = res.get_data(as_text=True)
|
||||
|
||||
assert "4 Recent" in html
|
||||
assert 'data-inline-modal-open="recent-players-modal"' not in html
|
||||
|
|
|
|||
Loading…
Reference in a new issue