Merge branch 'main' into glitch-soc/merge-upstream

Conflicts:
- `app/models/media_attachment.rb`:
  Upstream moved hardcoded values around, while in glitch-soc they are
  configurable.
  Moved them like upstream, but keeping glitch-soc's ability to configure
  them through env vars.
lolsob-rspec
Claire 2022-02-22 18:06:29 +01:00
commit e7ad957447
24 changed files with 242 additions and 94 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 (

View File

@ -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 (

View File

@ -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 (

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 (

View File

@ -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)

View File

@ -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

View File

@ -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?
·

View File

@ -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

View File

@ -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