Permette titolo, testo e link diversi per Android e iOS negli avvisi.

Separa i canali app e mostra il nodo ingest nelle sessioni admin.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-20 16:09:04 +02:00
co-authored by Cursor
parent 43d5003de8
commit a07f231417
24 changed files with 366 additions and 69 deletions
@@ -11,7 +11,8 @@ module Admin
kind: "info",
severity: "info",
status: "draft",
show_on_app: true,
show_on_android: true,
show_on_ios: true,
show_on_web_public: true,
show_on_web_private: true,
dismissible: true
@@ -55,12 +56,18 @@ module Admin
params.require(:app_announcement).permit(
:kind, :severity, :status, :title, :body,
:action_url, :action_label, :dismissible, :starts_at, :ends_at,
:show_on_app, :show_on_web_public, :show_on_web_private
:show_on_android, :show_on_ios, :show_on_web_public, :show_on_web_private,
:android_title, :android_body, :android_action_url, :android_action_label,
:ios_title, :ios_body, :ios_action_url, :ios_action_label
).tap do |permitted|
%i[dismissible show_on_app show_on_web_public show_on_web_private].each do |key|
%i[dismissible show_on_android show_on_ios show_on_web_public show_on_web_private].each do |key|
permitted[key] = ActiveModel::Type::Boolean.new.cast(permitted[key])
end
%i[starts_at ends_at action_url action_label].each do |key|
%i[
starts_at ends_at action_url action_label
android_title android_body android_action_url android_action_label
ios_title ios_body ios_action_url ios_action_label
].each do |key|
permitted[key] = nil if permitted[key].blank?
end
end
@@ -5,7 +5,7 @@ module Admin
@host = HostMetrics.new.sample!
@active_sessions = StreamSession
.where(status: DashboardStats::ACTIVE_STATUSES)
.includes(match: :team)
.includes(:stream_node, match: :team)
.order(started_at: :desc)
@teams = Team.includes(:matches).order(:name).limit(8)
end
@@ -1,11 +1,11 @@
module Admin
class SessionsController < Admin::BaseController
def index
@sessions = StreamSession.includes(:match, :user).order(created_at: :desc).limit(50)
@sessions = StreamSession.includes(:stream_node, :user, match: :team).order(created_at: :desc).limit(50)
end
def show
@session = StreamSession.find(params[:id])
@session = StreamSession.includes(:stream_node, match: :team).find(params[:id])
@events = @session.stream_events.recent.limit(50)
end
@@ -5,7 +5,7 @@ module Api
def index
platform = params[:platform].to_s.presence
announcements = AppAnnouncement.for_app.newest
announcements = AppAnnouncement.for_platform(platform).newest
render json: announcements.map { |item| item.as_api_json(platform: platform) }
end
end
+13
View File
@@ -30,6 +30,19 @@ module AdminHelper
links
end
def admin_session_ingest_badge_class(node)
case node.role
when "home" then "badge--ingest-home"
when "lab" then "badge--ingest-lab"
when "cloud" then "badge--ingest-cloud"
else "badge--paused"
end
end
def admin_session_ingest_role_label(node)
I18n.t("admin.sessions.ingest.role.#{node.role}", default: node.role.to_s.humanize)
end
def admin_regia_expires_label(iso_time)
return if iso_time.blank?
+39 -10
View File
@@ -3,10 +3,12 @@ class AppAnnouncement < ApplicationRecord
SEVERITIES = %w[info warning critical].freeze
STATUSES = %w[draft published archived].freeze
CHANNELS = {
app: :show_on_app,
android: :show_on_android,
ios: :show_on_ios,
web_public: :show_on_web_public,
web_private: :show_on_web_private
}.freeze
URL_FIELDS = %i[action_url android_action_url ios_action_url].freeze
belongs_to :created_by_admin, class_name: "AdminAccount", optional: true
@@ -14,10 +16,12 @@ class AppAnnouncement < ApplicationRecord
validates :kind, inclusion: { in: KINDS }
validates :severity, inclusion: { in: SEVERITIES }
validates :status, inclusion: { in: STATUSES }
validates :title, length: { maximum: 120 }
validates :body, length: { maximum: 2000 }
validates :action_label, length: { maximum: 40 }, allow_blank: true
validates :action_url, format: { with: /\Ahttps?:\/\/.+/i, allow_blank: true }
validates :title, :android_title, :ios_title, length: { maximum: 120 }, allow_blank: true
validates :body, :android_body, :ios_body, length: { maximum: 2000 }, allow_blank: true
validates :action_label, :android_action_label, :ios_action_label, length: { maximum: 40 }, allow_blank: true
URL_FIELDS.each do |field|
validates field, format: { with: /\Ahttps?:\/\/.+/i, allow_blank: true }
end
validate :ends_at_after_starts_at
validate :at_least_one_channel
@@ -38,7 +42,15 @@ class AppAnnouncement < ApplicationRecord
end
def self.for_app
for_channel(:app)
active_now.where("show_on_android = TRUE OR show_on_ios = TRUE")
end
def self.for_platform(platform)
case platform.to_s
when "android" then for_channel(:android)
when "ios" then for_channel(:ios)
else for_app
end
end
def published?
@@ -56,17 +68,19 @@ class AppAnnouncement < ApplicationRecord
id: id,
kind: kind,
severity: severity,
title: title,
body: body,
title: copy_for(platform, :title),
body: copy_for(platform, :body),
dismissible: dismissible,
action_url: resolved_action_url(platform),
action_label: action_label.presence,
action_label: copy_for(platform, :action_label).presence,
starts_at: starts_at&.iso8601,
ends_at: ends_at&.iso8601
}
end
def resolved_action_url(platform)
override = platform_override(platform, :action_url)
return override if override.present?
return action_url if action_url.present?
return nil unless kind == "update"
@@ -78,8 +92,23 @@ class AppAnnouncement < ApplicationRecord
private
def copy_for(platform, field)
override = platform_override(platform, field)
override.presence || public_send(field)
end
def platform_override(platform, field)
prefix = case platform.to_s
when "android" then :android
when "ios" then :ios
end
return if prefix.blank?
public_send("#{prefix}_#{field}")
end
def at_least_one_channel
return if show_on_app? || show_on_web_public? || show_on_web_private?
return if show_on_android? || show_on_ios? || show_on_web_public? || show_on_web_private?
errors.add(:base, :no_channel)
end
@@ -19,8 +19,12 @@
<fieldset class="admin-channels">
<legend><%= t("admin.announcements.form.channels") %></legend>
<label class="admin-checkbox">
<%= f.check_box :show_on_app %>
<span><%= t("admin.announcements.channels.app") %></span>
<%= f.check_box :show_on_android %>
<span><%= t("admin.announcements.channels.android") %></span>
</label>
<label class="admin-checkbox">
<%= f.check_box :show_on_ios %>
<span><%= t("admin.announcements.channels.ios") %></span>
</label>
<label class="admin-checkbox">
<%= f.check_box :show_on_web_public %>
@@ -43,6 +47,59 @@
<%= f.text_area :body, required: true, rows: 5, maxlength: 2000 %>
</div>
<div>
<%= f.label :action_url, t("admin.announcements.form.action_url") %>
<%= f.url_field :action_url, placeholder: "https://" %>
</div>
<p class="muted admin-form-hint"><%= t("admin.announcements.form.action_url_hint") %></p>
<div>
<%= f.label :action_label, t("admin.announcements.form.action_label") %>
<%= f.text_field :action_label, maxlength: 40 %>
</div>
<fieldset class="admin-channels admin-platform-copy">
<legend><%= t("admin.announcements.form.android_override") %></legend>
<p class="muted admin-form-hint"><%= t("admin.announcements.form.platform_override_hint") %></p>
<div>
<%= f.label :android_title, t("admin.announcements.form.title") %>
<%= f.text_field :android_title, maxlength: 120 %>
</div>
<div>
<%= f.label :android_body, t("admin.announcements.form.body") %>
<%= f.text_area :android_body, rows: 4, maxlength: 2000 %>
</div>
<div>
<%= f.label :android_action_url, t("admin.announcements.form.action_url") %>
<%= f.url_field :android_action_url, placeholder: "https://play.google.com/..." %>
</div>
<div>
<%= f.label :android_action_label, t("admin.announcements.form.action_label") %>
<%= f.text_field :android_action_label, maxlength: 40 %>
</div>
</fieldset>
<fieldset class="admin-channels admin-platform-copy">
<legend><%= t("admin.announcements.form.ios_override") %></legend>
<p class="muted admin-form-hint"><%= t("admin.announcements.form.platform_override_hint") %></p>
<div>
<%= f.label :ios_title, t("admin.announcements.form.title") %>
<%= f.text_field :ios_title, maxlength: 120 %>
</div>
<div>
<%= f.label :ios_body, t("admin.announcements.form.body") %>
<%= f.text_area :ios_body, rows: 4, maxlength: 2000 %>
</div>
<div>
<%= f.label :ios_action_url, t("admin.announcements.form.action_url") %>
<%= f.url_field :ios_action_url, placeholder: "https://apps.apple.com/..." %>
</div>
<div>
<%= f.label :ios_action_label, t("admin.announcements.form.action_label") %>
<%= f.text_field :ios_action_label, maxlength: 40 %>
</div>
</fieldset>
<div class="admin-form-row">
<div>
<%= f.label :starts_at, t("admin.announcements.form.starts_at") %>
@@ -55,17 +112,6 @@
</div>
<p class="muted admin-form-hint"><%= t("admin.announcements.form.window_hint") %></p>
<div>
<%= f.label :action_url, t("admin.announcements.form.action_url") %>
<%= f.url_field :action_url, placeholder: "https://" %>
</div>
<p class="muted admin-form-hint"><%= t("admin.announcements.form.action_url_hint") %></p>
<div>
<%= f.label :action_label, t("admin.announcements.form.action_label") %>
<%= f.text_field :action_label, maxlength: 40 %>
</div>
<label class="admin-checkbox">
<%= f.check_box :dismissible %>
<span><%= t("admin.announcements.form.dismissible") %></span>
@@ -107,6 +107,7 @@
<tr>
<th><%= t("admin.dashboard.sessions.table.match") %></th>
<th><%= t("admin.dashboard.sessions.table.status") %></th>
<th><%= t("admin.dashboard.sessions.table.ingest") %></th>
<th><%= t("admin.dashboard.sessions.table.start") %></th>
<th><%= t("admin.dashboard.sessions.table.link") %></th>
<th></th>
@@ -117,6 +118,7 @@
<tr>
<td><%= s.match.team.name %> vs <%= s.match.opponent_name %></td>
<td><span class="badge badge--<%= s.status == 'live' ? 'live' : (s.status == 'paused' ? 'paused' : 'connecting') %>"><%= s.status %></span></td>
<td><%= render "admin/sessions/ingest_cell", session: s %></td>
<td class="muted"><%= s.started_at&.strftime("%d/%m %H:%M") || t("admin.common.dash") %></td>
<td class="admin-link-compact">
<% admin_session_watch_links(s).each do |link| %>
@@ -0,0 +1,9 @@
<% node = session.stream_node %>
<% if node %>
<div class="admin-ingest">
<code class="admin-ingest__slug"><%= node.slug %></code>
<span class="badge <%= admin_session_ingest_badge_class(node) %>"><%= admin_session_ingest_role_label(node) %></span>
</div>
<% else %>
<span class="muted"><%= t("admin.sessions.ingest.none") %></span>
<% end %>
@@ -4,6 +4,7 @@
<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>
@@ -14,6 +15,7 @@
<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 class="admin-link-compact">
<% admin_session_watch_links(s).each do |link| %>
@@ -16,6 +16,16 @@
<% 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>
<% 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>
+1 -1
View File
@@ -4,7 +4,7 @@
<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=6">
<link rel="stylesheet" href="/admin.css?v=8">
<% if content_for?(:replay_archive_styles) %>
<link rel="stylesheet" href="/marketing.css?v=42">
<% end %>