Change design of federation pages in admin UI (#17704)
* Change design of federation pages in admin UI * Fix query performance in instance media attachments measure * Fix reblogs being included in instance languages dimension
This commit is contained in:
		
							parent
							
								
									318d34d528
								
							
						
					
					
						commit
						bd53dd5210
					
				
					 32 changed files with 712 additions and 235 deletions
				
			
		| 
						 | 
				
			
			@ -56,10 +56,6 @@ module Admin
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def show
 | 
			
		||||
      authorize @domain_block, :show?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def destroy
 | 
			
		||||
      authorize @domain_block, :destroy?
 | 
			
		||||
      UnblockDomainService.new.call(@domain_block)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,28 +4,26 @@ module Admin
 | 
			
		|||
  class InstancesController < BaseController
 | 
			
		||||
    before_action :set_instances, only: :index
 | 
			
		||||
    before_action :set_instance, except: :index
 | 
			
		||||
    before_action :set_exhausted_deliveries_days, only: :show
 | 
			
		||||
 | 
			
		||||
    def index
 | 
			
		||||
      authorize :instance, :index?
 | 
			
		||||
      preload_delivery_failures!
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def show
 | 
			
		||||
      authorize :instance, :show?
 | 
			
		||||
      @time_period = (6.days.ago.to_date...Time.now.utc.to_date)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def destroy
 | 
			
		||||
      authorize :instance, :destroy?
 | 
			
		||||
 | 
			
		||||
      Admin::DomainPurgeWorker.perform_async(@instance.domain)
 | 
			
		||||
 | 
			
		||||
      log_action :destroy, @instance
 | 
			
		||||
      redirect_to admin_instances_path, notice: I18n.t('admin.instances.destroyed_msg', domain: @instance.domain)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def clear_delivery_errors
 | 
			
		||||
      authorize :delivery, :clear_delivery_errors?
 | 
			
		||||
 | 
			
		||||
      @instance.delivery_failure_tracker.clear_failures!
 | 
			
		||||
      redirect_to admin_instance_path(@instance.domain)
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			@ -33,11 +31,9 @@ module Admin
 | 
			
		|||
    def restart_delivery
 | 
			
		||||
      authorize :delivery, :restart_delivery?
 | 
			
		||||
 | 
			
		||||
      last_unavailable_domain = unavailable_domain
 | 
			
		||||
 | 
			
		||||
      if last_unavailable_domain.present?
 | 
			
		||||
      if @instance.unavailable?
 | 
			
		||||
        @instance.delivery_failure_tracker.track_success!
 | 
			
		||||
        log_action :destroy, last_unavailable_domain
 | 
			
		||||
        log_action :destroy, @instance.unavailable_domain
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      redirect_to admin_instance_path(@instance.domain)
 | 
			
		||||
| 
						 | 
				
			
			@ -45,8 +41,7 @@ module Admin
 | 
			
		|||
 | 
			
		||||
    def stop_delivery
 | 
			
		||||
      authorize :delivery, :stop_delivery?
 | 
			
		||||
 | 
			
		||||
      UnavailableDomain.create(domain: @instance.domain)
 | 
			
		||||
      unavailable_domain = UnavailableDomain.create!(domain: @instance.domain)
 | 
			
		||||
      log_action :create, unavailable_domain
 | 
			
		||||
      redirect_to admin_instance_path(@instance.domain)
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			@ -57,12 +52,11 @@ module Admin
 | 
			
		|||
      @instance = Instance.find(params[:id])
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def set_exhausted_deliveries_days
 | 
			
		||||
      @exhausted_deliveries_days = @instance.delivery_failure_tracker.exhausted_deliveries_days
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def set_instances
 | 
			
		||||
      @instances = filtered_instances.page(params[:page])
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def preload_delivery_failures!
 | 
			
		||||
      warning_domains_map = DeliveryFailureTracker.warning_domains_map
 | 
			
		||||
 | 
			
		||||
      @instances.each do |instance|
 | 
			
		||||
| 
						 | 
				
			
			@ -70,10 +64,6 @@ module Admin
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def unavailable_domain
 | 
			
		||||
      UnavailableDomain.find_by(domain: @instance.domain)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def filtered_instances
 | 
			
		||||
      InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,12 +68,12 @@ export default class Counter extends React.PureComponent {
 | 
			
		|||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      const measure = data[0];
 | 
			
		||||
      const percentChange = percIncrease(measure.previous_total * 1, measure.total * 1);
 | 
			
		||||
      const percentChange = measure.previous_total && percIncrease(measure.previous_total * 1, measure.total * 1);
 | 
			
		||||
 | 
			
		||||
      content = (
 | 
			
		||||
        <React.Fragment>
 | 
			
		||||
          <span className='sparkline__value__total'><FormattedNumber value={measure.total} /></span>
 | 
			
		||||
          <span className={classNames('sparkline__value__change', { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'}<FormattedNumber value={percentChange} style='percent' /></span>
 | 
			
		||||
          <span className='sparkline__value__total'>{measure.human_value || <FormattedNumber value={measure.total} />}</span>
 | 
			
		||||
          {measure.previous_total && (<span className={classNames('sparkline__value__change', { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'}<FormattedNumber value={percentChange} style='percent' /></span>)}
 | 
			
		||||
        </React.Fragment>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -367,6 +367,21 @@ body,
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .positive-hint,
 | 
			
		||||
  .negative-hint,
 | 
			
		||||
  .neutral-hint {
 | 
			
		||||
    a {
 | 
			
		||||
      color: inherit;
 | 
			
		||||
      text-decoration: underline;
 | 
			
		||||
 | 
			
		||||
      &:focus,
 | 
			
		||||
      &:hover,
 | 
			
		||||
      &:active {
 | 
			
		||||
        text-decoration: none;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .positive-hint {
 | 
			
		||||
    color: $valid-value-color;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
| 
						 | 
				
			
			@ -1596,3 +1611,38 @@ a.sparkline {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.availability-indicator {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  margin-bottom: 30px;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  line-height: 21px;
 | 
			
		||||
 | 
			
		||||
  &__hint {
 | 
			
		||||
    padding: 0 15px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__graphic {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    margin: 0 -2px;
 | 
			
		||||
 | 
			
		||||
    &__item {
 | 
			
		||||
      display: block;
 | 
			
		||||
      flex: 0 0 auto;
 | 
			
		||||
      width: 4px;
 | 
			
		||||
      height: 21px;
 | 
			
		||||
      background: lighten($ui-base-color, 8%);
 | 
			
		||||
      margin: 0 2px;
 | 
			
		||||
      border-radius: 2px;
 | 
			
		||||
 | 
			
		||||
      &.positive {
 | 
			
		||||
        background: $valid-value-color;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &.negative {
 | 
			
		||||
        background: $error-value-color;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,6 +65,24 @@
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.horizontal-table {
 | 
			
		||||
    border-collapse: collapse;
 | 
			
		||||
    border-style: hidden;
 | 
			
		||||
 | 
			
		||||
    & > tbody > tr > th,
 | 
			
		||||
    & > tbody > tr > td {
 | 
			
		||||
      padding: 11px 10px;
 | 
			
		||||
      background: transparent;
 | 
			
		||||
      border: 1px solid lighten($ui-base-color, 8%);
 | 
			
		||||
      color: $secondary-text-color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & > tbody > tr > th {
 | 
			
		||||
      color: $darker-text-color;
 | 
			
		||||
      font-weight: 600;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.batch-table {
 | 
			
		||||
    & > thead > tr > th {
 | 
			
		||||
      background: $ui-base-color;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,8 @@ class Admin::Metrics::Dimension
 | 
			
		|||
    software_versions: Admin::Metrics::Dimension::SoftwareVersionsDimension,
 | 
			
		||||
    tag_servers: Admin::Metrics::Dimension::TagServersDimension,
 | 
			
		||||
    tag_languages: Admin::Metrics::Dimension::TagLanguagesDimension,
 | 
			
		||||
    instance_accounts: Admin::Metrics::Dimension::InstanceAccountsDimension,
 | 
			
		||||
    instance_languages: Admin::Metrics::Dimension::InstanceLanguagesDimension,
 | 
			
		||||
  }.freeze
 | 
			
		||||
 | 
			
		||||
  def self.retrieve(dimension_keys, start_at, end_at, limit, params)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Admin::Metrics::Dimension::InstanceAccountsDimension < Admin::Metrics::Dimension::BaseDimension
 | 
			
		||||
  include LanguagesHelper
 | 
			
		||||
 | 
			
		||||
  def self.with_params?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def key
 | 
			
		||||
    'instance_accounts'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def perform_query
 | 
			
		||||
    sql = <<-SQL.squish
 | 
			
		||||
      SELECT accounts.username, count(follows.*) AS value
 | 
			
		||||
      FROM accounts
 | 
			
		||||
      LEFT JOIN follows ON follows.target_account_id = accounts.id
 | 
			
		||||
      WHERE accounts.domain = $1
 | 
			
		||||
      GROUP BY accounts.id, follows.target_account_id
 | 
			
		||||
      ORDER BY value DESC
 | 
			
		||||
      LIMIT $2
 | 
			
		||||
    SQL
 | 
			
		||||
 | 
			
		||||
    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:domain]], [nil, @limit]])
 | 
			
		||||
 | 
			
		||||
    rows.map { |row| { key: row['username'], human_key: row['username'], value: row['value'].to_s } }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def params
 | 
			
		||||
    @params.permit(:domain)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Dimension::BaseDimension
 | 
			
		||||
  include LanguagesHelper
 | 
			
		||||
 | 
			
		||||
  def self.with_params?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def key
 | 
			
		||||
    'instance_languages'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def perform_query
 | 
			
		||||
    sql = <<-SQL.squish
 | 
			
		||||
      SELECT COALESCE(statuses.language, 'und') AS language, count(*) AS value
 | 
			
		||||
      FROM statuses
 | 
			
		||||
      INNER JOIN accounts ON accounts.id = statuses.account_id
 | 
			
		||||
      WHERE accounts.domain = $1
 | 
			
		||||
        AND statuses.id BETWEEN $2 AND $3
 | 
			
		||||
        AND statuses.reblog_of_id IS NULL
 | 
			
		||||
      GROUP BY COALESCE(statuses.language, 'und')
 | 
			
		||||
      ORDER BY count(*) DESC
 | 
			
		||||
      LIMIT $4
 | 
			
		||||
    SQL
 | 
			
		||||
 | 
			
		||||
    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:domain]], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @limit]])
 | 
			
		||||
 | 
			
		||||
    rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def params
 | 
			
		||||
    @params.permit(:domain)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +10,12 @@ class Admin::Metrics::Measure
 | 
			
		|||
    tag_accounts: Admin::Metrics::Measure::TagAccountsMeasure,
 | 
			
		||||
    tag_uses: Admin::Metrics::Measure::TagUsesMeasure,
 | 
			
		||||
    tag_servers: Admin::Metrics::Measure::TagServersMeasure,
 | 
			
		||||
    instance_accounts: Admin::Metrics::Measure::InstanceAccountsMeasure,
 | 
			
		||||
    instance_media_attachments: Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure,
 | 
			
		||||
    instance_reports: Admin::Metrics::Measure::InstanceReportsMeasure,
 | 
			
		||||
    instance_statuses: Admin::Metrics::Measure::InstanceStatusesMeasure,
 | 
			
		||||
    instance_follows: Admin::Metrics::Measure::InstanceFollowsMeasure,
 | 
			
		||||
    instance_followers: Admin::Metrics::Measure::InstanceFollowersMeasure,
 | 
			
		||||
  }.freeze
 | 
			
		||||
 | 
			
		||||
  def self.retrieve(measure_keys, start_at, end_at, params)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,14 @@ class Admin::Metrics::Measure::BaseMeasure
 | 
			
		|||
    raise NotImplementedError
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def unit
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def total_in_time_range?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def total
 | 
			
		||||
    load[:total]
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										58
									
								
								app/lib/admin/metrics/measure/instance_accounts_measure.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/lib/admin/metrics/measure/instance_accounts_measure.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure::BaseMeasure
 | 
			
		||||
  def self.with_params?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def key
 | 
			
		||||
    'instance_accounts'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def total_in_time_range?
 | 
			
		||||
    false
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def perform_total_query
 | 
			
		||||
    Account.where(domain: params[:domain]).count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_previous_total_query
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_data_query
 | 
			
		||||
    sql = <<-SQL.squish
 | 
			
		||||
      SELECT axis.*, (
 | 
			
		||||
        WITH new_accounts AS (
 | 
			
		||||
          SELECT accounts.id
 | 
			
		||||
          FROM accounts
 | 
			
		||||
          WHERE date_trunc('day', accounts.created_at)::date = axis.period
 | 
			
		||||
            AND accounts.domain = $3::text
 | 
			
		||||
        )
 | 
			
		||||
        SELECT count(*) FROM new_accounts
 | 
			
		||||
      ) AS value
 | 
			
		||||
      FROM (
 | 
			
		||||
        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
 | 
			
		||||
      ) AS axis
 | 
			
		||||
    SQL
 | 
			
		||||
 | 
			
		||||
    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
 | 
			
		||||
 | 
			
		||||
    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def time_period
 | 
			
		||||
    (@start_at.to_date..@end_at.to_date)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def previous_time_period
 | 
			
		||||
    ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def params
 | 
			
		||||
    @params.permit(:domain)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										59
									
								
								app/lib/admin/metrics/measure/instance_followers_measure.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/lib/admin/metrics/measure/instance_followers_measure.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measure::BaseMeasure
 | 
			
		||||
  def self.with_params?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def key
 | 
			
		||||
    'instance_followers'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def total_in_time_range?
 | 
			
		||||
    false
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def perform_total_query
 | 
			
		||||
    Follow.joins(:account).merge(Account.where(domain: params[:domain])).count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_previous_total_query
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_data_query
 | 
			
		||||
    sql = <<-SQL.squish
 | 
			
		||||
      SELECT axis.*, (
 | 
			
		||||
        WITH new_followers AS (
 | 
			
		||||
          SELECT follows.id
 | 
			
		||||
          FROM follows
 | 
			
		||||
          INNER JOIN accounts ON follows.account_id = accounts.id
 | 
			
		||||
          WHERE date_trunc('day', follows.created_at)::date = axis.period
 | 
			
		||||
            AND accounts.domain = $3::text
 | 
			
		||||
        )
 | 
			
		||||
        SELECT count(*) FROM new_followers
 | 
			
		||||
      ) AS value
 | 
			
		||||
      FROM (
 | 
			
		||||
        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
 | 
			
		||||
      ) AS axis
 | 
			
		||||
    SQL
 | 
			
		||||
 | 
			
		||||
    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
 | 
			
		||||
 | 
			
		||||
    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def time_period
 | 
			
		||||
    (@start_at.to_date..@end_at.to_date)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def previous_time_period
 | 
			
		||||
    ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def params
 | 
			
		||||
    @params.permit(:domain)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										59
									
								
								app/lib/admin/metrics/measure/instance_follows_measure.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/lib/admin/metrics/measure/instance_follows_measure.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure::BaseMeasure
 | 
			
		||||
  def self.with_params?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def key
 | 
			
		||||
    'instance_follows'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def total_in_time_range?
 | 
			
		||||
    false
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def perform_total_query
 | 
			
		||||
    Follow.joins(:target_account).merge(Account.where(domain: params[:domain])).count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_previous_total_query
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_data_query
 | 
			
		||||
    sql = <<-SQL.squish
 | 
			
		||||
      SELECT axis.*, (
 | 
			
		||||
        WITH new_follows AS (
 | 
			
		||||
          SELECT follows.id
 | 
			
		||||
          FROM follows
 | 
			
		||||
          INNER JOIN accounts ON follows.target_account_id = accounts.id
 | 
			
		||||
          WHERE date_trunc('day', follows.created_at)::date = axis.period
 | 
			
		||||
            AND accounts.domain = $3::text
 | 
			
		||||
        )
 | 
			
		||||
        SELECT count(*) FROM new_follows
 | 
			
		||||
      ) AS value
 | 
			
		||||
      FROM (
 | 
			
		||||
        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
 | 
			
		||||
      ) AS axis
 | 
			
		||||
    SQL
 | 
			
		||||
 | 
			
		||||
    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
 | 
			
		||||
 | 
			
		||||
    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def time_period
 | 
			
		||||
    (@start_at.to_date..@end_at.to_date)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def previous_time_period
 | 
			
		||||
    ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def params
 | 
			
		||||
    @params.permit(:domain)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics::Measure::BaseMeasure
 | 
			
		||||
  include ActionView::Helpers::NumberHelper
 | 
			
		||||
 | 
			
		||||
  def self.with_params?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def key
 | 
			
		||||
    'instance_media_attachments'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def unit
 | 
			
		||||
    'bytes'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def value_to_human_value(value)
 | 
			
		||||
    number_to_human_size(value)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def total_in_time_range?
 | 
			
		||||
    false
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def perform_total_query
 | 
			
		||||
    MediaAttachment.joins(:account).merge(Account.where(domain: params[:domain])).sum('COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_previous_total_query
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_data_query
 | 
			
		||||
    sql = <<-SQL.squish
 | 
			
		||||
      SELECT axis.*, (
 | 
			
		||||
        WITH new_media_attachments AS (
 | 
			
		||||
          SELECT COALESCE(media_attachments.file_file_size, 0) + COALESCE(media_attachments.thumbnail_file_size, 0) AS size
 | 
			
		||||
          FROM media_attachments
 | 
			
		||||
          INNER JOIN accounts ON accounts.id = media_attachments.account_id
 | 
			
		||||
          WHERE date_trunc('day', media_attachments.created_at)::date = axis.period
 | 
			
		||||
            AND accounts.domain = $3::text
 | 
			
		||||
        )
 | 
			
		||||
        SELECT SUM(size) FROM new_media_attachments
 | 
			
		||||
      ) AS value
 | 
			
		||||
      FROM (
 | 
			
		||||
        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
 | 
			
		||||
      ) AS axis
 | 
			
		||||
    SQL
 | 
			
		||||
 | 
			
		||||
    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
 | 
			
		||||
 | 
			
		||||
    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def time_period
 | 
			
		||||
    (@start_at.to_date..@end_at.to_date)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def previous_time_period
 | 
			
		||||
    ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def params
 | 
			
		||||
    @params.permit(:domain)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										59
									
								
								app/lib/admin/metrics/measure/instance_reports_measure.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/lib/admin/metrics/measure/instance_reports_measure.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Admin::Metrics::Measure::InstanceReportsMeasure < Admin::Metrics::Measure::BaseMeasure
 | 
			
		||||
  def self.with_params?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def key
 | 
			
		||||
    'instance_reports'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def total_in_time_range?
 | 
			
		||||
    false
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def perform_total_query
 | 
			
		||||
    Report.where(target_account: Account.where(domain: params[:domain])).count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_previous_total_query
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_data_query
 | 
			
		||||
    sql = <<-SQL.squish
 | 
			
		||||
      SELECT axis.*, (
 | 
			
		||||
        WITH new_reports AS (
 | 
			
		||||
          SELECT reports.id
 | 
			
		||||
          FROM reports
 | 
			
		||||
          INNER JOIN accounts ON accounts.id = reports.target_account_id
 | 
			
		||||
          WHERE date_trunc('day', reports.created_at)::date = axis.period
 | 
			
		||||
            AND accounts.domain = $3::text
 | 
			
		||||
        )
 | 
			
		||||
        SELECT count(*) FROM new_reports
 | 
			
		||||
      ) AS value
 | 
			
		||||
      FROM (
 | 
			
		||||
        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
 | 
			
		||||
      ) AS axis
 | 
			
		||||
    SQL
 | 
			
		||||
 | 
			
		||||
    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
 | 
			
		||||
 | 
			
		||||
    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def time_period
 | 
			
		||||
    (@start_at.to_date..@end_at.to_date)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def previous_time_period
 | 
			
		||||
    ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def params
 | 
			
		||||
    @params.permit(:domain)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										60
									
								
								app/lib/admin/metrics/measure/instance_statuses_measure.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								app/lib/admin/metrics/measure/instance_statuses_measure.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,60 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure::BaseMeasure
 | 
			
		||||
  def self.with_params?
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def key
 | 
			
		||||
    'instance_statuses'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def total_in_time_range?
 | 
			
		||||
    false
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def perform_total_query
 | 
			
		||||
    Status.joins(:account).merge(Account.where(domain: params[:domain])).count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_previous_total_query
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def perform_data_query
 | 
			
		||||
    sql = <<-SQL.squish
 | 
			
		||||
      SELECT axis.*, (
 | 
			
		||||
        WITH new_statuses AS (
 | 
			
		||||
          SELECT statuses.id
 | 
			
		||||
          FROM statuses
 | 
			
		||||
          INNER JOIN accounts ON accounts.id = statuses.account_id
 | 
			
		||||
          WHERE statuses.id BETWEEN $3 AND $4
 | 
			
		||||
            AND accounts.domain = $5::text
 | 
			
		||||
            AND date_trunc('day', statuses.created_at)::date = axis.period
 | 
			
		||||
        )
 | 
			
		||||
        SELECT count(*) FROM new_statuses
 | 
			
		||||
      ) AS value
 | 
			
		||||
      FROM (
 | 
			
		||||
        SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
 | 
			
		||||
      ) AS axis
 | 
			
		||||
    SQL
 | 
			
		||||
 | 
			
		||||
    rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, params[:domain]]])
 | 
			
		||||
 | 
			
		||||
    rows.map { |row| { date: row['period'], value: row['value'].to_s } }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def time_period
 | 
			
		||||
    (@start_at.to_date..@end_at.to_date)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def previous_time_period
 | 
			
		||||
    ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def params
 | 
			
		||||
    @params.permit(:domain)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ class DeliveryFailureTracker
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def exhausted_deliveries_days
 | 
			
		||||
    Redis.current.smembers(exhausted_deliveries_key).sort.map { |date| Date.new(date.slice(0, 4).to_i, date.slice(4, 2).to_i, date.slice(6, 2).to_i) }
 | 
			
		||||
    @exhausted_deliveries_days ||= Redis.current.smembers(exhausted_deliveries_key).sort.map { |date| Date.new(date.slice(0, 4).to_i, date.slice(4, 2).to_i, date.slice(6, 2).to_i) }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  alias reset! track_success!
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,14 @@ class DomainBlock < ApplicationRecord
 | 
			
		|||
  scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) }
 | 
			
		||||
  scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain')) }
 | 
			
		||||
 | 
			
		||||
  def policies
 | 
			
		||||
    if suspend?
 | 
			
		||||
      :suspend
 | 
			
		||||
    else
 | 
			
		||||
      [severity.to_sym, reject_media? ? :reject_media : nil, reject_reports? ? :reject_reports : nil].reject { |policy| policy == :noop || policy.nil? }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  class << self
 | 
			
		||||
    def suspend?(domain)
 | 
			
		||||
      !!rule_for(domain)&.suspend?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,35 +32,27 @@ class Instance < ApplicationRecord
 | 
			
		|||
    @delivery_failure_tracker ||= DeliveryFailureTracker.new(domain)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def following_count
 | 
			
		||||
    @following_count ||= Follow.where(account: accounts).count
 | 
			
		||||
  def unavailable?
 | 
			
		||||
    unavailable_domain.present?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def followers_count
 | 
			
		||||
    @followers_count ||= Follow.where(target_account: accounts).count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def reports_count
 | 
			
		||||
    @reports_count ||= Report.where(target_account: accounts).count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def blocks_count
 | 
			
		||||
    @blocks_count ||= Block.where(target_account: accounts).count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def public_comment
 | 
			
		||||
    domain_block&.public_comment
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def private_comment
 | 
			
		||||
    domain_block&.private_comment
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def media_storage
 | 
			
		||||
    @media_storage ||= MediaAttachment.where(account: accounts).sum(:file_file_size)
 | 
			
		||||
  def failing?
 | 
			
		||||
    failure_days.present? || unavailable?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def to_param
 | 
			
		||||
    domain
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  delegate :exhausted_deliveries_days, to: :delivery_failure_tracker
 | 
			
		||||
 | 
			
		||||
  def availability_over_days(num_days, end_date = Time.now.utc.to_date)
 | 
			
		||||
    failures_map    = exhausted_deliveries_days.index_with { true }
 | 
			
		||||
    period_end_at   = exhausted_deliveries_days.last || end_date
 | 
			
		||||
    period_start_at = period_end_at - num_days.days
 | 
			
		||||
 | 
			
		||||
    (period_start_at..period_end_at).map do |date|
 | 
			
		||||
      [date, failures_map[date]]
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,8 +4,7 @@ class InstanceFilter
 | 
			
		|||
  KEYS = %i(
 | 
			
		||||
    limited
 | 
			
		||||
    by_domain
 | 
			
		||||
    warning
 | 
			
		||||
    unavailable
 | 
			
		||||
    availability
 | 
			
		||||
  ).freeze
 | 
			
		||||
 | 
			
		||||
  attr_reader :params
 | 
			
		||||
| 
						 | 
				
			
			@ -34,12 +33,21 @@ class InstanceFilter
 | 
			
		|||
      Instance.joins(:domain_allow).reorder(Arel.sql('domain_allows.id desc'))
 | 
			
		||||
    when 'by_domain'
 | 
			
		||||
      Instance.matches_domain(value)
 | 
			
		||||
    when 'warning'
 | 
			
		||||
      Instance.where(domain: DeliveryFailureTracker.warning_domains)
 | 
			
		||||
    when 'unavailable'
 | 
			
		||||
      Instance.joins(:unavailable_domain)
 | 
			
		||||
    when 'availability'
 | 
			
		||||
      availability_scope(value)
 | 
			
		||||
    else
 | 
			
		||||
      raise "Unknown filter: #{key}"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def availability_scope(value)
 | 
			
		||||
    case value
 | 
			
		||||
    when 'failing'
 | 
			
		||||
      Instance.where(domain: DeliveryFailureTracker.warning_domains)
 | 
			
		||||
    when 'unavailable'
 | 
			
		||||
      Instance.joins(:unavailable_domain)
 | 
			
		||||
    else
 | 
			
		||||
      raise "Unknown availability: #{value}"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,20 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class REST::Admin::MeasureSerializer < ActiveModel::Serializer
 | 
			
		||||
  attributes :key, :total, :previous_total, :data
 | 
			
		||||
  attributes :key, :unit, :total
 | 
			
		||||
 | 
			
		||||
  attribute :human_value, if: -> { object.respond_to?(:value_to_human_value) }
 | 
			
		||||
  attribute :previous_total, if: -> { object.total_in_time_range? }
 | 
			
		||||
  attribute :data
 | 
			
		||||
 | 
			
		||||
  def total
 | 
			
		||||
    object.total.to_s
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def human_value
 | 
			
		||||
    object.value_to_human_value(object.total)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def previous_total
 | 
			
		||||
    object.previous_total.to_s
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,10 +24,10 @@
 | 
			
		|||
    = f.input :obfuscate, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.obfuscate'), hint: I18n.t('admin.domain_blocks.obfuscate_hint')
 | 
			
		||||
 | 
			
		||||
  .field-group
 | 
			
		||||
    = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6
 | 
			
		||||
    = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), as: :string
 | 
			
		||||
 | 
			
		||||
  .field-group
 | 
			
		||||
    = f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), rows: 6
 | 
			
		||||
    = f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), as: :string
 | 
			
		||||
 | 
			
		||||
  .actions
 | 
			
		||||
    = f.button :button, t('generic.save_changes'), type: :submit
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,10 +24,10 @@
 | 
			
		|||
    = f.input :obfuscate, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.obfuscate'), hint: I18n.t('admin.domain_blocks.obfuscate_hint')
 | 
			
		||||
 | 
			
		||||
  .field-group
 | 
			
		||||
    = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6
 | 
			
		||||
    = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), as: :string
 | 
			
		||||
 | 
			
		||||
  .field-group
 | 
			
		||||
    = f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), rows: 6
 | 
			
		||||
    = f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), as: :string
 | 
			
		||||
 | 
			
		||||
  .actions
 | 
			
		||||
    = f.button :button, t('.create'), type: :submit
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,25 +0,0 @@
 | 
			
		|||
- content_for :page_title do
 | 
			
		||||
  = t('admin.domain_blocks.show.title', domain: @domain_block.domain)
 | 
			
		||||
 | 
			
		||||
- if @domain_block.private_comment.present?
 | 
			
		||||
  .speech-bubble
 | 
			
		||||
    .speech-bubble__bubble
 | 
			
		||||
      = simple_format(h(@domain_block.private_comment))
 | 
			
		||||
    .speech-bubble__owner= t 'admin.instances.private_comment'
 | 
			
		||||
 | 
			
		||||
- if @domain_block.public_comment.present?
 | 
			
		||||
  .speech-bubble
 | 
			
		||||
    .speech-bubble__bubble
 | 
			
		||||
      = simple_format(h(@domain_block.public_comment))
 | 
			
		||||
    .speech-bubble__owner= t 'admin.instances.public_comment'
 | 
			
		||||
 | 
			
		||||
= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f|
 | 
			
		||||
 | 
			
		||||
  - unless (@domain_block.noop?)
 | 
			
		||||
    %p= t(".retroactive.#{@domain_block.severity}")
 | 
			
		||||
    %p.hint= t(:affected_accounts,
 | 
			
		||||
      scope: [:admin, :domain_blocks, :show],
 | 
			
		||||
      count: @domain_block.affected_accounts_count)
 | 
			
		||||
 | 
			
		||||
  .actions
 | 
			
		||||
    = f.button :button, t('.undo'), type: :submit
 | 
			
		||||
| 
						 | 
				
			
			@ -1,2 +0,0 @@
 | 
			
		|||
%li.negative-hint
 | 
			
		||||
  = l(exhausted_deliveries_days)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,33 +1,15 @@
 | 
			
		|||
.directory__tag
 | 
			
		||||
  = link_to admin_instance_path(instance) do
 | 
			
		||||
    %h4
 | 
			
		||||
      = fa_icon 'warning fw' if instance.failing?
 | 
			
		||||
      = instance.domain
 | 
			
		||||
 | 
			
		||||
      %small
 | 
			
		||||
        - if instance.domain_block
 | 
			
		||||
          - first_item = true
 | 
			
		||||
          - if !instance.domain_block.noop?
 | 
			
		||||
            = t("admin.domain_blocks.severity.#{instance.domain_block.severity}")
 | 
			
		||||
            - first_item = false
 | 
			
		||||
          - unless instance.domain_block.suspend?
 | 
			
		||||
            - if instance.domain_block.reject_media?
 | 
			
		||||
              - unless first_item
 | 
			
		||||
                •
 | 
			
		||||
              = t('admin.domain_blocks.rejecting_media')
 | 
			
		||||
              - first_item = false
 | 
			
		||||
            - if instance.domain_block.reject_reports?
 | 
			
		||||
              - unless first_item
 | 
			
		||||
                •
 | 
			
		||||
              = t('admin.domain_blocks.rejecting_reports')
 | 
			
		||||
        - elsif whitelist_mode?
 | 
			
		||||
          = instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ')
 | 
			
		||||
        - elsif instance.domain_allow
 | 
			
		||||
          = t('admin.accounts.whitelisted')
 | 
			
		||||
        - else
 | 
			
		||||
          = t('admin.accounts.no_limits_imposed')
 | 
			
		||||
        - if instance.failure_days
 | 
			
		||||
          = ' / '
 | 
			
		||||
          %span.negative-hint
 | 
			
		||||
            = t('admin.instances.delivery.warning_message', count: instance.failure_days)
 | 
			
		||||
        - if instance.unavailable_domain
 | 
			
		||||
          = ' / '
 | 
			
		||||
          %span.negative-hint
 | 
			
		||||
            = t('admin.instances.delivery.unavailable_message')
 | 
			
		||||
 | 
			
		||||
    .trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= friendly_number_to_human instance.accounts_count
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,22 +17,11 @@
 | 
			
		|||
        %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
 | 
			
		||||
 | 
			
		||||
  .filter-subset
 | 
			
		||||
    %strong= t('admin.instances.delivery.title')
 | 
			
		||||
    %strong= t('admin.instances.availability.title')
 | 
			
		||||
    %ul
 | 
			
		||||
      %li= filter_link_to t('admin.instances.delivery.all'), warning: nil, unavailable: nil
 | 
			
		||||
      %li= filter_link_to t('admin.instances.delivery.warning'), warning: '1', unavailable: nil
 | 
			
		||||
      %li= filter_link_to t('admin.instances.delivery.unavailable'), warning: nil, unavailable: '1'
 | 
			
		||||
 | 
			
		||||
  .back-link
 | 
			
		||||
    = link_to admin_instances_path() do
 | 
			
		||||
      %i.fa.fa-chevron-left.fa-fw
 | 
			
		||||
      = t('admin.instances.back_to_all')
 | 
			
		||||
    = link_to admin_instances_path(limited: 1) do
 | 
			
		||||
      %i.fa.fa-chevron-left.fa-fw
 | 
			
		||||
      = t('admin.instances.back_to_limited')
 | 
			
		||||
    = link_to admin_instances_path(warning: 1) do
 | 
			
		||||
      %i.fa.fa-chevron-left.fa-fw
 | 
			
		||||
      = t('admin.instances.back_to_warning')
 | 
			
		||||
      %li= filter_link_to t('admin.instances.delivery.all'), availability: nil
 | 
			
		||||
      %li= filter_link_to t('admin.instances.delivery.failing'), availability: 'failing'
 | 
			
		||||
      %li= filter_link_to t('admin.instances.delivery.unavailable'), availability: 'unavailable'
 | 
			
		||||
 | 
			
		||||
- unless whitelist_mode?
 | 
			
		||||
  = form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,88 +1,95 @@
 | 
			
		|||
- content_for :page_title do
 | 
			
		||||
  = @instance.domain
 | 
			
		||||
 | 
			
		||||
.filters
 | 
			
		||||
  .back-link
 | 
			
		||||
    = link_to admin_instances_path() do
 | 
			
		||||
      %i.fa.fa-chevron-left.fa-fw
 | 
			
		||||
      = t('admin.instances.back_to_all')
 | 
			
		||||
    = link_to admin_instances_path(limited: 1) do
 | 
			
		||||
      %i.fa.fa-chevron-left.fa-fw
 | 
			
		||||
      = t('admin.instances.back_to_limited')
 | 
			
		||||
    = link_to admin_instances_path(warning: 1) do
 | 
			
		||||
      %i.fa.fa-chevron-left.fa-fw
 | 
			
		||||
      = t('admin.instances.back_to_warning')
 | 
			
		||||
- content_for :header_tags do
 | 
			
		||||
  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
 | 
			
		||||
 | 
			
		||||
.dashboard__counters
 | 
			
		||||
  %div
 | 
			
		||||
    = link_to admin_accounts_path(origin: 'remote', by_domain: @instance.domain) do
 | 
			
		||||
      .dashboard__counters__num= number_with_delimiter @instance.accounts_count
 | 
			
		||||
      .dashboard__counters__label= t 'admin.accounts.title'
 | 
			
		||||
  %div
 | 
			
		||||
    = link_to admin_reports_path(by_target_domain: @instance.domain) do
 | 
			
		||||
      .dashboard__counters__num= number_with_delimiter @instance.reports_count
 | 
			
		||||
      .dashboard__counters__label= t 'admin.instances.total_reported'
 | 
			
		||||
  %div
 | 
			
		||||
    %div
 | 
			
		||||
      .dashboard__counters__num= number_to_human_size @instance.media_storage
 | 
			
		||||
      .dashboard__counters__label= t 'admin.instances.total_storage'
 | 
			
		||||
  %div
 | 
			
		||||
    %div
 | 
			
		||||
      .dashboard__counters__num= number_with_delimiter @instance.following_count
 | 
			
		||||
      .dashboard__counters__label= t 'admin.instances.total_followed_by_them'
 | 
			
		||||
  %div
 | 
			
		||||
    %div
 | 
			
		||||
      .dashboard__counters__num= number_with_delimiter @instance.followers_count
 | 
			
		||||
      .dashboard__counters__label= t 'admin.instances.total_followed_by_us'
 | 
			
		||||
  %div
 | 
			
		||||
    %div
 | 
			
		||||
      .dashboard__counters__num= number_with_delimiter @instance.blocks_count
 | 
			
		||||
      .dashboard__counters__label= t 'admin.instances.total_blocked_by_us'
 | 
			
		||||
- content_for :heading_actions do
 | 
			
		||||
  = l(@time_period.first)
 | 
			
		||||
  = ' - '
 | 
			
		||||
  = l(@time_period.last)
 | 
			
		||||
 | 
			
		||||
  %div
 | 
			
		||||
    %div
 | 
			
		||||
      .dashboard__counters__num
 | 
			
		||||
        - if @instance.delivery_failure_tracker.available?
 | 
			
		||||
          = fa_icon 'check'
 | 
			
		||||
        - else
 | 
			
		||||
          = fa_icon 'times'
 | 
			
		||||
      .dashboard__counters__label= t 'admin.instances.delivery_available'
 | 
			
		||||
%p
 | 
			
		||||
  = fa_icon 'info fw'
 | 
			
		||||
  = t('admin.instances.totals_time_period_hint_html')
 | 
			
		||||
 | 
			
		||||
- if @instance.private_comment.present?
 | 
			
		||||
  .speech-bubble
 | 
			
		||||
    .speech-bubble__bubble
 | 
			
		||||
      = simple_format(h(@instance.private_comment))
 | 
			
		||||
    .speech-bubble__owner= t 'admin.instances.private_comment'
 | 
			
		||||
 | 
			
		||||
- if @instance.public_comment.present?
 | 
			
		||||
  .speech-bubble
 | 
			
		||||
    .speech-bubble__bubble
 | 
			
		||||
      = simple_format(h(@instance.public_comment))
 | 
			
		||||
    .speech-bubble__owner= t 'admin.instances.public_comment'
 | 
			
		||||
 | 
			
		||||
- unless @exhausted_deliveries_days.empty?
 | 
			
		||||
  %h4= t 'admin.instances.delivery_error_days'
 | 
			
		||||
  %ul
 | 
			
		||||
    = render partial: 'exhausted_deliveries_days', collection: @exhausted_deliveries_days
 | 
			
		||||
  %p.hint
 | 
			
		||||
    = t 'admin.instances.delivery_error_hint', count: DeliveryFailureTracker::FAILURE_DAYS_THRESHOLD
 | 
			
		||||
.dashboard
 | 
			
		||||
  .dashboard__item
 | 
			
		||||
    = react_admin_component :counter, measure: 'instance_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_accounts_measure'), href: admin_accounts_path(origin: 'remote', by_domain: @instance.domain)
 | 
			
		||||
  .dashboard__item
 | 
			
		||||
    = react_admin_component :counter, measure: 'instance_statuses', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_statuses_measure')
 | 
			
		||||
  .dashboard__item
 | 
			
		||||
    = react_admin_component :counter, measure: 'instance_media_attachments', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_media_attachments_measure')
 | 
			
		||||
  .dashboard__item
 | 
			
		||||
    = react_admin_component :counter, measure: 'instance_follows', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_follows_measure')
 | 
			
		||||
  .dashboard__item
 | 
			
		||||
    = react_admin_component :counter, measure: 'instance_followers', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_followers_measure')
 | 
			
		||||
  .dashboard__item
 | 
			
		||||
    = react_admin_component :counter, measure: 'instance_reports', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_reports_measure'), href: admin_reports_path(by_target_domain: @instance.domain)
 | 
			
		||||
  .dashboard__item
 | 
			
		||||
    = react_admin_component :dimension, dimension: 'instance_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, limit: 8, label: t('admin.instances.dashboard.instance_accounts_dimension')
 | 
			
		||||
  .dashboard__item
 | 
			
		||||
    = react_admin_component :dimension, dimension: 'instance_languages', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, limit: 8, label: t('admin.instances.dashboard.instance_languages_dimension')
 | 
			
		||||
 | 
			
		||||
%hr.spacer/
 | 
			
		||||
 | 
			
		||||
%div.action-buttons
 | 
			
		||||
  %div
 | 
			
		||||
    - if @instance.domain_allow
 | 
			
		||||
      = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@instance.domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
 | 
			
		||||
    - elsif @instance.domain_block
 | 
			
		||||
      = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
 | 
			
		||||
      = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button'
 | 
			
		||||
%h3= t('admin.instances.content_policies.title')
 | 
			
		||||
 | 
			
		||||
- if whitelist_mode?
 | 
			
		||||
  %p= t('admin.instances.content_policies.limited_federation_mode_description_html')
 | 
			
		||||
 | 
			
		||||
  - if @instance.domain_allow
 | 
			
		||||
    = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@instance.domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
 | 
			
		||||
  - else
 | 
			
		||||
    = link_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: 'button', method: :post
 | 
			
		||||
- else
 | 
			
		||||
  %p= t('admin.instances.content_policies.description_html')
 | 
			
		||||
 | 
			
		||||
  - if @instance.domain_block
 | 
			
		||||
    .table-wrapper
 | 
			
		||||
      %table.table.horizontal-table
 | 
			
		||||
        %tbody
 | 
			
		||||
          %tr
 | 
			
		||||
            %th= t('admin.instances.content_policies.comment')
 | 
			
		||||
            %td= @instance.domain_block.private_comment
 | 
			
		||||
          %tr
 | 
			
		||||
            %th= t('admin.instances.content_policies.reason')
 | 
			
		||||
            %td= @instance.domain_block.public_comment
 | 
			
		||||
          %tr
 | 
			
		||||
            %th= t('admin.instances.content_policies.policy')
 | 
			
		||||
            %td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ')
 | 
			
		||||
 | 
			
		||||
    = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
 | 
			
		||||
    = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
 | 
			
		||||
  - else
 | 
			
		||||
    = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
 | 
			
		||||
 | 
			
		||||
%hr.spacer/
 | 
			
		||||
 | 
			
		||||
%h3= t('admin.instances.availability.title')
 | 
			
		||||
 | 
			
		||||
%p
 | 
			
		||||
  = t('admin.instances.availability.description_html', count: DeliveryFailureTracker::FAILURE_DAYS_THRESHOLD)
 | 
			
		||||
 | 
			
		||||
.availability-indicator
 | 
			
		||||
  %ul.availability-indicator__graphic
 | 
			
		||||
    - @instance.availability_over_days(14).each do |(date, failing)|
 | 
			
		||||
      %li.availability-indicator__graphic__item{ class: failing ? 'negative' : 'neutral', title: l(date) }
 | 
			
		||||
  .availability-indicator__hint
 | 
			
		||||
    - if @instance.unavailable?
 | 
			
		||||
      %span.negative-hint
 | 
			
		||||
        = t('admin.instances.availability.failure_threshold_reached', date: l(@instance.unavailable_domain.created_at.to_date))
 | 
			
		||||
        = link_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
 | 
			
		||||
    - elsif @instance.exhausted_deliveries_days.empty?
 | 
			
		||||
      %span.positive-hint
 | 
			
		||||
        = t('admin.instances.availability.no_failures_recorded')
 | 
			
		||||
        = link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
 | 
			
		||||
    - else
 | 
			
		||||
      = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
 | 
			
		||||
    - if @instance.delivery_failure_tracker.available?
 | 
			
		||||
      - unless @exhausted_deliveries_days.empty?
 | 
			
		||||
        = link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button'
 | 
			
		||||
      = link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button'
 | 
			
		||||
    - else
 | 
			
		||||
      = link_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button'
 | 
			
		||||
    - if !@instance.delivery_failure_tracker.available? || @instance.accounts_count.zero? || @instance.domain_block&.suspend?
 | 
			
		||||
      = link_to t('admin.instances.purge'), admin_instance_path(@instance), data: { confirm: t('admin.instances.confirm_purge'), method: :delete }, class: 'button'
 | 
			
		||||
      %span.negative-hint
 | 
			
		||||
        = t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
 | 
			
		||||
        = link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
 | 
			
		||||
 | 
			
		||||
- if @instance.unavailable?
 | 
			
		||||
  %p= t('admin.instances.purge_description_html')
 | 
			
		||||
 | 
			
		||||
  = link_to t('admin.instances.purge'), admin_instance_path(@instance), data: { confirm: t('admin.instances.confirm_purge'), method: :delete }, class: 'button button--destructive'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
class Admin::DomainPurgeWorker
 | 
			
		||||
  include Sidekiq::Worker
 | 
			
		||||
 | 
			
		||||
  sidekiq_options queue: 'pull', lock: :until_executed
 | 
			
		||||
 | 
			
		||||
  def perform(domain)
 | 
			
		||||
    PurgeDomainService.new.call(domain)
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -450,21 +450,6 @@ en:
 | 
			
		|||
      reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions
 | 
			
		||||
      reject_reports: Reject reports
 | 
			
		||||
      reject_reports_hint: Ignore all reports coming from this domain. Irrelevant for suspensions
 | 
			
		||||
      rejecting_media: rejecting media files
 | 
			
		||||
      rejecting_reports: rejecting reports
 | 
			
		||||
      severity:
 | 
			
		||||
        silence: limited
 | 
			
		||||
        suspend: suspended
 | 
			
		||||
      show:
 | 
			
		||||
        affected_accounts:
 | 
			
		||||
          one: One account in the database affected
 | 
			
		||||
          other: "%{count} accounts in the database affected"
 | 
			
		||||
          zero: No account in the database is affected
 | 
			
		||||
        retroactive:
 | 
			
		||||
          silence: Undo limit of existing affected accounts from this domain
 | 
			
		||||
          suspend: Unsuspend existing affected accounts from this domain
 | 
			
		||||
        title: Undo domain block for %{domain}
 | 
			
		||||
        undo: Undo
 | 
			
		||||
      undo: Undo domain block
 | 
			
		||||
      view: View domain block
 | 
			
		||||
    email_domain_blocks:
 | 
			
		||||
| 
						 | 
				
			
			@ -495,23 +480,47 @@ en:
 | 
			
		|||
      title: Follow recommendations
 | 
			
		||||
      unsuppress: Restore follow recommendation
 | 
			
		||||
    instances:
 | 
			
		||||
      availability:
 | 
			
		||||
        description_html:
 | 
			
		||||
          one: If delivering to the domain fails <strong>%{count} day</strong> without succeeding, no further delivery attempts will be made unless a delivery <em>from</em> the domain is received.
 | 
			
		||||
          other: If delivering to the domain fails on <strong>%{count} different days</strong> without succeeding, no further delivery attempts will be made unless a delivery <em>from</em> the domain is received.
 | 
			
		||||
        failure_threshold_reached: Failure threshold reached on %{date}.
 | 
			
		||||
        failures_recorded:
 | 
			
		||||
          one: Failed attempt on %{count} day.
 | 
			
		||||
          other: Failed attempts on %{count} different days.
 | 
			
		||||
        no_failures_recorded: No failures on record.
 | 
			
		||||
        title: Availability
 | 
			
		||||
      back_to_all: All
 | 
			
		||||
      back_to_limited: Limited
 | 
			
		||||
      back_to_warning: Warning
 | 
			
		||||
      by_domain: Domain
 | 
			
		||||
      confirm_purge: Are you sure you want to permanently delete data from this domain?
 | 
			
		||||
      content_policies:
 | 
			
		||||
        comment: Internal note
 | 
			
		||||
        description_html: You can define content policies that will be applied to all accounts from this domain and any of its subdomains.
 | 
			
		||||
        policies:
 | 
			
		||||
          reject_media: Reject media
 | 
			
		||||
          reject_reports: Reject reports
 | 
			
		||||
          silence: Limit
 | 
			
		||||
          suspend: Suspend
 | 
			
		||||
        policy: Policy
 | 
			
		||||
        reason: Public reason
 | 
			
		||||
        title: Content policies
 | 
			
		||||
      dashboard:
 | 
			
		||||
        instance_accounts_dimension: Most followed accounts
 | 
			
		||||
        instance_accounts_measure: stored accounts
 | 
			
		||||
        instance_followers_measure: our followers there
 | 
			
		||||
        instance_follows_measure: their followers here
 | 
			
		||||
        instance_languages_dimension: Top languages
 | 
			
		||||
        instance_media_attachments_measure: stored media attachments
 | 
			
		||||
        instance_reports_measure: reports about them
 | 
			
		||||
        instance_statuses_measure: stored posts
 | 
			
		||||
      delivery:
 | 
			
		||||
        all: All
 | 
			
		||||
        clear: Clear delivery errors
 | 
			
		||||
        restart: Restart delivery
 | 
			
		||||
        stop: Stop delivery
 | 
			
		||||
        title: Delivery
 | 
			
		||||
        unavailable: Unavailable
 | 
			
		||||
        unavailable_message: Delivery unavailable
 | 
			
		||||
        warning: Warning
 | 
			
		||||
        warning_message:
 | 
			
		||||
          one: Delivery failure %{count} day
 | 
			
		||||
          other: Delivery failure %{count} days
 | 
			
		||||
      delivery_available: Delivery is available
 | 
			
		||||
      delivery_error_days: Delivery error days
 | 
			
		||||
      delivery_error_hint: If delivery is not possible for %{count} days, it will be automatically marked as undeliverable.
 | 
			
		||||
| 
						 | 
				
			
			@ -528,12 +537,14 @@ en:
 | 
			
		|||
      private_comment: Private comment
 | 
			
		||||
      public_comment: Public comment
 | 
			
		||||
      purge: Purge
 | 
			
		||||
      purge_description_html: If you believe this domain is offline for good, you can delete all account records and associated data from this domain from your storage. This may take a while.
 | 
			
		||||
      title: Federation
 | 
			
		||||
      total_blocked_by_us: Blocked by us
 | 
			
		||||
      total_followed_by_them: Followed by them
 | 
			
		||||
      total_followed_by_us: Followed by us
 | 
			
		||||
      total_reported: Reports about them
 | 
			
		||||
      total_storage: Media attachments
 | 
			
		||||
      totals_time_period_hint_html: The totals displayed below include data for all time.
 | 
			
		||||
    invites:
 | 
			
		||||
      deactivate_all: Deactivate all
 | 
			
		||||
      filter:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -191,7 +191,7 @@ Rails.application.routes.draw do
 | 
			
		|||
    get '/dashboard', to: 'dashboard#index'
 | 
			
		||||
 | 
			
		||||
    resources :domain_allows, only: [:new, :create, :show, :destroy]
 | 
			
		||||
    resources :domain_blocks, only: [:new, :create, :show, :destroy, :update, :edit]
 | 
			
		||||
    resources :domain_blocks, only: [:new, :create, :destroy, :update, :edit]
 | 
			
		||||
 | 
			
		||||
    resources :email_domain_blocks, only: [:index, :new, :create] do
 | 
			
		||||
      collection do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,15 +16,6 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'GET #show' do
 | 
			
		||||
    it 'returns http success' do
 | 
			
		||||
      domain_block = Fabricate(:domain_block)
 | 
			
		||||
      get :show, params: { id: domain_block.id }
 | 
			
		||||
 | 
			
		||||
      expect(response).to have_http_status(200)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'POST #create' do
 | 
			
		||||
    it 'blocks the domain when succeeded to save' do
 | 
			
		||||
      allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Reference in a new issue