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)pull/1526/head
parent
d9ae3db8d5
commit
7cb34b32f8
|
@ -3,7 +3,8 @@
|
||||||
module Admin
|
module Admin
|
||||||
class InstancesController < BaseController
|
class InstancesController < BaseController
|
||||||
before_action :set_instances, only: :index
|
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
|
def index
|
||||||
authorize :instance, :index?
|
authorize :instance, :index?
|
||||||
|
@ -13,14 +14,55 @@ module Admin
|
||||||
authorize :instance, :show?
|
authorize :instance, :show?
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def set_instance
|
def set_instance
|
||||||
@instance = Instance.find(params[:id])
|
@instance = Instance.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_exhausted_deliveries_days
|
||||||
|
@exhausted_deliveries_days = @instance.delivery_failure_tracker.exhausted_deliveries_days
|
||||||
|
end
|
||||||
|
|
||||||
def set_instances
|
def set_instances
|
||||||
@instances = filtered_instances.page(params[:page])
|
@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
|
end
|
||||||
|
|
||||||
def filtered_instances
|
def filtered_instances
|
||||||
|
|
|
@ -21,7 +21,7 @@ module Admin::ActionLogsHelper
|
||||||
record.shortcode
|
record.shortcode
|
||||||
when 'Report'
|
when 'Report'
|
||||||
link_to "##{record.id}", admin_report_path(record)
|
link_to "##{record.id}", admin_report_path(record)
|
||||||
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock'
|
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
|
||||||
link_to record.domain, "https://#{record.domain}"
|
link_to record.domain, "https://#{record.domain}"
|
||||||
when 'Status'
|
when 'Status'
|
||||||
link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
|
link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
|
||||||
|
@ -38,7 +38,7 @@ module Admin::ActionLogsHelper
|
||||||
case type
|
case type
|
||||||
when 'CustomEmoji'
|
when 'CustomEmoji'
|
||||||
attributes['shortcode']
|
attributes['shortcode']
|
||||||
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock'
|
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
|
||||||
link_to attributes['domain'], "https://#{attributes['domain']}"
|
link_to attributes['domain'], "https://#{attributes['domain']}"
|
||||||
when 'Status'
|
when 'Status'
|
||||||
tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
|
tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
|
||||||
|
|
|
@ -17,6 +17,10 @@ class DeliveryFailureTracker
|
||||||
UnavailableDomain.find_by(domain: @host)&.destroy
|
UnavailableDomain.find_by(domain: @host)&.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clear_failures!
|
||||||
|
Redis.current.del(exhausted_deliveries_key)
|
||||||
|
end
|
||||||
|
|
||||||
def days
|
def days
|
||||||
Redis.current.scard(exhausted_deliveries_key) || 0
|
Redis.current.scard(exhausted_deliveries_key) || 0
|
||||||
end
|
end
|
||||||
|
@ -25,6 +29,10 @@ class DeliveryFailureTracker
|
||||||
!UnavailableDomain.where(domain: @host).exists?
|
!UnavailableDomain.where(domain: @host).exists?
|
||||||
end
|
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!
|
alias reset! track_success!
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
@ -44,6 +52,24 @@ class DeliveryFailureTracker
|
||||||
def reset!(url)
|
def reset!(url)
|
||||||
new(url).reset!
|
new(url).reset!
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -17,12 +17,14 @@ class Admin::ActionLogFilter
|
||||||
create_domain_allow: { target_type: 'DomainAllow', action: 'create' }.freeze,
|
create_domain_allow: { target_type: 'DomainAllow', action: 'create' }.freeze,
|
||||||
create_domain_block: { target_type: 'DomainBlock', action: 'create' }.freeze,
|
create_domain_block: { target_type: 'DomainBlock', action: 'create' }.freeze,
|
||||||
create_email_domain_block: { target_type: 'EmailDomainBlock', 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,
|
demote_user: { target_type: 'User', action: 'demote' }.freeze,
|
||||||
destroy_announcement: { target_type: 'Announcement', action: 'destroy' }.freeze,
|
destroy_announcement: { target_type: 'Announcement', action: 'destroy' }.freeze,
|
||||||
destroy_custom_emoji: { target_type: 'CustomEmoji', action: 'destroy' }.freeze,
|
destroy_custom_emoji: { target_type: 'CustomEmoji', action: 'destroy' }.freeze,
|
||||||
destroy_domain_allow: { target_type: 'DomainAllow', action: 'destroy' }.freeze,
|
destroy_domain_allow: { target_type: 'DomainAllow', action: 'destroy' }.freeze,
|
||||||
destroy_domain_block: { target_type: 'DomainBlock', action: 'destroy' }.freeze,
|
destroy_domain_block: { target_type: 'DomainBlock', action: 'destroy' }.freeze,
|
||||||
destroy_email_domain_block: { target_type: 'EmailDomainBlock', 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,
|
destroy_status: { target_type: 'Status', action: 'destroy' }.freeze,
|
||||||
disable_2fa_user: { target_type: 'User', action: 'disable' }.freeze,
|
disable_2fa_user: { target_type: 'User', action: 'disable' }.freeze,
|
||||||
disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
|
disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
|
||||||
|
|
|
@ -10,10 +10,13 @@
|
||||||
class Instance < ApplicationRecord
|
class Instance < ApplicationRecord
|
||||||
self.primary_key = :domain
|
self.primary_key = :domain
|
||||||
|
|
||||||
|
attr_accessor :failure_days
|
||||||
|
|
||||||
has_many :accounts, foreign_key: :domain, primary_key: :domain
|
has_many :accounts, foreign_key: :domain, primary_key: :domain
|
||||||
|
|
||||||
belongs_to :domain_block, 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 :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}%")) }
|
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ class InstanceFilter
|
||||||
KEYS = %i(
|
KEYS = %i(
|
||||||
limited
|
limited
|
||||||
by_domain
|
by_domain
|
||||||
|
warning
|
||||||
|
unavailable
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
attr_reader :params
|
attr_reader :params
|
||||||
|
@ -13,7 +15,7 @@ class InstanceFilter
|
||||||
end
|
end
|
||||||
|
|
||||||
def results
|
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|
|
params.each do |key, value|
|
||||||
scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
|
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'))
|
Instance.joins(:domain_allow).reorder(Arel.sql('domain_allows.id desc'))
|
||||||
when 'by_domain'
|
when 'by_domain'
|
||||||
Instance.matches_domain(value)
|
Instance.matches_domain(value)
|
||||||
|
when 'warning'
|
||||||
|
Instance.where(domain: DeliveryFailureTracker.warning_domains)
|
||||||
|
when 'unavailable'
|
||||||
|
Instance.joins(:unavailable_domain)
|
||||||
else
|
else
|
||||||
raise "Unknown filter: #{key}"
|
raise "Unknown filter: #{key}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -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')
|
= t('admin.accounts.whitelisted')
|
||||||
- else
|
- else
|
||||||
= t('admin.accounts.no_limits_imposed')
|
= 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
|
.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?
|
- unless whitelist_mode?
|
||||||
%li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
|
%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?
|
- unless whitelist_mode?
|
||||||
= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
|
= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
|
||||||
.fields-group
|
.fields-group
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= @instance.domain
|
= @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
|
.dashboard__counters
|
||||||
%div
|
%div
|
||||||
= link_to admin_accounts_path(remote: '1', by_domain: @instance.domain) do
|
= link_to admin_accounts_path(remote: '1', by_domain: @instance.domain) do
|
||||||
|
@ -48,6 +60,13 @@
|
||||||
= simple_format(h(@instance.public_comment))
|
= simple_format(h(@instance.public_comment))
|
||||||
.speech-bubble__owner= t 'admin.instances.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/
|
%hr.spacer/
|
||||||
|
|
||||||
%div.action-buttons
|
%div.action-buttons
|
||||||
|
@ -59,3 +78,9 @@
|
||||||
= link_to t('admin.domain_blocks.undo'), 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'
|
||||||
- else
|
- else
|
||||||
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
|
= 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_domain_block: Create Domain Block
|
||||||
create_email_domain_block: Create E-mail Domain Block
|
create_email_domain_block: Create E-mail Domain Block
|
||||||
create_ip_block: Create IP rule
|
create_ip_block: Create IP rule
|
||||||
|
create_unavailable_domain: Create Unavailable Domain
|
||||||
demote_user: Demote User
|
demote_user: Demote User
|
||||||
destroy_announcement: Delete Announcement
|
destroy_announcement: Delete Announcement
|
||||||
destroy_custom_emoji: Delete Custom Emoji
|
destroy_custom_emoji: Delete Custom Emoji
|
||||||
|
@ -238,6 +239,7 @@ en:
|
||||||
destroy_email_domain_block: Delete e-mail domain block
|
destroy_email_domain_block: Delete e-mail domain block
|
||||||
destroy_ip_block: Delete IP rule
|
destroy_ip_block: Delete IP rule
|
||||||
destroy_status: Delete Post
|
destroy_status: Delete Post
|
||||||
|
destroy_unavailable_domain: Delete Unavailable Domain
|
||||||
disable_2fa_user: Disable 2FA
|
disable_2fa_user: Disable 2FA
|
||||||
disable_custom_emoji: Disable Custom Emoji
|
disable_custom_emoji: Disable Custom Emoji
|
||||||
disable_user: Disable User
|
disable_user: Disable User
|
||||||
|
@ -271,6 +273,7 @@ en:
|
||||||
create_domain_block_html: "%{name} blocked domain %{target}"
|
create_domain_block_html: "%{name} blocked domain %{target}"
|
||||||
create_email_domain_block_html: "%{name} blocked e-mail domain %{target}"
|
create_email_domain_block_html: "%{name} blocked e-mail domain %{target}"
|
||||||
create_ip_block_html: "%{name} created rule for IP %{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}"
|
demote_user_html: "%{name} demoted user %{target}"
|
||||||
destroy_announcement_html: "%{name} deleted announcement %{target}"
|
destroy_announcement_html: "%{name} deleted announcement %{target}"
|
||||||
destroy_custom_emoji_html: "%{name} destroyed emoji %{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_email_domain_block_html: "%{name} unblocked e-mail domain %{target}"
|
||||||
destroy_ip_block_html: "%{name} deleted rule for IP %{target}"
|
destroy_ip_block_html: "%{name} deleted rule for IP %{target}"
|
||||||
destroy_status_html: "%{name} removed post by %{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_2fa_user_html: "%{name} disabled two factor requirement for user %{target}"
|
||||||
disable_custom_emoji_html: "%{name} disabled emoji %{target}"
|
disable_custom_emoji_html: "%{name} disabled emoji %{target}"
|
||||||
disable_user_html: "%{name} disabled login for user %{target}"
|
disable_user_html: "%{name} disabled login for user %{target}"
|
||||||
|
@ -451,8 +455,25 @@ en:
|
||||||
title: Follow recommendations
|
title: Follow recommendations
|
||||||
unsuppress: Restore follow recommendation
|
unsuppress: Restore follow recommendation
|
||||||
instances:
|
instances:
|
||||||
|
back_to_all: All
|
||||||
|
back_to_limited: Limited
|
||||||
|
back_to_warning: Warning
|
||||||
by_domain: Domain
|
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_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.
|
empty: No domains found.
|
||||||
known_accounts:
|
known_accounts:
|
||||||
one: "%{count} known account"
|
one: "%{count} known account"
|
||||||
|
|
|
@ -217,7 +217,14 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
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 :rules
|
||||||
|
|
||||||
resources :reports, only: [:index, :show] do
|
resources :reports, only: [:index, :show] do
|
||||||
|
|
Loading…
Reference in New Issue