about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTakeshi Umeda <noel.yoshiba@gmail.com>2021-05-06 06:39:02 +0900
committerGitHub <noreply@github.com>2021-05-05 23:39:02 +0200
commit7cb34b32f8bc925b56c79dbcf053671f93f2eb42 (patch)
treeb05d123350c4af203a6009418b1bfc7056820b27
parentd9ae3db8d5543cf0b7fa44186c191c9bb2472d23 (diff)
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)
-rw-r--r--app/controllers/admin/instances_controller.rb44
-rw-r--r--app/helpers/admin/action_logs_helper.rb4
-rw-r--r--app/lib/delivery_failure_tracker.rb26
-rw-r--r--app/models/admin/action_log_filter.rb2
-rw-r--r--app/models/instance.rb3
-rw-r--r--app/models/instance_filter.rb8
-rw-r--r--app/policies/delivery_policy.rb15
-rw-r--r--app/views/admin/instances/_exhausted_deliveries_days.haml2
-rw-r--r--app/views/admin/instances/_instance.html.haml8
-rw-r--r--app/views/admin/instances/index.html.haml18
-rw-r--r--app/views/admin/instances/show.html.haml25
-rw-r--r--config/locales/en.yml21
-rw-r--r--config/routes.rb9
13 files changed, 180 insertions, 5 deletions
diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb
index b5918d231..748c5de5a 100644
--- a/app/controllers/admin/instances_controller.rb
+++ b/app/controllers/admin/instances_controller.rb
@@ -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
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index 0f3ca36e2..e9a298a24 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -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'))
diff --git a/app/lib/delivery_failure_tracker.rb b/app/lib/delivery_failure_tracker.rb
index 2cd6ef7ad..8907ade4c 100644
--- a/app/lib/delivery_failure_tracker.rb
+++ b/app/lib/delivery_failure_tracker.rb
@@ -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
diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb
index 3a1b67e06..a1c156a8b 100644
--- a/app/models/admin/action_log_filter.rb
+++ b/app/models/admin/action_log_filter.rb
@@ -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,
diff --git a/app/models/instance.rb b/app/models/instance.rb
index 29be03662..8949be054 100644
--- a/app/models/instance.rb
+++ b/app/models/instance.rb
@@ -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}%")) }
 
diff --git a/app/models/instance_filter.rb b/app/models/instance_filter.rb
index 0598d8fea..9e533c4aa 100644
--- a/app/models/instance_filter.rb
+++ b/app/models/instance_filter.rb
@@ -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
diff --git a/app/policies/delivery_policy.rb b/app/policies/delivery_policy.rb
new file mode 100644
index 000000000..24d06c168
--- /dev/null
+++ b/app/policies/delivery_policy.rb
@@ -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
diff --git a/app/views/admin/instances/_exhausted_deliveries_days.haml b/app/views/admin/instances/_exhausted_deliveries_days.haml
new file mode 100644
index 000000000..e581f542d
--- /dev/null
+++ b/app/views/admin/instances/_exhausted_deliveries_days.haml
@@ -0,0 +1,2 @@
+%li.negative-hint
+  = l(exhausted_deliveries_days)
diff --git a/app/views/admin/instances/_instance.html.haml b/app/views/admin/instances/_instance.html.haml
index 188d0d984..990cf9ec8 100644
--- a/app/views/admin/instances/_instance.html.haml
+++ b/app/views/admin/instances/_instance.html.haml
@@ -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
diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml
index 7c7958786..797948d94 100644
--- a/app/views/admin/instances/index.html.haml
+++ b/app/views/admin/instances/index.html.haml
@@ -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
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index 0b9382771..462529338 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -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'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 1b41ee063..bfa489817 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -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"
diff --git a/config/routes.rb b/config/routes.rb
index 4661a7c11..8ca7fccdd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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