Add public blocks to /about/blocks (#11298)

* Add automatic blocklist display in /about/blocks

Inspired by https://github.com/Gargron/mastodon.social-misc

* Add admin option to set who can see instance blocks

* Normalize locales files

* Rename “Sandbox” to “Silence” for consistency

* Disable /about/blocks when in whitelist mode

* Optionally display rationale for domain blocks

* Only display domain blocks that have user-facing limitations, and order them

* Redesign table of blocked domains to better handle long domain names and rationales

* Change domain blocks ordering now that rationales aren't displayed right away

* Only show explanation for block severities actually in use

* Reword instance block explanations and add disclaimer for public fetch mode
main
ThibG 2019-08-19 11:35:48 +02:00 committed by Eugen Rochko
parent 9e1d28f48e
commit 9b6a5ed109
10 changed files with 197 additions and 5 deletions

View File

@ -3,10 +3,12 @@
class AboutController < ApplicationController
layout 'public'
before_action :require_open_federation!, only: [:show, :more]
before_action :require_open_federation!, only: [:show, :more, :blocks]
before_action :check_blocklist_enabled, only: [:blocks]
before_action :authenticate_user!, only: [:blocks], if: :blocklist_account_required?
before_action :set_body_classes, only: :show
before_action :set_instance_presenter
before_action :set_expires_in
before_action :set_expires_in, only: [:show, :more, :terms]
skip_before_action :require_functional!, only: [:more, :terms]
@ -18,12 +20,40 @@ class AboutController < ApplicationController
def terms; end
def blocks
@show_rationale = Setting.show_domain_blocks_rationale == 'all'
@show_rationale |= Setting.show_domain_blocks_rationale == 'users' && !current_user.nil? && current_user.functional?
@blocks = DomainBlock.with_user_facing_limitations.order('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain').to_a
end
private
def require_open_federation!
not_found if whitelist_mode?
end
def check_blocklist_enabled
not_found if Setting.show_domain_blocks == 'disabled'
end
def blocklist_account_required?
Setting.show_domain_blocks == 'users'
end
def block_severity_text(block)
if block.severity == 'suspend'
I18n.t('domain_blocks.suspension')
else
limitations = []
limitations << I18n.t('domain_blocks.media_block') if block.reject_media?
limitations << I18n.t('domain_blocks.silence') if block.severity == 'silence'
limitations.join(', ')
end
end
helper_method :block_severity_text
helper_method :public_fetch_mode?
def new_user
User.new.tap do |user|
user.build_account

View File

@ -141,6 +141,15 @@ function main() {
return false;
});
delegate(document, '.blocks-table button.icon-button', 'click', function(e) {
e.preventDefault();
const classList = this.firstElementChild.classList;
classList.toggle('fa-chevron-down');
classList.toggle('fa-chevron-up');
this.parentElement.parentElement.nextElementSibling.classList.toggle('hidden');
});
delegate(document, '.modal-button', 'click', e => {
e.preventDefault();

View File

@ -241,3 +241,70 @@ a.table-action-link {
}
}
}
.blocks-table {
width: 100%;
max-width: 100%;
border-spacing: 0;
border-collapse: collapse;
table-layout: fixed;
border: 1px solid darken($ui-base-color, 8%);
thead {
border: 1px solid darken($ui-base-color, 8%);
background: darken($ui-base-color, 4%);
font-weight: 500;
th.severity-column {
width: 120px;
}
th.button-column {
width: 23px;
}
}
tbody > tr {
border: 1px solid darken($ui-base-color, 8%);
border-bottom: 0;
background: darken($ui-base-color, 4%);
&:hover {
background: darken($ui-base-color, 2%);
}
&.even {
background: $ui-base-color;
&:hover {
background: lighten($ui-base-color, 2%);
}
}
&.rationale {
background: lighten($ui-base-color, 4%);
border-top: 0;
&:hover {
background: lighten($ui-base-color, 6%);
}
&.hidden {
display: none;
}
}
td:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
}
th,
td {
padding: 8px;
line-height: 18px;
vertical-align: top;
text-align: left;
}
}

View File

@ -25,6 +25,7 @@ class DomainBlock < ApplicationRecord
delegate :count, to: :accounts, prefix: true
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) }
class << self
def suspend?(domain)

View File

@ -30,6 +30,8 @@ class Form::AdminSettings
mascot
spam_check_enabled
trends
show_domain_blocks
show_domain_blocks_rationale
).freeze
BOOLEAN_KEYS = %i(
@ -60,6 +62,8 @@ class Form::AdminSettings
validates :site_contact_email, :site_contact_username, presence: true
validates :site_contact_username, existing_username: true
validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }
validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }
def initialize(_attributes = {})
super

View File

@ -0,0 +1,48 @@
- content_for :page_title do
= t('domain_blocks.title', instance: site_hostname)
.grid
.column-0
.box-widget.rich-formatting
%h2= t('domain_blocks.blocked_domains')
%p= t('domain_blocks.description', instance: site_hostname)
.table-wrapper
%table.blocks-table
%thead
%tr
%th= t('domain_blocks.domain')
%th.severity-column= t('domain_blocks.severity')
- if @show_rationale
%th.button-column
%tbody
- if @blocks.empty?
%tr
%td{ colspan: @show_rationale ? 3 : 2 }= t('domain_blocks.no_domain_blocks')
- else
- @blocks.each_with_index do |block, i|
%tr{ class: i % 2 == 0 ? 'even': nil }
%td{ title: block.domain }= block.domain
%td= block_severity_text(block)
- if @show_rationale
%td
- if block.public_comment.present?
%button.icon-button{ title: t('domain_blocks.show_rationale'), 'aria-label' => t('domain_blocks.show_rationale') }
= fa_icon 'chevron-down fw', 'aria-hidden' => true
- if @show_rationale
- if block.public_comment.present?
%tr.rationale.hidden
%td{ colspan: 3 }= block.public_comment.presence
%h2= t('domain_blocks.severity_legend.title')
- if @blocks.any? { |block| block.reject_media? }
%h3= t('domain_blocks.media_block')
%p= t('domain_blocks.severity_legend.media_block')
- if @blocks.any? { |block| block.severity == 'silence' }
%h3= t('domain_blocks.silence')
%p= t('domain_blocks.severity_legend.silence')
- if @blocks.any? { |block| block.severity == 'suspend' }
%h3= t('domain_blocks.suspension')
%p= t('domain_blocks.severity_legend.suspension')
- if public_fetch_mode?
%p= t('domain_blocks.severity_legend.suspension_disclaimer')
.column-1
= render 'application/sidebar'

View File

@ -79,6 +79,12 @@
.fields-group
= f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
.fields-row
.fields-row__column.fields-row__column-6.fields-group
= f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
.fields-row__column.fields-row__column-6.fields-group
= f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
.fields-group
= f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
= f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?

View File

@ -423,6 +423,13 @@ en:
custom_css:
desc_html: Modify the look with CSS loaded on every page
title: Custom CSS
domain_blocks:
all: To everyone
disabled: To no one
title: Show domain blocks
users: To logged-in local users
domain_blocks_rationale:
title: Show rationale
hero:
desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail
title: Hero image
@ -630,6 +637,23 @@ en:
people:
one: "%{count} person"
other: "%{count} people"
domain_blocks:
blocked_domains: List of limited and blocked domains
description: This is the list of servers that %{instance} limits or reject federation with.
domain: Domain
media_block: Media block
no_domain_blocks: "(No domain blocks)"
severity: Severity
severity_legend:
media_block: Media files coming from the server are neither fetched, stored, or displayed to the user.
silence: Accounts from silenced servers can be found, followed and interacted with, but their toots will not appear in the public timelines, and notifications from them will not reach local users who are not following them.
suspension: No content from suspended servers is stored or displayed, nor is any content sent to them. Interactions from suspended servers are ignored.
suspension_disclaimer: Suspended servers may occasionally retrieve public content from this server.
title: Severities
show_rationale: Show rationale
silence: Silence
suspension: Suspension
title: "%{instance} List of blocked instances"
domain_validator:
invalid_domain: is not a valid domain name
errors:

View File

@ -425,6 +425,7 @@ Rails.application.routes.draw do
get '/about', to: 'about#show'
get '/about/more', to: 'about#more'
get '/about/blocks', to: 'about#blocks'
get '/terms', to: 'about#terms'
root 'home#index'

View File

@ -64,6 +64,8 @@ defaults: &defaults
peers_api_enabled: true
show_known_fediverse_at_about_page: true
spam_check_enabled: true
show_domain_blocks: 'disabled'
show_domain_blocks_rationale: 'disabled'
development:
<<: *defaults