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:
parent
d9ae3db8d5
commit
7cb34b32f8
13 changed files with 180 additions and 5 deletions
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue