Migliora sessioni admin e rende robusto il logout web.
Aggiunge filtri/colonne (società, orari) e dettaglio leggibile; evita 422 CSRF su logout e non cancella le cover slate custom in sync prod. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
module Admin
|
||||
class AuthController < Admin::BaseController
|
||||
skip_before_action :require_admin_login, only: %i[new create]
|
||||
# Same as public logout: tolerate stale CSRF after long-lived admin tabs.
|
||||
skip_before_action :verify_authenticity_token, only: :destroy
|
||||
|
||||
def new
|
||||
redirect_to admin_root_path if admin_logged_in?
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
module Admin
|
||||
class SessionsController < Admin::BaseController
|
||||
def index
|
||||
@sessions = StreamSession.includes(:stream_node, :user, match: :team).order(created_at: :desc).limit(50)
|
||||
@filters = session_filters
|
||||
scope = filtered_sessions
|
||||
@total_count = scope.count
|
||||
@sessions = scope
|
||||
.includes(:stream_node, :user, match: { team: :club })
|
||||
.order(Arel.sql("COALESCE(stream_sessions.started_at, stream_sessions.created_at) DESC"))
|
||||
.limit(100)
|
||||
@clubs = Club.order(:name)
|
||||
@nodes = StreamNode.order(:slug)
|
||||
end
|
||||
|
||||
def show
|
||||
@session = StreamSession.includes(:stream_node, match: :team).find(params[:id])
|
||||
@events = @session.stream_events.recent.limit(50)
|
||||
@session = StreamSession
|
||||
.includes(:stream_node, :user, :device_states, :recording, match: { team: :club })
|
||||
.find(params[:id])
|
||||
@events = @session.stream_events.recent.limit(100)
|
||||
@club = @session.match.team.club
|
||||
end
|
||||
|
||||
def stop
|
||||
@@ -39,5 +50,66 @@ module Admin
|
||||
regia_expires_at: @session.regia_token_expires_at&.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def session_filters
|
||||
{
|
||||
q: params[:q].to_s.strip.presence,
|
||||
status: params[:status].to_s.strip.presence,
|
||||
platform: params[:platform].to_s.strip.presence,
|
||||
club_id: params[:club_id].to_s.strip.presence,
|
||||
stream_node_id: params[:stream_node_id].to_s.strip.presence,
|
||||
from: params[:from].to_s.strip.presence,
|
||||
to: params[:to].to_s.strip.presence
|
||||
}
|
||||
end
|
||||
|
||||
def filtered_sessions
|
||||
scope = StreamSession.left_outer_joins(match: { team: :club })
|
||||
|
||||
if @filters[:q].present?
|
||||
term = "%#{ActiveRecord::Base.sanitize_sql_like(@filters[:q])}%"
|
||||
scope = scope.where(
|
||||
"teams.name ILIKE :term OR matches.opponent_name ILIKE :term OR clubs.name ILIKE :term OR matches.location ILIKE :term",
|
||||
term: term
|
||||
)
|
||||
end
|
||||
|
||||
if @filters[:status].present? && StreamSession::STATUSES.include?(@filters[:status])
|
||||
scope = scope.where(stream_sessions: { status: @filters[:status] })
|
||||
end
|
||||
if @filters[:platform].present? && StreamSession::PLATFORMS.include?(@filters[:platform])
|
||||
scope = scope.where(stream_sessions: { platform: @filters[:platform] })
|
||||
end
|
||||
if @filters[:club_id].present?
|
||||
scope = scope.where(teams: { club_id: @filters[:club_id] })
|
||||
end
|
||||
if @filters[:stream_node_id].present?
|
||||
scope = scope.where(stream_sessions: { stream_node_id: @filters[:stream_node_id] })
|
||||
end
|
||||
if (from_time = parse_filter_date(@filters[:from], end_of_day: false))
|
||||
scope = scope.where(
|
||||
"COALESCE(stream_sessions.started_at, stream_sessions.created_at) >= ?",
|
||||
from_time
|
||||
)
|
||||
end
|
||||
if (to_time = parse_filter_date(@filters[:to], end_of_day: true))
|
||||
scope = scope.where(
|
||||
"COALESCE(stream_sessions.started_at, stream_sessions.created_at) <= ?",
|
||||
to_time
|
||||
)
|
||||
end
|
||||
scope
|
||||
end
|
||||
|
||||
def parse_filter_date(value, end_of_day:)
|
||||
return nil if value.blank?
|
||||
|
||||
date = Date.parse(value)
|
||||
end_of_day ? date.end_of_day : date.beginning_of_day
|
||||
rescue ArgumentError, TypeError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
module Public
|
||||
class SessionsController < WebBaseController
|
||||
# Logout must succeed even with a stale CSRF token (old tab after deploy /
|
||||
# session rotate). SameSite=Lax already blocks cross-site cookie POSTs.
|
||||
skip_before_action :verify_authenticity_token, only: :destroy
|
||||
|
||||
def new
|
||||
if logged_in? && current_user.primary_club
|
||||
redirect_to public_club_path(current_user.primary_club)
|
||||
|
||||
@@ -43,6 +43,75 @@ module AdminHelper
|
||||
I18n.t("admin.sessions.ingest.role.#{node.role}", default: node.role.to_s.humanize)
|
||||
end
|
||||
|
||||
def admin_session_status_badge_class(status)
|
||||
case status.to_s
|
||||
when "live" then "badge--live"
|
||||
when "connecting", "reconnecting" then "badge--connecting"
|
||||
when "paused", "idle" then "badge--paused"
|
||||
when "ended" then "badge--ended"
|
||||
when "error" then "badge--error"
|
||||
else "badge--paused"
|
||||
end
|
||||
end
|
||||
|
||||
def admin_datetime(time, with_seconds: false)
|
||||
return I18n.t("admin.common.dash") if time.blank?
|
||||
|
||||
format = with_seconds ? "%d/%m/%Y %H:%M:%S" : "%d/%m/%Y %H:%M"
|
||||
time.in_time_zone.strftime(format)
|
||||
end
|
||||
|
||||
def admin_session_duration_label(session)
|
||||
secs = session.total_duration_secs.to_i
|
||||
if secs <= 0 && session.started_at
|
||||
end_time = session.ended_at || (session.terminal? ? session.updated_at : Time.current)
|
||||
secs = (end_time - session.started_at).to_i if end_time
|
||||
end
|
||||
return I18n.t("admin.common.dash") if secs <= 0
|
||||
|
||||
hours = secs / 3600
|
||||
mins = (secs % 3600) / 60
|
||||
rem = secs % 60
|
||||
if hours.positive?
|
||||
format("%dh %02dm %02ds", hours, mins, rem)
|
||||
elsif mins.positive?
|
||||
format("%dm %02ds", mins, rem)
|
||||
else
|
||||
"#{rem}s"
|
||||
end
|
||||
end
|
||||
|
||||
def admin_format_event_meta(metadata)
|
||||
return content_tag(:span, I18n.t("admin.common.dash"), class: "muted") if metadata.blank?
|
||||
|
||||
pairs = metadata.to_h
|
||||
return content_tag(:span, I18n.t("admin.common.dash"), class: "muted") if pairs.empty?
|
||||
|
||||
content_tag(:dl, class: "admin-event-meta") do
|
||||
safe_join(
|
||||
pairs.map do |key, value|
|
||||
content_tag(:div, class: "admin-event-meta__row") do
|
||||
content_tag(:dt, key.to_s) +
|
||||
content_tag(:dd, admin_event_meta_value(value))
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def admin_event_meta_value(value)
|
||||
case value
|
||||
when Hash, Array
|
||||
content_tag(:code, value.to_json)
|
||||
when TrueClass, FalseClass
|
||||
value ? "true" : "false"
|
||||
when NilClass
|
||||
I18n.t("admin.common.dash")
|
||||
else
|
||||
value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def admin_regia_expires_label(iso_time)
|
||||
return if iso_time.blank?
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ class StreamSession < ApplicationRecord
|
||||
belongs_to :stream_node, optional: true
|
||||
has_many :stream_events, dependent: :destroy
|
||||
has_one :score_state, dependent: :destroy
|
||||
has_one :recording
|
||||
has_many :device_states, dependent: :destroy
|
||||
|
||||
validates :platform, inclusion: { in: PLATFORMS }
|
||||
|
||||
@@ -1,32 +1,131 @@
|
||||
<h2><%= t("admin.sessions.index.title") %></h2>
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= t("admin.sessions.index.table.match") %></th>
|
||||
<th><%= t("admin.sessions.index.table.status") %></th>
|
||||
<th><%= t("admin.sessions.index.table.ingest") %></th>
|
||||
<th><%= t("admin.sessions.index.table.disconnects") %></th>
|
||||
<th><%= t("admin.sessions.index.table.link") %></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @sessions.each do |s| %>
|
||||
<tr>
|
||||
<td><%= s.match.team.name %> vs <%= s.match.opponent_name %></td>
|
||||
<td><span class="badge <%= s.status == 'live' ? 'badge--live' : 'badge--paused' %>"><%= s.status %></span></td>
|
||||
<td><%= render "admin/sessions/ingest_cell", session: s %></td>
|
||||
<td><%= s.disconnection_count %></td>
|
||||
<td>
|
||||
<div class="admin-link-compact">
|
||||
<% admin_session_watch_links(s).each do |link| %>
|
||||
<%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %>
|
||||
<% end %>
|
||||
<%= link_to t("admin.sessions.index.regia"), admin_session_path(s), class: "admin-link-chip admin-link-chip--regia" %>
|
||||
</div>
|
||||
</td>
|
||||
<td><%= link_to t("admin.sessions.index.detail"), admin_session_path(s) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% content_for :body_class, "admin-body" %>
|
||||
|
||||
<div class="admin-page-head">
|
||||
<h2 class="admin-page-title"><%= t("admin.sessions.index.title") %></h2>
|
||||
<p class="muted admin-page-sub"><%= t("admin.sessions.index.lead") %></p>
|
||||
</div>
|
||||
|
||||
<div class="panel admin-sessions-filters">
|
||||
<%= form_with url: admin_sessions_path, method: :get, local: true, class: "admin-filter-form" do %>
|
||||
<div class="admin-filter-grid">
|
||||
<label class="admin-filter-field">
|
||||
<span><%= t("admin.sessions.index.filters.q") %></span>
|
||||
<%= text_field_tag :q, @filters[:q], placeholder: t("admin.sessions.index.filters.q_placeholder") %>
|
||||
</label>
|
||||
|
||||
<label class="admin-filter-field">
|
||||
<span><%= t("admin.sessions.index.filters.status") %></span>
|
||||
<%= select_tag :status,
|
||||
options_for_select(
|
||||
[[t("admin.sessions.index.filters.any"), ""]] + StreamSession::STATUSES.map { |s| [s, s] },
|
||||
@filters[:status]
|
||||
) %>
|
||||
</label>
|
||||
|
||||
<label class="admin-filter-field">
|
||||
<span><%= t("admin.sessions.index.filters.platform") %></span>
|
||||
<%= select_tag :platform,
|
||||
options_for_select(
|
||||
[[t("admin.sessions.index.filters.any"), ""]] + StreamSession::PLATFORMS.map { |p| [p, p] },
|
||||
@filters[:platform]
|
||||
) %>
|
||||
</label>
|
||||
|
||||
<label class="admin-filter-field">
|
||||
<span><%= t("admin.sessions.index.filters.club") %></span>
|
||||
<%= select_tag :club_id,
|
||||
options_for_select(
|
||||
[[t("admin.sessions.index.filters.any"), ""]] + @clubs.map { |c| [c.name, c.id] },
|
||||
@filters[:club_id]
|
||||
) %>
|
||||
</label>
|
||||
|
||||
<label class="admin-filter-field">
|
||||
<span><%= t("admin.sessions.index.filters.node") %></span>
|
||||
<%= select_tag :stream_node_id,
|
||||
options_for_select(
|
||||
[[t("admin.sessions.index.filters.any"), ""]] + @nodes.map { |n| ["#{n.slug} (#{n.role})", n.id] },
|
||||
@filters[:stream_node_id]
|
||||
) %>
|
||||
</label>
|
||||
|
||||
<label class="admin-filter-field">
|
||||
<span><%= t("admin.sessions.index.filters.from") %></span>
|
||||
<%= date_field_tag :from, @filters[:from] %>
|
||||
</label>
|
||||
|
||||
<label class="admin-filter-field">
|
||||
<span><%= t("admin.sessions.index.filters.to") %></span>
|
||||
<%= date_field_tag :to, @filters[:to] %>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="admin-filter-actions">
|
||||
<%= submit_tag t("admin.sessions.index.filters.apply"), class: "admin-btn admin-btn--primary admin-btn--sm" %>
|
||||
<%= link_to t("admin.sessions.index.filters.reset"), admin_sessions_path, class: "admin-btn admin-btn--outline admin-btn--sm" %>
|
||||
<span class="muted admin-filter-count">
|
||||
<%= t("admin.sessions.index.results", shown: @sessions.size, total: @total_count) %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<% if @sessions.any? %>
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table admin-table--sessions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= t("admin.sessions.index.table.club") %></th>
|
||||
<th><%= t("admin.sessions.index.table.match") %></th>
|
||||
<th><%= t("admin.sessions.index.table.status") %></th>
|
||||
<th><%= t("admin.sessions.index.table.started") %></th>
|
||||
<th><%= t("admin.sessions.index.table.ended") %></th>
|
||||
<th><%= t("admin.sessions.index.table.duration") %></th>
|
||||
<th><%= t("admin.sessions.index.table.ingest") %></th>
|
||||
<th><%= t("admin.sessions.index.table.disconnects") %></th>
|
||||
<th><%= t("admin.sessions.index.table.link") %></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @sessions.each do |s| %>
|
||||
<% club = s.match.team.club %>
|
||||
<tr>
|
||||
<td>
|
||||
<% if club %>
|
||||
<%= link_to club.name, admin_club_path(club), class: "admin-table-strong" %>
|
||||
<% else %>
|
||||
<span class="muted"><%= t("admin.common.dash") %></span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<div class="admin-table-strong"><%= s.match.team.name %> vs <%= s.match.opponent_name %></div>
|
||||
<div class="muted admin-table-sub"><%= s.platform %></div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <%= admin_session_status_badge_class(s.status) %>"><%= s.status %></span>
|
||||
</td>
|
||||
<td class="muted"><%= admin_datetime(s.started_at || s.created_at) %></td>
|
||||
<td class="muted"><%= s.ended_at ? admin_datetime(s.ended_at) : t("admin.common.dash") %></td>
|
||||
<td class="muted"><%= admin_session_duration_label(s) %></td>
|
||||
<td><%= render "admin/sessions/ingest_cell", session: s %></td>
|
||||
<td><%= s.disconnection_count %></td>
|
||||
<td>
|
||||
<div class="admin-link-compact">
|
||||
<% admin_session_watch_links(s).each do |link| %>
|
||||
<%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %>
|
||||
<% end %>
|
||||
<%= link_to t("admin.sessions.index.regia"), admin_session_path(s), class: "admin-link-chip admin-link-chip--regia" %>
|
||||
</div>
|
||||
</td>
|
||||
<td><%= link_to t("admin.sessions.index.detail"), admin_session_path(s) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="empty"><%= t("admin.sessions.index.none") %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,49 +1,247 @@
|
||||
<h2><%= t("admin.sessions.show.title", id: @session.id) %></h2>
|
||||
<p><%= t("admin.sessions.show.status_label") %> <strong><%= @session.status %></strong></p>
|
||||
<% unless @session.terminal? %>
|
||||
<p>
|
||||
<%= button_to t("admin.sessions.show.stop_button"),
|
||||
stop_admin_session_path(@session),
|
||||
method: :post,
|
||||
class: "admin-btn admin-btn--danger",
|
||||
form: { data: { turbo_confirm: t("admin.sessions.show.stop_confirm") } } %>
|
||||
</p>
|
||||
<% end %>
|
||||
<p><%= t("admin.sessions.show.match_label", team: @session.match.team.name, opponent: @session.match.opponent_name) %></p>
|
||||
<% content_for :body_class, "admin-body" %>
|
||||
<% team = @session.match.team %>
|
||||
<% club = @club || team.club %>
|
||||
|
||||
<div class="admin-page-head admin-session-head">
|
||||
<div>
|
||||
<p class="muted admin-page-sub">
|
||||
<%= link_to t("admin.sessions.show.back"), admin_sessions_path %>
|
||||
</p>
|
||||
<h2 class="admin-page-title"><%= t("admin.sessions.show.heading") %></h2>
|
||||
<p class="admin-session-match">
|
||||
<strong><%= team.name %></strong>
|
||||
<span class="muted">vs</span>
|
||||
<strong><%= @session.match.opponent_name %></strong>
|
||||
</p>
|
||||
<% if club %>
|
||||
<p class="muted">
|
||||
<%= t("admin.sessions.show.club_label") %>
|
||||
<%= link_to club.name, admin_club_path(club) %>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="admin-session-head-actions">
|
||||
<span class="badge <%= admin_session_status_badge_class(@session.status) %>"><%= @session.status %></span>
|
||||
<% unless @session.terminal? %>
|
||||
<%= button_to t("admin.sessions.show.stop_button"),
|
||||
stop_admin_session_path(@session),
|
||||
method: :post,
|
||||
class: "admin-btn admin-btn--danger admin-btn--sm",
|
||||
form: { data: { turbo_confirm: t("admin.sessions.show.stop_confirm") } } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-session-grid">
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.sessions.show.summary_title") %></h3>
|
||||
<dl class="admin-kv">
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.id") %></dt>
|
||||
<dd><code class="admin-mono"><%= @session.id %></code></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.club") %></dt>
|
||||
<dd><%= club&.name || t("admin.common.dash") %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.team") %></dt>
|
||||
<dd><%= team.name %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.opponent") %></dt>
|
||||
<dd><%= @session.match.opponent_name %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.operator") %></dt>
|
||||
<dd>
|
||||
<% if @session.user %>
|
||||
<%= @session.user.name.presence || @session.user.email %>
|
||||
<span class="muted">(<%= @session.user.email %>)</span>
|
||||
<% else %>
|
||||
<%= t("admin.common.dash") %>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.platform") %></dt>
|
||||
<dd><%= @session.platform %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.privacy") %></dt>
|
||||
<dd><%= @session.privacy_status %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.quality") %></dt>
|
||||
<dd><%= @session.quality_preset %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.min_quality") %></dt>
|
||||
<dd><%= @session.min_quality_preset %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.audio") %></dt>
|
||||
<dd><%= @session.audio_muted? ? t("admin.sessions.show.audio_muted") : t("admin.sessions.show.audio_on") %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.sessions.show.timing_title") %></h3>
|
||||
<dl class="admin-kv">
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.created") %></dt>
|
||||
<dd><%= admin_datetime(@session.created_at, with_seconds: true) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.started") %></dt>
|
||||
<dd><%= admin_datetime(@session.started_at, with_seconds: true) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.ended") %></dt>
|
||||
<dd><%= admin_datetime(@session.ended_at, with_seconds: true) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.duration") %></dt>
|
||||
<dd><%= admin_session_duration_label(@session) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.disconnects") %></dt>
|
||||
<dd><%= @session.disconnection_count %></dd>
|
||||
</div>
|
||||
<% if @session.match.scheduled_at %>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.scheduled") %></dt>
|
||||
<dd><%= admin_datetime(@session.match.scheduled_at) %></dd>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @session.match.location.present? %>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.location") %></dt>
|
||||
<dd><%= @session.match.location %></dd>
|
||||
</div>
|
||||
<% end %>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.sessions.show.ingest_title") %></h3>
|
||||
<dl class="admin-kv">
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.node") %></dt>
|
||||
<dd>
|
||||
<% if @session.stream_node %>
|
||||
<code><%= @session.stream_node.slug %></code>
|
||||
<span class="badge <%= admin_session_ingest_badge_class(@session.stream_node) %>">
|
||||
<%= admin_session_ingest_role_label(@session.stream_node) %>
|
||||
</span>
|
||||
<span class="muted">(<%= @session.stream_node.provider %>)</span>
|
||||
<% else %>
|
||||
<span class="muted"><%= t("admin.sessions.ingest.none") %></span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.rtmp") %></dt>
|
||||
<dd><code class="admin-mono admin-mono--wrap"><%= @session.rtmp_ingest_url %></code></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.hls") %></dt>
|
||||
<dd><code class="admin-mono admin-mono--wrap"><%= @session.hls_playback_url %></code></dd>
|
||||
</div>
|
||||
<% if @session.youtube_broadcast_id.present? %>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.youtube_studio") %></dt>
|
||||
<dd>
|
||||
<a href="https://studio.youtube.com/video/<%= @session.youtube_broadcast_id %>/livestreaming" target="_blank" rel="noopener">
|
||||
<%= t("admin.sessions.show.broadcast") %>
|
||||
</a>
|
||||
<span class="muted">(<%= @session.youtube_broadcast_id %>)</span>
|
||||
</dd>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @session.recording %>
|
||||
<div>
|
||||
<dt><%= t("admin.sessions.show.fields.recording") %></dt>
|
||||
<dd>
|
||||
<span class="badge badge--<%= @session.recording.status == 'ready' ? 'ready' : 'paused' %>">
|
||||
<%= @session.recording.status %>
|
||||
</span>
|
||||
<% if club %>
|
||||
<%= link_to t("admin.sessions.show.open_replays"), admin_club_recordings_path(club) %>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
<% end %>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<%= render "admin/sessions/links", session: @session %>
|
||||
|
||||
<% if @session.youtube_broadcast_id %>
|
||||
<p><%= t("admin.sessions.show.youtube_studio") %>: <a href="https://studio.youtube.com/video/<%= @session.youtube_broadcast_id %>/livestreaming" target="_blank" rel="noopener"><%= t("admin.sessions.show.broadcast") %></a></p>
|
||||
<% if @session.device_states.any? %>
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.sessions.show.devices_title") %></h3>
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= t("admin.sessions.show.devices.role") %></th>
|
||||
<th><%= t("admin.sessions.show.devices.network") %></th>
|
||||
<th><%= t("admin.sessions.show.devices.battery") %></th>
|
||||
<th><%= t("admin.sessions.show.devices.bitrate") %></th>
|
||||
<th><%= t("admin.sessions.show.devices.fps") %></th>
|
||||
<th><%= t("admin.sessions.show.devices.last_seen") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @session.device_states.order(:device_role).each do |device| %>
|
||||
<tr>
|
||||
<td><%= device.device_role %></td>
|
||||
<td class="muted"><%= device.network_type.presence || t("admin.common.dash") %></td>
|
||||
<td class="muted"><%= device.battery_level.nil? ? t("admin.common.dash") : "#{device.battery_level}%" %></td>
|
||||
<td class="muted">
|
||||
<% if device.current_bitrate || device.target_bitrate %>
|
||||
<%= device.current_bitrate || t("admin.common.dash") %>
|
||||
<span class="muted">/ <%= device.target_bitrate || t("admin.common.dash") %></span>
|
||||
<% else %>
|
||||
<%= t("admin.common.dash") %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="muted"><%= device.fps || t("admin.common.dash") %></td>
|
||||
<td class="muted"><%= admin_datetime(device.last_seen_at, with_seconds: true) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
<p>
|
||||
<%= t("admin.sessions.show.ingest_node") %>
|
||||
<% if @session.stream_node %>
|
||||
<code><%= @session.stream_node.slug %></code>
|
||||
<span class="badge <%= admin_session_ingest_badge_class(@session.stream_node) %>"><%= admin_session_ingest_role_label(@session.stream_node) %></span>
|
||||
<span class="muted">(<%= @session.stream_node.provider %>)</span>
|
||||
<% else %>
|
||||
<span class="muted"><%= t("admin.sessions.ingest.none") %></span>
|
||||
<% end %>
|
||||
</p>
|
||||
<p><%= t("admin.sessions.show.rtmp_ingest") %> <code><%= @session.rtmp_ingest_url %></code></p>
|
||||
|
||||
<h3><%= t("admin.sessions.show.events_title") %></h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= t("admin.sessions.show.table.type") %></th>
|
||||
<th><%= t("admin.sessions.show.table.when") %></th>
|
||||
<th><%= t("admin.sessions.show.table.meta") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @events.each do |e| %>
|
||||
<tr>
|
||||
<td><%= e.event_type %></td>
|
||||
<td><%= e.occurred_at %></td>
|
||||
<td><%= e.metadata.to_json %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.sessions.show.events_title") %></h3>
|
||||
<% if @events.any? %>
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table admin-table--events">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= t("admin.sessions.show.table.type") %></th>
|
||||
<th><%= t("admin.sessions.show.table.when") %></th>
|
||||
<th><%= t("admin.sessions.show.table.meta") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @events.each do |e| %>
|
||||
<tr>
|
||||
<td><span class="admin-event-type"><%= e.event_type %></span></td>
|
||||
<td class="muted"><%= admin_datetime(e.occurred_at, with_seconds: true) %></td>
|
||||
<td><%= admin_format_event_meta(e.metadata) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="empty"><%= t("admin.sessions.show.events_none") %></p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<title><%= t("admin.layout.title") %></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<link rel="stylesheet" href="/admin.css?v=11">
|
||||
<%= csrf_meta_tags %>
|
||||
<link rel="stylesheet" href="/admin.css?v=12">
|
||||
<% if content_for?(:replay_archive_styles) %>
|
||||
<link rel="stylesheet" href="/marketing.css?v=42">
|
||||
<% end %>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><%= content_for?(:title) ? yield(:title) : "Match Live TV" %></title>
|
||||
<meta name="application-name" content="Match Live TV">
|
||||
<%= csrf_meta_tags %>
|
||||
<%= render "shared/meta_tags" %>
|
||||
<%= yield :head %>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><%= content_for?(:title) ? yield(:title) : "Match Live TV" %></title>
|
||||
<%= csrf_meta_tags %>
|
||||
<%= render "shared/meta_tags" %>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<link rel="stylesheet" href="/marketing.css?v=68">
|
||||
|
||||
Reference in New Issue
Block a user