about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/javascript/styles/mastodon/admin.scss66
-rw-r--r--app/lib/admin/metrics/dimension/base_dimension.rb32
-rw-r--r--app/lib/admin/metrics/dimension/languages_dimension.rb4
-rw-r--r--app/lib/admin/metrics/dimension/servers_dimension.rb4
-rw-r--r--app/lib/admin/metrics/dimension/software_versions_dimension.rb6
-rw-r--r--app/lib/admin/metrics/dimension/sources_dimension.rb4
-rw-r--r--app/lib/admin/metrics/dimension/space_usage_dimension.rb6
-rw-r--r--app/lib/admin/metrics/dimension/tag_languages_dimension.rb6
-rw-r--r--app/lib/admin/metrics/dimension/tag_servers_dimension.rb6
-rw-r--r--app/lib/admin/metrics/measure/active_users_measure.rb10
-rw-r--r--app/lib/admin/metrics/measure/base_measure.rb52
-rw-r--r--app/lib/admin/metrics/measure/interactions_measure.rb10
-rw-r--r--app/lib/admin/metrics/measure/new_users_measure.rb8
-rw-r--r--app/lib/admin/metrics/measure/opened_reports_measure.rb8
-rw-r--r--app/lib/admin/metrics/measure/resolved_reports_measure.rb8
-rw-r--r--app/lib/admin/metrics/measure/tag_accounts_measure.rb10
-rw-r--r--app/lib/admin/metrics/measure/tag_servers_measure.rb10
-rw-r--r--app/lib/admin/metrics/measure/tag_uses_measure.rb10
-rw-r--r--app/lib/admin/metrics/retention.rb26
-rw-r--r--app/lib/video_metadata_extractor.rb3
-rw-r--r--app/models/media_attachment.rb13
-rw-r--r--app/views/admin/account_warnings/_account_warning.html.haml7
-rw-r--r--app/views/disputes/strikes/show.html.haml11
-rw-r--r--lib/paperclip/transcoder.rb16
24 files changed, 242 insertions, 94 deletions
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 33e115c1a..a1b99636c 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -661,18 +661,6 @@ body,
       text-decoration: underline;
     }
   }
-
-  &--inactive {
-    .log-entry__title {
-      text-decoration: line-through;
-    }
-
-    a,
-    .username,
-    .target {
-      color: $darker-text-color;
-    }
-  }
 }
 
 a.name-tag,
@@ -1208,17 +1196,6 @@ a.sparkline {
         font-weight: 600;
         padding: 4px 0;
       }
-
-      a {
-        color: $ui-highlight-color;
-        text-decoration: none;
-
-        &:hover,
-        &:focus,
-        &:active {
-          text-decoration: underline;
-        }
-      }
     }
 
     &--horizontal {
@@ -1306,6 +1283,30 @@ a.sparkline {
       background: linear-gradient(to left, $ui-base-color, transparent);
       pointer-events: none;
     }
+
+    a {
+      color: $secondary-text-color;
+      text-decoration: none;
+      unicode-bidi: isolate;
+
+      &:hover {
+        text-decoration: underline;
+
+        .fa {
+          color: lighten($dark-text-color, 7%);
+        }
+      }
+
+      &.mention {
+        &:hover {
+          text-decoration: none;
+
+          span {
+            text-decoration: underline;
+          }
+        }
+      }
+    }
   }
 
   &__actions {
@@ -1513,6 +1514,25 @@ a.sparkline {
     &:last-child {
       margin-bottom: 0;
     }
+
+    strong {
+      font-weight: 700;
+    }
+  }
+
+  &__rules {
+    list-style: disc;
+    padding-left: 15px;
+    margin-bottom: 20px;
+    color: $darker-text-color;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    &__text {
+      color: $primary-text-color;
+    }
   }
 
   &__statuses-list {
diff --git a/app/lib/admin/metrics/dimension/base_dimension.rb b/app/lib/admin/metrics/dimension/base_dimension.rb
index 5872c22cb..bd2e4ecec 100644
--- a/app/lib/admin/metrics/dimension/base_dimension.rb
+++ b/app/lib/admin/metrics/dimension/base_dimension.rb
@@ -1,23 +1,34 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Dimension::BaseDimension
+  CACHE_TTL = 5.minutes.freeze
+
   def self.with_params?
     false
   end
 
+  attr_reader :loaded
+
+  alias loaded? loaded
+
   def initialize(start_at, end_at, limit, params)
     @start_at = start_at&.to_datetime
     @end_at   = end_at&.to_datetime
     @limit    = limit&.to_i
     @params   = params
+    @loaded   = false
   end
 
   def key
     raise NotImplementedError
   end
 
+  def cache_key
+    ["metrics/dimension/#{key}", @start_at, @end_at, @limit, canonicalized_params].join(';')
+  end
+
   def data
-    raise NotImplementedError
+    load
   end
 
   def self.model_name
@@ -30,11 +41,28 @@ class Admin::Metrics::Dimension::BaseDimension
 
   protected
 
+  def load
+    unless loaded?
+      @values = Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) { perform_query }
+      @loaded = true
+    end
+
+    @values
+  end
+
+  def perform_query
+    raise NotImplementedError
+  end
+
   def time_period
     (@start_at..@end_at)
   end
 
   def params
-    raise NotImplementedError
+    {}
+  end
+
+  def canonicalized_params
+    params.to_h.to_a.sort_by { |k, _v| k.to_s }.map { |k, v| "#{k}=#{v}" }.join(';')
   end
 end
diff --git a/app/lib/admin/metrics/dimension/languages_dimension.rb b/app/lib/admin/metrics/dimension/languages_dimension.rb
index 1cc5f4120..f1cf82cf2 100644
--- a/app/lib/admin/metrics/dimension/languages_dimension.rb
+++ b/app/lib/admin/metrics/dimension/languages_dimension.rb
@@ -7,7 +7,9 @@ class Admin::Metrics::Dimension::LanguagesDimension < Admin::Metrics::Dimension:
     'languages'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT locale, count(*) AS value
       FROM users
diff --git a/app/lib/admin/metrics/dimension/servers_dimension.rb b/app/lib/admin/metrics/dimension/servers_dimension.rb
index 3e80b6625..91bcce655 100644
--- a/app/lib/admin/metrics/dimension/servers_dimension.rb
+++ b/app/lib/admin/metrics/dimension/servers_dimension.rb
@@ -5,7 +5,9 @@ class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::B
     'servers'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT accounts.domain, count(*) AS value
       FROM statuses
diff --git a/app/lib/admin/metrics/dimension/software_versions_dimension.rb b/app/lib/admin/metrics/dimension/software_versions_dimension.rb
index 34917404d..816615f99 100644
--- a/app/lib/admin/metrics/dimension/software_versions_dimension.rb
+++ b/app/lib/admin/metrics/dimension/software_versions_dimension.rb
@@ -7,12 +7,12 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim
     'software_versions'
   end
 
-  def data
+  protected
+
+  def perform_query
     [mastodon_version, ruby_version, postgresql_version, redis_version]
   end
 
-  private
-
   def mastodon_version
     value = Mastodon::Version.to_s
 
diff --git a/app/lib/admin/metrics/dimension/sources_dimension.rb b/app/lib/admin/metrics/dimension/sources_dimension.rb
index a9f061809..122807cdc 100644
--- a/app/lib/admin/metrics/dimension/sources_dimension.rb
+++ b/app/lib/admin/metrics/dimension/sources_dimension.rb
@@ -5,7 +5,9 @@ class Admin::Metrics::Dimension::SourcesDimension < Admin::Metrics::Dimension::B
     'sources'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT oauth_applications.name, count(*) AS value
       FROM users
diff --git a/app/lib/admin/metrics/dimension/space_usage_dimension.rb b/app/lib/admin/metrics/dimension/space_usage_dimension.rb
index aa00a2e18..5867c5bab 100644
--- a/app/lib/admin/metrics/dimension/space_usage_dimension.rb
+++ b/app/lib/admin/metrics/dimension/space_usage_dimension.rb
@@ -8,12 +8,12 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension
     'space_usage'
   end
 
-  def data
+  protected
+
+  def perform_query
     [postgresql_size, redis_size, media_size]
   end
 
-  private
-
   def postgresql_size
     value = ActiveRecord::Base.connection.execute('SELECT pg_database_size(current_database())').first['pg_database_size']
 
diff --git a/app/lib/admin/metrics/dimension/tag_languages_dimension.rb b/app/lib/admin/metrics/dimension/tag_languages_dimension.rb
index afbc8cde8..e1349c229 100644
--- a/app/lib/admin/metrics/dimension/tag_languages_dimension.rb
+++ b/app/lib/admin/metrics/dimension/tag_languages_dimension.rb
@@ -11,7 +11,9 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi
     'tag_languages'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT COALESCE(statuses.language, 'und') AS language, count(*) AS value
       FROM statuses
@@ -28,8 +30,6 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi
     rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } }
   end
 
-  private
-
   def params
     @params.permit(:id)
   end
diff --git a/app/lib/admin/metrics/dimension/tag_servers_dimension.rb b/app/lib/admin/metrics/dimension/tag_servers_dimension.rb
index 12c5980d7..7ddf3378c 100644
--- a/app/lib/admin/metrics/dimension/tag_servers_dimension.rb
+++ b/app/lib/admin/metrics/dimension/tag_servers_dimension.rb
@@ -9,7 +9,9 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension
     'tag_servers'
   end
 
-  def data
+  protected
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT accounts.domain, count(*) AS value
       FROM statuses
@@ -27,8 +29,6 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension
     rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } }
   end
 
-  private
-
   def params
     @params.permit(:id)
   end
diff --git a/app/lib/admin/metrics/measure/active_users_measure.rb b/app/lib/admin/metrics/measure/active_users_measure.rb
index 513189780..e6f09d4bc 100644
--- a/app/lib/admin/metrics/measure/active_users_measure.rb
+++ b/app/lib/admin/metrics/measure/active_users_measure.rb
@@ -5,20 +5,20 @@ class Admin::Metrics::Measure::ActiveUsersMeasure < Admin::Metrics::Measure::Bas
     'active_users'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     activity_tracker.sum(time_period.first, time_period.last)
   end
 
-  def previous_total
+  def perform_previous_total_query
     activity_tracker.sum(previous_time_period.first, previous_time_period.last)
   end
 
-  def data
+  def perform_data_query
     activity_tracker.get(time_period.first, time_period.last).map { |date, value| { date: date.to_time(:utc).iso8601, value: value.to_s } }
   end
 
-  protected
-
   def activity_tracker
     @activity_tracker ||= ActivityTracker.new('activity:logins', :unique)
   end
diff --git a/app/lib/admin/metrics/measure/base_measure.rb b/app/lib/admin/metrics/measure/base_measure.rb
index 0107ffd9c..ed1df9c7d 100644
--- a/app/lib/admin/metrics/measure/base_measure.rb
+++ b/app/lib/admin/metrics/measure/base_measure.rb
@@ -1,14 +1,25 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Measure::BaseMeasure
+  CACHE_TTL = 5.minutes.freeze
+
   def self.with_params?
     false
   end
 
+  attr_reader :loaded
+
+  alias loaded? loaded
+
   def initialize(start_at, end_at, params)
     @start_at = start_at&.to_datetime
     @end_at   = end_at&.to_datetime
     @params   = params
+    @loaded   = false
+  end
+
+  def cache_key
+    ["metrics/measure/#{key}", @start_at, @end_at, canonicalized_params].join(';')
   end
 
   def key
@@ -16,15 +27,15 @@ class Admin::Metrics::Measure::BaseMeasure
   end
 
   def total
-    raise NotImplementedError
+    load[:total]
   end
 
   def previous_total
-    raise NotImplementedError
+    load[:previous_total]
   end
 
   def data
-    raise NotImplementedError
+    load[:data]
   end
 
   def self.model_name
@@ -37,6 +48,35 @@ class Admin::Metrics::Measure::BaseMeasure
 
   protected
 
+  def load
+    unless loaded?
+      @values = Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) { perform_queries }.with_indifferent_access
+      @loaded = true
+    end
+
+    @values
+  end
+
+  def perform_queries
+    {
+      total: perform_total_query,
+      previous_total: perform_previous_total_query,
+      data: perform_data_query,
+    }
+  end
+
+  def perform_total_query
+    raise NotImplementedError
+  end
+
+  def perform_previous_total_query
+    raise NotImplementedError
+  end
+
+  def perform_data_query
+    raise NotImplementedError
+  end
+
   def time_period
     (@start_at..@end_at)
   end
@@ -50,6 +90,10 @@ class Admin::Metrics::Measure::BaseMeasure
   end
 
   def params
-    raise NotImplementedError
+    {}
+  end
+
+  def canonicalized_params
+    params.to_h.to_a.sort_by { |k, _v| k.to_s }.map { |k, v| "#{k}=#{v}" }.join(';')
   end
 end
diff --git a/app/lib/admin/metrics/measure/interactions_measure.rb b/app/lib/admin/metrics/measure/interactions_measure.rb
index b928fdb8f..7a2b7e0fa 100644
--- a/app/lib/admin/metrics/measure/interactions_measure.rb
+++ b/app/lib/admin/metrics/measure/interactions_measure.rb
@@ -5,20 +5,20 @@ class Admin::Metrics::Measure::InteractionsMeasure < Admin::Metrics::Measure::Ba
     'interactions'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     activity_tracker.sum(time_period.first, time_period.last)
   end
 
-  def previous_total
+  def perform_previous_total_query
     activity_tracker.sum(previous_time_period.first, previous_time_period.last)
   end
 
-  def data
+  def perform_data_query
     activity_tracker.get(time_period.first, time_period.last).map { |date, value| { date: date.to_time(:utc).iso8601, value: value.to_s } }
   end
 
-  protected
-
   def activity_tracker
     @activity_tracker ||= ActivityTracker.new('activity:interactions', :basic)
   end
diff --git a/app/lib/admin/metrics/measure/new_users_measure.rb b/app/lib/admin/metrics/measure/new_users_measure.rb
index b31679ad3..71191f1a2 100644
--- a/app/lib/admin/metrics/measure/new_users_measure.rb
+++ b/app/lib/admin/metrics/measure/new_users_measure.rb
@@ -5,15 +5,17 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe
     'new_users'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     User.where(created_at: time_period).count
   end
 
-  def previous_total
+  def perform_previous_total_query
     User.where(created_at: previous_time_period).count
   end
 
-  def data
+  def perform_data_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         WITH new_users AS (
diff --git a/app/lib/admin/metrics/measure/opened_reports_measure.rb b/app/lib/admin/metrics/measure/opened_reports_measure.rb
index 9acc2c33d..4b80a0c8c 100644
--- a/app/lib/admin/metrics/measure/opened_reports_measure.rb
+++ b/app/lib/admin/metrics/measure/opened_reports_measure.rb
@@ -5,15 +5,17 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B
     'opened_reports'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     Report.where(created_at: time_period).count
   end
 
-  def previous_total
+  def perform_previous_total_query
     Report.where(created_at: previous_time_period).count
   end
 
-  def data
+  def perform_data_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         WITH new_reports AS (
diff --git a/app/lib/admin/metrics/measure/resolved_reports_measure.rb b/app/lib/admin/metrics/measure/resolved_reports_measure.rb
index 00cb24f7e..4ab746c8f 100644
--- a/app/lib/admin/metrics/measure/resolved_reports_measure.rb
+++ b/app/lib/admin/metrics/measure/resolved_reports_measure.rb
@@ -5,15 +5,17 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
     'resolved_reports'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     Report.resolved.where(action_taken_at: time_period).count
   end
 
-  def previous_total
+  def perform_previous_total_query
     Report.resolved.where(action_taken_at: previous_time_period).count
   end
 
-  def data
+  def perform_data_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         WITH resolved_reports AS (
diff --git a/app/lib/admin/metrics/measure/tag_accounts_measure.rb b/app/lib/admin/metrics/measure/tag_accounts_measure.rb
index ef773081b..8f4512efe 100644
--- a/app/lib/admin/metrics/measure/tag_accounts_measure.rb
+++ b/app/lib/admin/metrics/measure/tag_accounts_measure.rb
@@ -9,20 +9,20 @@ class Admin::Metrics::Measure::TagAccountsMeasure < Admin::Metrics::Measure::Bas
     'tag_accounts'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     tag.history.aggregate(time_period).accounts
   end
 
-  def previous_total
+  def perform_previous_total_query
     tag.history.aggregate(previous_time_period).accounts
   end
 
-  def data
+  def perform_data_query
     time_period.map { |date| { date: date.to_time(:utc).iso8601, value: tag.history.get(date).accounts.to_s } }
   end
 
-  protected
-
   def tag
     @tag ||= Tag.find(params[:id])
   end
diff --git a/app/lib/admin/metrics/measure/tag_servers_measure.rb b/app/lib/admin/metrics/measure/tag_servers_measure.rb
index cc064f63f..11f229602 100644
--- a/app/lib/admin/metrics/measure/tag_servers_measure.rb
+++ b/app/lib/admin/metrics/measure/tag_servers_measure.rb
@@ -9,15 +9,17 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
     'tag_servers'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     tag.statuses.where('statuses.id BETWEEN ? AND ?', Mastodon::Snowflake.id_at(@start_at, with_random: false), Mastodon::Snowflake.id_at(@end_at, with_random: false)).joins(:account).count('distinct accounts.domain')
   end
 
-  def previous_total
+  def perform_previous_total_query
     tag.statuses.where('statuses.id BETWEEN ? AND ?', Mastodon::Snowflake.id_at(@start_at - length_of_period, with_random: false), Mastodon::Snowflake.id_at(@end_at - length_of_period, with_random: false)).joins(:account).count('distinct accounts.domain')
   end
 
-  def data
+  def perform_data_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         SELECT count(distinct accounts.domain) AS value
@@ -38,8 +40,6 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
     rows.map { |row| { date: row['day'], value: row['value'].to_s } }
   end
 
-  protected
-
   def tag
     @tag ||= Tag.find(params[:id])
   end
diff --git a/app/lib/admin/metrics/measure/tag_uses_measure.rb b/app/lib/admin/metrics/measure/tag_uses_measure.rb
index b7667bc6c..bce86b89f 100644
--- a/app/lib/admin/metrics/measure/tag_uses_measure.rb
+++ b/app/lib/admin/metrics/measure/tag_uses_measure.rb
@@ -9,20 +9,20 @@ class Admin::Metrics::Measure::TagUsesMeasure < Admin::Metrics::Measure::BaseMea
     'tag_uses'
   end
 
-  def total
+  protected
+
+  def perform_total_query
     tag.history.aggregate(time_period).uses
   end
 
-  def previous_total
+  def perform_previous_total_query
     tag.history.aggregate(previous_time_period).uses
   end
 
-  def data
+  def perform_data_query
     time_period.map { |date| { date: date.to_time(:utc).iso8601, value: tag.history.get(date).uses.to_s } }
   end
 
-  protected
-
   def tag
     @tag ||= Tag.find(params[:id])
   end
diff --git a/app/lib/admin/metrics/retention.rb b/app/lib/admin/metrics/retention.rb
index 0179a6e28..f6135ac1e 100644
--- a/app/lib/admin/metrics/retention.rb
+++ b/app/lib/admin/metrics/retention.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Admin::Metrics::Retention
+  CACHE_TTL = 5.minutes.freeze
+
   class Cohort < ActiveModelSerializers::Model
     attributes :period, :frequency, :data
   end
@@ -9,13 +11,37 @@ class Admin::Metrics::Retention
     attributes :date, :rate, :value
   end
 
+  attr_reader :loaded
+
+  alias loaded? loaded
+
   def initialize(start_at, end_at, frequency)
     @start_at  = start_at&.to_date
     @end_at    = end_at&.to_date
     @frequency = %w(day month).include?(frequency) ? frequency : 'day'
+    @loaded    = false
+  end
+
+  def cache_key
+    ['metrics/retention', @start_at, @end_at, @frequency].join(';')
   end
 
   def cohorts
+    load
+  end
+
+  protected
+
+  def load
+    unless loaded?
+      @values = Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) { perform_query }
+      @loaded = true
+    end
+
+    @values
+  end
+
+  def perform_query
     sql = <<-SQL.squish
       SELECT axis.*, (
         WITH new_users AS (
diff --git a/app/lib/video_metadata_extractor.rb b/app/lib/video_metadata_extractor.rb
index 03e40f923..2896620cb 100644
--- a/app/lib/video_metadata_extractor.rb
+++ b/app/lib/video_metadata_extractor.rb
@@ -2,7 +2,7 @@
 
 class VideoMetadataExtractor
   attr_reader :duration, :bitrate, :video_codec, :audio_codec,
-              :colorspace, :width, :height, :frame_rate
+              :colorspace, :width, :height, :frame_rate, :r_frame_rate
 
   def initialize(path)
     @path     = path
@@ -42,6 +42,7 @@ class VideoMetadataExtractor
         @width       = video_stream[:width]
         @height      = video_stream[:height]
         @frame_rate  = video_stream[:avg_frame_rate] == '0/0' ? nil : Rational(video_stream[:avg_frame_rate])
+        @r_frame_rate = video_stream[:r_frame_rate] == '0/0' ? nil : Rational(video_stream[:r_frame_rate])
       end
 
       if (audio_stream = audio_streams.first)
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 9eaacdc03..4b38d729e 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -38,6 +38,12 @@ class MediaAttachment < ApplicationRecord
 
   MAX_DESCRIPTION_LENGTH = 1_500
 
+  IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 10.megabytes).to_i
+  VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.megabytes).to_i
+
+  MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
+  MAX_VIDEO_FRAME_RATE   = 60
+
   IMAGE_FILE_EXTENSIONS = %w(.jpg .jpeg .png .gif).freeze
   VIDEO_FILE_EXTENSIONS = %w(.webm .mp4 .m4v .mov).freeze
   AUDIO_FILE_EXTENSIONS = %w(.ogg .oga .mp3 .wav .flac .opus .aac .m4a .3gp .wma).freeze
@@ -75,6 +81,7 @@ class MediaAttachment < ApplicationRecord
   VIDEO_FORMAT = {
     format: 'mp4',
     content_type: 'video/mp4',
+    vfr_frame_rate_threshold: MAX_VIDEO_FRAME_RATE,
     convert_options: {
       output: {
         'loglevel' => 'fatal',
@@ -152,12 +159,6 @@ class MediaAttachment < ApplicationRecord
     all: '-quality 90 -strip +set modify-date +set create-date',
   }.freeze
 
-  IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 10.megabytes).to_i
-  VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.megabytes).to_i
-
-  MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
-  MAX_VIDEO_FRAME_RATE   = 60
-
   belongs_to :account,          inverse_of: :media_attachments, optional: true
   belongs_to :status,           inverse_of: :media_attachments, optional: true
   belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true
diff --git a/app/views/admin/account_warnings/_account_warning.html.haml b/app/views/admin/account_warnings/_account_warning.html.haml
index ef23c3b77..1462e76d0 100644
--- a/app/views/admin/account_warnings/_account_warning.html.haml
+++ b/app/views/admin/account_warnings/_account_warning.html.haml
@@ -1,7 +1,8 @@
-= link_to disputes_strike_path(account_warning), class: ['log-entry', account_warning.overruled? && 'log-entry--inactive'] do
+= link_to disputes_strike_path(account_warning), class: 'log-entry' do
   .log-entry__header
     .log-entry__avatar
-      = image_tag account_warning.target_account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar'
+      .indicator-icon{ class: account_warning.overruled? ? 'success' : 'failure' }
+        = fa_icon 'warning'
     .log-entry__content
       .log-entry__title
         = t(account_warning.action, scope: 'admin.strikes.actions', name: content_tag(:span, account_warning.account.username, class: 'username'), target: content_tag(:span, account_warning.target_account.acct, class: 'target')).html_safe
@@ -11,7 +12,7 @@
 
         - if account_warning.report_id.present?
           ·
-          = t('admin.reports.title', id: account_warning.report_id)
+          = t('admin.reports.report', id: account_warning.report_id)
 
         - if account_warning.overruled?
           ·
diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml
index 3dcb19016..7248b2574 100644
--- a/app/views/disputes/strikes/show.html.haml
+++ b/app/views/disputes/strikes/show.html.haml
@@ -23,7 +23,7 @@
   .report-header__card
     .strike-card
       - unless @strike.none_action?
-        %p= t "user_mailer.warning.explanation.#{@strike.action}"
+        %p= t "user_mailer.warning.explanation.#{@strike.action}", instance: Rails.configuration.x.local_domain
 
       - unless @strike.text.blank?
         = Formatter.instance.linkify(@strike.text)
@@ -34,9 +34,10 @@
           = t("user_mailer.warning.categories.#{@strike.report.category}")
 
         - if @strike.report.violation? && @strike.report.rule_ids.present?
-          %ul.rules-list
+          %ul.strike-card__rules
             - @strike.report.rules.each do |rule|
-              %li= rule.text
+              %li
+                %span.strike-card__rules__text= rule.text
 
       - if @strike.status_ids.present? && !@strike.status_ids.empty?
         %p
@@ -75,7 +76,7 @@
       .report-header__details__item__header
         %strong= t('disputes.strikes.recipient')
       .report-header__details__item__content
-        = admin_account_link_to @strike.target_account, path: can?(:show, @strike.target_account) ? admin_account_path(@strike.target_account_id) : ActivityPub::TagManager.instance.url_for(@strike.target_account)
+        = link_to @strike.target_account.username, can?(:show, @strike.target_account) ? admin_account_path(@strike.target_account_id) : ActivityPub::TagManager.instance.url_for(@strike.target_account), class: 'table-action-link'
     .report-header__details__item
       .report-header__details__item__header
         %strong= t('disputes.strikes.action_taken')
@@ -89,7 +90,7 @@
         .report-header__details__item__header
           %strong= t('disputes.strikes.associated_report')
         .report-header__details__item__content
-          = link_to t('admin.reports.report', id: @strike.report.id), admin_report_path(@strike.report)
+          = link_to t('admin.reports.report', id: @strike.report.id), admin_report_path(@strike.report), class: 'table-action-link'
     - if @appeal.persisted?
       .report-header__details__item
         .report-header__details__item__header
diff --git a/lib/paperclip/transcoder.rb b/lib/paperclip/transcoder.rb
index ec1305038..afd9f58ff 100644
--- a/lib/paperclip/transcoder.rb
+++ b/lib/paperclip/transcoder.rb
@@ -13,6 +13,7 @@ module Paperclip
       @time                = options[:time] || 3
       @passthrough_options = options[:passthrough_options]
       @convert_options     = options[:convert_options].dup
+      @vfr_threshold       = options[:vfr_frame_rate_threshold]
     end
 
     def make
@@ -41,6 +42,11 @@ module Paperclip
       when 'mp4'
         @output_options['acodec'] = 'aac'
         @output_options['strict'] = 'experimental'
+
+        if high_vfr?(metadata) && !eligible_to_passthrough?(metadata)
+          @output_options['vsync'] = 'vfr'
+          @output_options['r'] = @vfr_threshold
+        end
       end
 
       command_arguments, interpolations = prepare_command(destination)
@@ -88,13 +94,21 @@ module Paperclip
     end
 
     def update_options_from_metadata(metadata)
-      return unless @passthrough_options && @passthrough_options[:video_codecs].include?(metadata.video_codec) && @passthrough_options[:audio_codecs].include?(metadata.audio_codec) && @passthrough_options[:colorspaces].include?(metadata.colorspace)
+      return unless eligible_to_passthrough?(metadata)
 
       @format          = @passthrough_options[:options][:format] || @format
       @time            = @passthrough_options[:options][:time]   || @time
       @convert_options = @passthrough_options[:options][:convert_options].dup
     end
 
+    def high_vfr?(metadata)
+      @vfr_threshold && metadata.r_frame_rate && metadata.r_frame_rate > @vfr_threshold
+    end
+
+    def eligible_to_passthrough?(metadata)
+      @passthrough_options && @passthrough_options[:video_codecs].include?(metadata.video_codec) && @passthrough_options[:audio_codecs].include?(metadata.audio_codec) && @passthrough_options[:colorspaces].include?(metadata.colorspace)
+    end
+
     def update_attachment_type(metadata)
       @attachment.instance.type = MediaAttachment.types[:gifv] unless metadata.audio_codec
     end