Add management of delivery availability in Federation settings (#15771)
* Add management of delivery availavility in Federation settings * fix translate * Remove useless object creation * Fix DeepSource issue * Add shortcut for all * Fix DeepSource(skipcq) * Change 'remove' to 'clear' * Fix style * Change class method name (exhausted_deliveries_key_by)
This commit is contained in:
		| @@ -3,7 +3,8 @@ | ||||
| module Admin | ||||
|   class InstancesController < BaseController | ||||
|     before_action :set_instances, only: :index | ||||
|     before_action :set_instance, only: :show | ||||
|     before_action :set_instance, except: :index | ||||
|     before_action :set_exhausted_deliveries_days, only: :show | ||||
|  | ||||
|     def index | ||||
|       authorize :instance, :index? | ||||
| @@ -13,14 +14,55 @@ module Admin | ||||
|       authorize :instance, :show? | ||||
|     end | ||||
|  | ||||
|     def clear_delivery_errors | ||||
|       authorize :delivery, :clear_delivery_errors? | ||||
|  | ||||
|       @instance.delivery_failure_tracker.clear_failures! | ||||
|       redirect_to admin_instance_path(@instance.domain) | ||||
|     end | ||||
|  | ||||
|     def restart_delivery | ||||
|       authorize :delivery, :restart_delivery? | ||||
|  | ||||
|       last_unavailable_domain = unavailable_domain | ||||
|  | ||||
|       if last_unavailable_domain.present? | ||||
|         @instance.delivery_failure_tracker.track_success! | ||||
|         log_action :destroy, last_unavailable_domain | ||||
|       end | ||||
|  | ||||
|       redirect_to admin_instance_path(@instance.domain) | ||||
|     end | ||||
|  | ||||
|     def stop_delivery | ||||
|       authorize :delivery, :stop_delivery? | ||||
|  | ||||
|       UnavailableDomain.create(domain: @instance.domain) | ||||
|       log_action :create, unavailable_domain | ||||
|       redirect_to admin_instance_path(@instance.domain) | ||||
|     end | ||||
|  | ||||
|     private | ||||
|  | ||||
|     def set_instance | ||||
|       @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]) | ||||
|       warning_domains_map = DeliveryFailureTracker.warning_domains_map | ||||
|  | ||||
|       @instances.each do |instance| | ||||
|         instance.failure_days = warning_domains_map[instance.domain] | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     def unavailable_domain | ||||
|       UnavailableDomain.find_by(domain: @instance.domain) | ||||
|     end | ||||
|  | ||||
|     def filtered_instances | ||||
|   | ||||
| @@ -21,7 +21,7 @@ module Admin::ActionLogsHelper | ||||
|       record.shortcode | ||||
|     when 'Report' | ||||
|       link_to "##{record.id}", admin_report_path(record) | ||||
|     when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock' | ||||
|     when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain' | ||||
|       link_to record.domain, "https://#{record.domain}" | ||||
|     when 'Status' | ||||
|       link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record) | ||||
| @@ -38,7 +38,7 @@ module Admin::ActionLogsHelper | ||||
|     case type | ||||
|     when 'CustomEmoji' | ||||
|       attributes['shortcode'] | ||||
|     when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock' | ||||
|     when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain' | ||||
|       link_to attributes['domain'], "https://#{attributes['domain']}" | ||||
|     when 'Status' | ||||
|       tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count')) | ||||
|   | ||||
| @@ -17,6 +17,10 @@ class DeliveryFailureTracker | ||||
|     UnavailableDomain.find_by(domain: @host)&.destroy | ||||
|   end | ||||
|  | ||||
|   def clear_failures! | ||||
|     Redis.current.del(exhausted_deliveries_key) | ||||
|   end | ||||
|  | ||||
|   def days | ||||
|     Redis.current.scard(exhausted_deliveries_key) || 0 | ||||
|   end | ||||
| @@ -25,6 +29,10 @@ class DeliveryFailureTracker | ||||
|     !UnavailableDomain.where(domain: @host).exists? | ||||
|   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) } | ||||
|   end | ||||
|  | ||||
|   alias reset! track_success! | ||||
|  | ||||
|   class << self | ||||
| @@ -44,6 +52,24 @@ class DeliveryFailureTracker | ||||
|     def reset!(url) | ||||
|       new(url).reset! | ||||
|     end | ||||
|  | ||||
|     def warning_domains | ||||
|       domains = Redis.current.keys(exhausted_deliveries_key_by('*')).map do |key| | ||||
|         key.delete_prefix(exhausted_deliveries_key_by('')) | ||||
|       end | ||||
|  | ||||
|       domains - UnavailableDomain.all.pluck(:domain) | ||||
|     end | ||||
|  | ||||
|     def warning_domains_map | ||||
|       warning_domains.index_with { |domain| Redis.current.scard(exhausted_deliveries_key_by(domain)) } | ||||
|     end | ||||
|  | ||||
|     private | ||||
|  | ||||
|     def exhausted_deliveries_key_by(host) | ||||
|       "exhausted_deliveries:#{host}" | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   private | ||||
|   | ||||
| @@ -17,12 +17,14 @@ class Admin::ActionLogFilter | ||||
|     create_domain_allow: { target_type: 'DomainAllow', action: 'create' }.freeze, | ||||
|     create_domain_block: { target_type: 'DomainBlock', action: 'create' }.freeze, | ||||
|     create_email_domain_block: { target_type: 'EmailDomainBlock', action: 'create' }.freeze, | ||||
|     create_unavailable_domain: { target_type: 'UnavailableDomain', action: 'create' }.freeze, | ||||
|     demote_user: { target_type: 'User', action: 'demote' }.freeze, | ||||
|     destroy_announcement: { target_type: 'Announcement', action: 'destroy' }.freeze, | ||||
|     destroy_custom_emoji: { target_type: 'CustomEmoji', action: 'destroy' }.freeze, | ||||
|     destroy_domain_allow: { target_type: 'DomainAllow', action: 'destroy' }.freeze, | ||||
|     destroy_domain_block: { target_type: 'DomainBlock', action: 'destroy' }.freeze, | ||||
|     destroy_email_domain_block: { target_type: 'EmailDomainBlock', action: 'destroy' }.freeze, | ||||
|     destroy_unavailable_domain: { target_type: 'UnavailableDomain', action: 'destroy' }.freeze, | ||||
|     destroy_status: { target_type: 'Status', action: 'destroy' }.freeze, | ||||
|     disable_2fa_user: { target_type: 'User', action: 'disable' }.freeze, | ||||
|     disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze, | ||||
|   | ||||
| @@ -10,10 +10,13 @@ | ||||
| class Instance < ApplicationRecord | ||||
|   self.primary_key = :domain | ||||
|  | ||||
|   attr_accessor :failure_days | ||||
|  | ||||
|   has_many :accounts, foreign_key: :domain, primary_key: :domain | ||||
|  | ||||
|   belongs_to :domain_block, foreign_key: :domain, primary_key: :domain | ||||
|   belongs_to :domain_allow, foreign_key: :domain, primary_key: :domain | ||||
|   belongs_to :unavailable_domain, foreign_key: :domain, primary_key: :domain # skipcq: RB-RL1031 | ||||
|  | ||||
|   scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,8 @@ class InstanceFilter | ||||
|   KEYS = %i( | ||||
|     limited | ||||
|     by_domain | ||||
|     warning | ||||
|     unavailable | ||||
|   ).freeze | ||||
|  | ||||
|   attr_reader :params | ||||
| @@ -13,7 +15,7 @@ class InstanceFilter | ||||
|   end | ||||
|  | ||||
|   def results | ||||
|     scope = Instance.includes(:domain_block, :domain_allow).order(accounts_count: :desc) | ||||
|     scope = Instance.includes(:domain_block, :domain_allow, :unavailable_domain).order(accounts_count: :desc) | ||||
|  | ||||
|     params.each do |key, value| | ||||
|       scope.merge!(scope_for(key, value.to_s.strip)) if value.present? | ||||
| @@ -32,6 +34,10 @@ 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) | ||||
|     else | ||||
|       raise "Unknown filter: #{key}" | ||||
|     end | ||||
|   | ||||
							
								
								
									
										15
									
								
								app/policies/delivery_policy.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/policies/delivery_policy.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class DeliveryPolicy < ApplicationPolicy | ||||
|   def clear_delivery_errors? | ||||
|     admin? | ||||
|   end | ||||
|  | ||||
|   def restart_delivery? | ||||
|     admin? | ||||
|   end | ||||
|  | ||||
|   def stop_delivery? | ||||
|     admin? | ||||
|   end | ||||
| end | ||||
| @@ -0,0 +1,2 @@ | ||||
| %li.negative-hint | ||||
|   = l(exhausted_deliveries_days) | ||||
| @@ -22,4 +22,12 @@ | ||||
|           = 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) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true | ||||
|   | ||||
| @@ -16,6 +16,24 @@ | ||||
|       - unless whitelist_mode? | ||||
|         %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1' | ||||
|  | ||||
|   .filter-subset | ||||
|     %strong= t('admin.instances.delivery.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') | ||||
|  | ||||
| - unless whitelist_mode? | ||||
|   = form_tag admin_instances_url, method: 'GET', class: 'simple_form' do | ||||
|     .fields-group | ||||
|   | ||||
| @@ -1,6 +1,18 @@ | ||||
| - 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') | ||||
|  | ||||
| .dashboard__counters | ||||
|   %div | ||||
|     = link_to admin_accounts_path(remote: '1', by_domain: @instance.domain) do | ||||
| @@ -48,6 +60,13 @@ | ||||
|       = 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 | ||||
|  | ||||
| %hr.spacer/ | ||||
|  | ||||
| %div.action-buttons | ||||
| @@ -59,3 +78,9 @@ | ||||
|       = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button' | ||||
|     - 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' | ||||
|   | ||||
| @@ -230,6 +230,7 @@ en: | ||||
|         create_domain_block: Create Domain Block | ||||
|         create_email_domain_block: Create E-mail Domain Block | ||||
|         create_ip_block: Create IP rule | ||||
|         create_unavailable_domain: Create Unavailable Domain | ||||
|         demote_user: Demote User | ||||
|         destroy_announcement: Delete Announcement | ||||
|         destroy_custom_emoji: Delete Custom Emoji | ||||
| @@ -238,6 +239,7 @@ en: | ||||
|         destroy_email_domain_block: Delete e-mail domain block | ||||
|         destroy_ip_block: Delete IP rule | ||||
|         destroy_status: Delete Post | ||||
|         destroy_unavailable_domain: Delete Unavailable Domain | ||||
|         disable_2fa_user: Disable 2FA | ||||
|         disable_custom_emoji: Disable Custom Emoji | ||||
|         disable_user: Disable User | ||||
| @@ -271,6 +273,7 @@ en: | ||||
|         create_domain_block_html: "%{name} blocked domain %{target}" | ||||
|         create_email_domain_block_html: "%{name} blocked e-mail domain %{target}" | ||||
|         create_ip_block_html: "%{name} created rule for IP %{target}" | ||||
|         create_unavailable_domain_html: "%{name} stopped delivery to domain %{target}" | ||||
|         demote_user_html: "%{name} demoted user %{target}" | ||||
|         destroy_announcement_html: "%{name} deleted announcement %{target}" | ||||
|         destroy_custom_emoji_html: "%{name} destroyed emoji %{target}" | ||||
| @@ -279,6 +282,7 @@ en: | ||||
|         destroy_email_domain_block_html: "%{name} unblocked e-mail domain %{target}" | ||||
|         destroy_ip_block_html: "%{name} deleted rule for IP %{target}" | ||||
|         destroy_status_html: "%{name} removed post by %{target}" | ||||
|         destroy_unavailable_domain_html: "%{name} resumed delivery to domain %{target}" | ||||
|         disable_2fa_user_html: "%{name} disabled two factor requirement for user %{target}" | ||||
|         disable_custom_emoji_html: "%{name} disabled emoji %{target}" | ||||
|         disable_user_html: "%{name} disabled login for user %{target}" | ||||
| @@ -451,8 +455,25 @@ en: | ||||
|       title: Follow recommendations | ||||
|       unsuppress: Restore follow recommendation | ||||
|     instances: | ||||
|       back_to_all: All | ||||
|       back_to_limited: Limited | ||||
|       back_to_warning: Warning | ||||
|       by_domain: Domain | ||||
|       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. | ||||
|       empty: No domains found. | ||||
|       known_accounts: | ||||
|         one: "%{count} known account" | ||||
|   | ||||
| @@ -217,7 +217,14 @@ Rails.application.routes.draw do | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     resources :instances, only: [:index, :show], constraints: { id: /[^\/]+/ } | ||||
|     resources :instances, only: [:index, :show], constraints: { id: /[^\/]+/ } do | ||||
|       member do | ||||
|         post :clear_delivery_errors | ||||
|         post :restart_delivery | ||||
|         post :stop_delivery | ||||
|       end | ||||
|     end | ||||
|    | ||||
|     resources :rules | ||||
|  | ||||
|     resources :reports, only: [:index, :show] do | ||||
|   | ||||
		Reference in New Issue
	
	Block a user