Add moderation warnings (#9519)

* Add moderation warnings

Replace individual routes for disabling, silencing, and suspending
a user, as well as the report update route, with a unified account
action controller that allows you to select an action (none,
disable, silence, suspend) as well as whether it should generate an
e-mail notification with optional custom text. That notification,
with the optional custom text, is saved as a warning.

Additionally, there are warning presets you can configure to save
time when performing the above.

* Use Account#local_username_and_domain
master
Eugen Rochko 5 years ago committed by GitHub
parent 00862dcaff
commit 3c033c4352
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 36
      app/controllers/admin/account_actions_controller.rb
  2. 1
      app/controllers/admin/account_moderation_notes_controller.rb
  3. 23
      app/controllers/admin/accounts_controller.rb
  4. 79
      app/controllers/admin/reports_controller.rb
  5. 27
      app/controllers/admin/silences_controller.rb
  6. 60
      app/controllers/admin/suspensions_controller.rb
  7. 58
      app/controllers/admin/warning_presets_controller.rb
  8. 7
      app/helpers/admin/action_logs_helper.rb
  9. 4
      app/javascript/images/icon_flag.svg
  10. BIN
      app/javascript/images/mailer/icon_warning.png
  11. 4
      app/javascript/styles/mailer.scss
  12. 4
      app/javascript/styles/mastodon/admin.scss
  13. 12
      app/mailers/user_mailer.rb
  14. 8
      app/models/account.rb
  15. 23
      app/models/account_warning.rb
  16. 15
      app/models/account_warning_preset.rb
  17. 134
      app/models/admin/account_action.rb
  18. 2
      app/models/concerns/account_associations.rb
  19. 7
      app/models/form/admin_suspension_confirmation.rb
  20. 4
      app/policies/account_policy.rb
  21. 19
      app/policies/account_warning_preset_policy.rb
  22. 26
      app/views/admin/account_actions/new.html.haml
  23. 6
      app/views/admin/account_warnings/_account_warning.html.haml
  24. 14
      app/views/admin/accounts/show.html.haml
  25. 17
      app/views/admin/reports/show.html.haml
  26. 25
      app/views/admin/suspensions/new.html.haml
  27. 11
      app/views/admin/warning_presets/edit.html.haml
  28. 30
      app/views/admin/warning_presets/index.html.haml
  29. 63
      app/views/user_mailer/warning.html.haml
  30. 9
      app/views/user_mailer/warning.text.erb
  31. 2
      app/views/user_mailer/welcome.text.erb
  32. 5
      config/locales/ar.yml
  33. 2
      config/locales/ast.yml
  34. 6
      config/locales/ca.yml
  35. 6
      config/locales/co.yml
  36. 6
      config/locales/cs.yml
  37. 6
      config/locales/cy.yml
  38. 6
      config/locales/da.yml
  39. 6
      config/locales/de.yml
  40. 6
      config/locales/el.yml
  41. 32
      config/locales/en.yml
  42. 5
      config/locales/eo.yml
  43. 6
      config/locales/es.yml
  44. 6
      config/locales/eu.yml
  45. 6
      config/locales/fa.yml
  46. 6
      config/locales/fr.yml
  47. 6
      config/locales/gl.yml
  48. 6
      config/locales/it.yml
  49. 6
      config/locales/ja.yml
  50. 6
      config/locales/ko.yml
  51. 6
      config/locales/nl.yml
  52. 6
      config/locales/oc.yml
  53. 6
      config/locales/pl.yml
  54. 6
      config/locales/pt-BR.yml
  55. 6
      config/locales/ru.yml
  56. 19
      config/locales/simple_form.en.yml
  57. 6
      config/locales/sk.yml
  58. 6
      config/locales/sr.yml
  59. 16
      config/routes.rb
  60. 12
      db/migrate/20181213184704_create_account_warnings.rb
  61. 9
      db/migrate/20181213185533_create_account_warning_presets.rb
  62. 21
      db/schema.rb
  63. 52
      spec/controllers/admin/accounts_controller_spec.rb
  64. 84
      spec/controllers/admin/reports_controller_spec.rb
  65. 33
      spec/controllers/admin/silences_controller_spec.rb
  66. 39
      spec/controllers/admin/suspensions_controller_spec.rb
  67. 5
      spec/fabricators/account_warning_fabricator.rb
  68. 3
      spec/fabricators/account_warning_preset_fabricator.rb
  69. 5
      spec/mailers/previews/user_mailer_preview.rb
  70. 5
      spec/models/account_warning_preset_spec.rb
  71. 5
      spec/models/account_warning_spec.rb
  72. 4
      spec/models/admin/account_action_spec.rb

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Admin
class AccountActionsController < BaseController
before_action :set_account
def new
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true)
@warning_presets = AccountWarningPreset.all
end
def create
account_action = Admin::AccountAction.new(resource_params)
account_action.target_account = @account
account_action.current_account = current_account
account_action.save!
if account_action.with_report?
redirect_to admin_report_path(account_action.report)
else
redirect_to admin_account_path(@account.id)
end
end
private
def set_account
@account = Account.find(params[:account_id])
end
def resource_params
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification)
end
end
end

@ -14,6 +14,7 @@ module Admin
else
@account = @account_moderation_note.target_account
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
render template: 'admin/accounts/show'
end

@ -2,9 +2,9 @@
module Admin
class AccountsController < BaseController
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :disable, :memorialize]
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :memorialize]
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
before_action :require_local_account!, only: [:enable, :disable, :memorialize]
before_action :require_local_account!, only: [:enable, :memorialize]
def index
authorize :account, :index?
@ -13,8 +13,10 @@ module Admin
def show
authorize @account, :show?
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
end
def subscribe
@ -43,10 +45,17 @@ module Admin
redirect_to admin_account_path(@account.id)
end
def disable
authorize @account.user, :disable?
@account.user.disable!
log_action :disable, @account.user
def unsilence
authorize @account, :unsilence?
@account.unsilence!
log_action :unsilence, @account
redirect_to admin_account_path(@account.id)
end
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
log_action :unsuspend, @account
redirect_to admin_account_path(@account.id)
end

@ -13,75 +13,42 @@ module Admin
authorize @report, :show?
@report_note = @report.notes.new
@report_notes = (@report.notes.latest + @report.history).sort_by(&:created_at)
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
@form = Form::StatusBatch.new
end
def update
def assign_to_self
authorize @report, :update?
process_report
if @report.action_taken?
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
else
redirect_to admin_report_path(@report)
end
@report.update!(assigned_account_id: current_account.id)
log_action :assigned_to_self, @report
redirect_to admin_report_path(@report)
end
private
def process_report
case params[:outcome].to_s
when 'assign_to_self'
@report.update!(assigned_account_id: current_account.id)
log_action :assigned_to_self, @report
when 'unassign'
@report.update!(assigned_account_id: nil)
log_action :unassigned, @report
when 'reopen'
@report.unresolve!
log_action :reopen, @report
when 'resolve'
@report.resolve!(current_account)
log_action :resolve, @report
when 'disable'
@report.resolve!(current_account)
@report.target_account.user.disable!
log_action :resolve, @report
log_action :disable, @report.target_account.user
resolve_all_target_account_reports
when 'silence'
@report.resolve!(current_account)
@report.target_account.update!(silenced: true)
log_action :resolve, @report
log_action :silence, @report.target_account
resolve_all_target_account_reports
else
raise ActiveRecord::RecordNotFound
end
@report.reload
def unassign
authorize @report, :update?
@report.update!(assigned_account_id: nil)
log_action :unassigned, @report
redirect_to admin_report_path(@report)
end
def resolve_all_target_account_reports
unresolved_reports_for_target_account.update_all(action_taken: true, action_taken_by_account_id: current_account.id)
def reopen
authorize @report, :update?
@report.unresolve!
log_action :reopen, @report
redirect_to admin_report_path(@report)
end
def unresolved_reports_for_target_account
Report.where(
target_account: @report.target_account
).unresolved
def resolve
authorize @report, :update?
@report.resolve!(current_account)
log_action :resolve, @report
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
end
private
def filtered_reports
ReportFilter.new(filter_params).results.order(id: :desc).includes(
:account,
:target_account
)
ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account)
end
def filter_params

@ -1,27 +0,0 @@
# frozen_string_literal: true
module Admin
class SilencesController < BaseController
before_action :set_account
def create
authorize @account, :silence?
@account.update!(silenced: true)
log_action :silence, @account
redirect_to admin_accounts_path
end
def destroy
authorize @account, :unsilence?
@account.update!(silenced: false)
log_action :unsilence, @account
redirect_to admin_accounts_path
end
private
def set_account
@account = Account.find(params[:account_id])
end
end
end

@ -1,60 +0,0 @@
# frozen_string_literal: true
module Admin
class SuspensionsController < BaseController
before_action :set_account
def new
@suspension = Form::AdminSuspensionConfirmation.new(report_id: params[:report_id])
end
def create
authorize @account, :suspend?
@suspension = Form::AdminSuspensionConfirmation.new(suspension_params)
if suspension_params[:acct] == @account.acct
resolve_report! if suspension_params[:report_id].present?
perform_suspend!
mark_reports_resolved!
redirect_to admin_accounts_path
else
flash.now[:alert] = I18n.t('admin.suspensions.bad_acct_msg')
render :new
end
end
def destroy
authorize @account, :unsuspend?
@account.unsuspend!
log_action :unsuspend, @account
redirect_to admin_accounts_path
end
private
def set_account
@account = Account.find(params[:account_id])
end
def suspension_params
params.require(:form_admin_suspension_confirmation).permit(:acct, :report_id)
end
def resolve_report!
report = Report.find(suspension_params[:report_id])
report.resolve!(current_account)
log_action :resolve, report
end
def perform_suspend!
@account.suspend!
Admin::SuspensionWorker.perform_async(@account.id)
log_action :suspend, @account
end
def mark_reports_resolved!
Report.where(target_account: @account).unresolved.update_all(action_taken: true, action_taken_by_account_id: current_account.id)
end
end
end

@ -0,0 +1,58 @@
# frozen_string_literal: true
module Admin
class WarningPresetsController < BaseController
before_action :set_warning_preset, except: [:index, :create]
def index
authorize :account_warning_preset, :index?
@warning_presets = AccountWarningPreset.all
@warning_preset = AccountWarningPreset.new
end
def create
authorize :account_warning_preset, :create?
@warning_preset = AccountWarningPreset.new(warning_preset_params)
if @warning_preset.save
redirect_to admin_warning_presets_path
else
@warning_presets = AccountWarningPreset.all
render :index
end
end
def edit
authorize @warning_preset, :update?
end
def update
authorize @warning_preset, :update?
if @warning_preset.update(warning_preset_params)
redirect_to admin_warning_presets_path
else
render :edit
end
end
def destroy
authorize @warning_preset, :destroy?
@warning_preset.destroy!
redirect_to admin_warning_presets_path
end
private
def set_warning_preset
@warning_preset = AccountWarningPreset.find(params[:id])
end
def warning_preset_params
params.require(:account_warning_preset).permit(:text)
end
end
end

@ -23,6 +23,8 @@ module Admin::ActionLogsHelper
link_to record.domain, "https://#{record.domain}"
when 'Status'
link_to record.account.acct, TagManager.instance.url_for(record)
when 'AccountWarning'
link_to record.target_account.acct, admin_account_path(record.target_account_id)
end
end
@ -34,6 +36,7 @@ module Admin::ActionLogsHelper
link_to attributes['domain'], "https://#{attributes['domain']}"
when 'Status'
tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
if tmp_status.account
link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id)
else
@ -81,6 +84,8 @@ module Admin::ActionLogsHelper
'envelope'
when 'Status'
'pencil'
when 'AccountWarning'
'warning'
end
end
@ -104,6 +109,6 @@ module Admin::ActionLogsHelper
private
def opposite_verbs?(log)
%w(DomainBlock EmailDomainBlock).include?(log.target_type)
%w(DomainBlock EmailDomainBlock AccountWarning).include?(log.target_type)
end
end

@ -0,0 +1,4 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"/>
</svg>

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

@ -426,6 +426,10 @@ h5 {
background: $success-green;
}
&.alert-icon td {
background: $error-red;
}
img {
max-width: 32px;
width: 32px;

@ -542,6 +542,10 @@ a.name-tag,
border-left-color: lighten($error-red, 12%);
}
&.warning {
border-left-color: $gold-star;
}
&__bubble {
padding: 16px;
padding-left: 14px;

@ -78,4 +78,16 @@ class UserMailer < Devise::Mailer
mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject')
end
end
def warning(user, warning)
@resource = user
@warning = warning
@instance = Rails.configuration.x.local_domain
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email,
subject: I18n.t("user_mailer.warning.subject.#{@warning.action}", acct: "@#{user.account.local_username_and_domain}"),
reply_to: Setting.site_contact_email
end
end
end

@ -155,6 +155,14 @@ class Account < ApplicationRecord
ResolveAccountService.new.call(acct)
end
def silence!
update!(silenced: true)
end
def unsilence!
update!(silenced: false)
end
def suspend!
transaction do
user&.disable! if local?

@ -0,0 +1,23 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: account_warnings
#
# id :bigint(8) not null, primary key
# account_id :bigint(8)
# target_account_id :bigint(8)
# action :integer default("none"), not null
# text :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class AccountWarning < ApplicationRecord
enum action: %i(none disable silence suspend), _suffix: :action
belongs_to :account, inverse_of: :account_warnings
belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings
scope :latest, -> { order(created_at: :desc) }
scope :custom, -> { where.not(text: '') }
end

@ -0,0 +1,15 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: account_warning_presets
#
# id :bigint(8) not null, primary key
# text :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class AccountWarningPreset < ApplicationRecord
validates :text, presence: true
end

@ -0,0 +1,134 @@
# frozen_string_literal: true
class Admin::AccountAction
include ActiveModel::Model
include AccountableConcern
include Authorization
TYPES = %w(
none
disable
silence
suspend
).freeze
attr_accessor :target_account,
:current_account,
:type,
:text,
:report_id,
:warning_preset_id,
:send_email_notification
attr_reader :warning
def save!
ApplicationRecord.transaction do
process_action!
process_warning!
end
queue_email!
process_reports!
end
def report
@report ||= Report.find(report_id) if report_id.present?
end
def with_report?
!report.nil?
end
class << self
def types_for_account(account)
if account.local?
TYPES
else
TYPES - %w(none disable)
end
end
end
private
def process_action!
case type
when 'disable'
handle_disable!
when 'silence'
handle_silence!
when 'suspend'
handle_suspend!
end
end
def process_warning!
return unless warnable?
authorize(target_account, :warn?)
@warning = AccountWarning.create!(target_account: target_account,
account: current_account,
action: type,
text: text_for_warning)
# A log entry is only interesting if the warning contains
# custom text from someone. Otherwise it's just noise.
log_action(:create, warning) if warning.text.present?
end
def process_reports!
return if report_id.blank?
authorize(report, :update?)
if type == 'none'
log_action(:resolve, report)
report.resolve!(current_account)
else
Report.where(target_account: target_account).unresolved.update_all(action_taken: true, action_taken_by_account_id: current_account.id)
end
end
def handle_disable!
authorize(target_account.user, :disable?)
log_action(:disable, target_account.user)
target_account.user&.disable!
end
def handle_silence!
authorize(target_account, :silence?)
log_action(:silence, target_account)
target_account.silence!
end
def handle_suspend!
authorize(target_account, :suspend?)
log_action(:suspend, target_account)
target_account.suspend!
queue_suspension_worker!
end
def text_for_warning
[warning_preset&.text, text].compact.join("\n\n")
end
def queue_suspension_worker!
Admin::SuspensionWorker.perform_async(target_account.id)
end
def queue_email!
return unless warnable?
UserMailer.warning(target_account.user, warning).deliver_later!
end
def warnable?
send_email_notification && target_account.local?
end
def warning_preset
@warning_preset ||= AccountWarningPreset.find(warning_preset_id) if warning_preset_id.present?
end
end

@ -39,6 +39,8 @@ module AccountAssociations
# Moderation notes
has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
has_many :account_warnings, dependent: :destroy, inverse_of: :account
has_many :targeted_account_warnings, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
# Lists (that the account is on, not owned by the account)
has_many :list_accounts, inverse_of: :account, dependent: :destroy

@ -1,7 +0,0 @@
# frozen_string_literal: true
class Form::AdminSuspensionConfirmation
include ActiveModel::Model
attr_accessor :acct, :report_id
end

@ -9,6 +9,10 @@ class AccountPolicy < ApplicationPolicy
staff?
end
def warn?
staff? && !record.user&.staff?
end
def suspend?
staff? && !record.user&.staff?
end

@ -0,0 +1,19 @@
# frozen_string_literal: true
class AccountWarningPresetPolicy < ApplicationPolicy
def index?
staff?
end
def create?
staff?
end
def update?
staff?
end
def destroy?
staff?
end
end

@ -0,0 +1,26 @@
- content_for :page_title do
= t('admin.account_actions.title', acct: @account.acct)
= simple_form_for @account_action, url: admin_account_action_path(@account.id) do |f|
= f.input :report_id, as: :hidden
.fields-group
= f.input :type, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { I18n.t("simple_form.labels.admin_account_action.types.#{type}")}, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.acct)
- if @account.local?
%hr.spacer/
.fields-group
= f.input :send_email_notification, as: :boolean, wrapper: :with_label
%hr.spacer/
- unless @warning_presets.empty?
.fields-group
= f.input :warning_preset_id, collection: @warning_presets, label_method: :text, wrapper: :with_block_label
.fields-group
= f.input :text, as: :text, wrapper: :with_block_label, hint: t('simple_form.hints.admin_account_action.text_html', path: admin_warning_presets_path)
.actions
= f.button :button, t('admin.account_actions.action'), type: :submit

@ -0,0 +1,6 @@
.speech-bubble.warning
.speech-bubble__bubble
= Formatter.instance.linkify(account_warning.text)
.speech-bubble__owner
= admin_account_link_to account_warning.account
%time.formatted{ datetime: account_warning.created_at.iso8601 }= l account_warning.created_at

@ -64,7 +64,7 @@
= table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user)
- else
= t('admin.accounts.enabled')
= table_link_to 'lock', t('admin.accounts.disable'), disable_admin_account_path(@account.id), method: :post if can?(:disable, @account.user)
= table_link_to 'lock', t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable') if can?(:disable, @account.user)
%tr
%th= t('admin.accounts.most_recent_ip')
%td= @account.user_current_sign_in_ip
@ -119,18 +119,18 @@
%div{ style: 'float: left' }
- if @account.silenced?
= link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button' if can?(:unsilence, @account)
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
- else
= link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button button--destructive' if can?(:silence, @account)
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button button--destructive' if can?(:silence, @account)
- if @account.local?
- unless @account.user_confirmed?
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
- if @account.suspended?
= link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button' if can?(:unsuspend, @account)
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
- else
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_suspension_path(@account.id), class: 'button button--destructive' if can?(:suspend, @account)
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
- if !@account.local? && @account.hub_url.present?
%hr.spacer/
@ -184,6 +184,10 @@
%hr.spacer/
= render @warnings
%hr.spacer/
= render @moderation_notes
= simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f|

@ -8,13 +8,14 @@
- if @report.unresolved?
%div{ style: 'float: right' }
- if @report.target_account.local?
= link_to t('admin.accounts.disable'), admin_report_path(@report, outcome: 'disable'), method: :put, class: 'button button--destructive'
= link_to t('admin.accounts.silence'), admin_report_path(@report, outcome: 'silence'), method: :put, class: 'button button--destructive'
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_suspension_path(@report.target_account_id, report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button'
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@report.target_account_id, type: 'silence', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@report.target_account_id, type: 'suspend', report_id: @report.id), class: 'button button--destructive'
%div{ style: 'float: left' }
= link_to t('admin.reports.mark_as_resolved'), admin_report_path(@report, outcome: 'resolve'), method: :put, class: 'button'
= link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button'
- else
= link_to t('admin.reports.mark_as_unresolved'), admin_report_path(@report, outcome: 'reopen'), method: :put, class: 'button'
= link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
%hr.spacer
@ -67,10 +68,10 @@
= admin_account_link_to @report.assigned_account
%td
- if @report.assigned_account != current_user.account
= table_link_to 'user', t('admin.reports.assign_to_self'), admin_report_path(@report, outcome: 'assign_to_self'), method: :put
= table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(@report), method: :post
%td
- if !@report.assigned_account.nil?
= table_link_to 'trash', t('admin.reports.unassign'), admin_report_path(@report, outcome: 'unassign'), method: :put
= table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post
%hr.spacer
@ -104,7 +105,7 @@
- @report_notes.each do |item|
- if item.is_a?(Admin::ActionLog)
= render partial: 'action_log', locals: { action_log: item }
- elsif item.is_a?(ReportNote)
- else
= render item
= simple_form_for @report_note, url: admin_report_notes_path do |f|

@ -1,25 +0,0 @@
- content_for :page_title do
= t('admin.suspensions.title', acct: @account.acct)
= simple_form_for @suspension, url: admin_account_suspension_path(@account.id), method: :post do |f|
%p.hint= t('admin.suspensions.warning_html')
.fields-group
%ul
%li.negative-hint
= number_to_human @account.statuses_count, strip_insignificant_zeros: true
= t('accounts.posts', count: @account.statuses_count)
%li.negative-hint
= number_to_human @account.following_count, strip_insignificant_zeros: true
= t('accounts.following', count: @account.following_count)
%li.negative-hint
= number_to_human @account.followers_count, strip_insignificant_zeros: true
= t('accounts.followers', count: @account.followers_count)
%p.hint= t('admin.suspensions.hint_html', value: content_tag(:code, @account.acct))
= f.input :acct
= f.input_field :report_id, as: :hidden
.actions
= f.button :button, t('admin.suspensions.proceed'), type: :submit, class: 'negative'

@ -0,0 +1,11 @@
- content_for :page_title do
= t('admin.warning_presets.edit_preset')
= simple_form_for @warning_preset, url: admin_warning_preset_path(@warning_preset) do |f|
= render 'shared/error_messages', object: @warning_preset
.fields-group
= f.input :text, wrapper: :with_block_label
.actions
= f.button :button, t('generic.save_changes'), type: :submit

@ -0,0 +1,30 @@
- content_for :page_title do
= t('admin.warning_presets.title')
- if can? :create, :account_warning_preset
= simple_form_for @warning_preset, url: admin_warning_presets_path do |f|
= render 'shared/error_messages', object: @warning_preset
.fields-group
= f.input :text, wrapper: :with_block_label
.actions
= f.button :button, t('admin.warning_presets.add_new'), type: :submit
%hr.spacer/
- unless @warning_presets.empty?
.table-wrapper
%table.table
%thead
%tr
%th= t('simple_form.labels.account_warning_preset.text')
%th
%tbody
- @warning_presets.each do |preset|
%tr
%td
= Formatter.instance.linkify(preset.text)
%td
= table_link_to 'pencil', t('admin.warning_presets.edit'), edit_admin_warning_preset_path(preset)
= table_link_to 'trash', t('admin.warning_presets.delete'), admin_warning_preset_path(preset), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }

@ -0,0 +1,63 @@
%table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.email-body
.email-container
%table.content-section{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.content-cell.hero
.email-row
.col-6
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.text-center.padded
%table.hero-icon.alert-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td
= image_tag full_pack_url('icon_warning.png'), alt: ''
%h1= t "user_mailer.warning.title.#{@warning.action}"
%table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.email-body
.email-container
%table.content-section{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.content-cell.content-start
.email-row
.col-6
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.text-center
- unless @warning.none_action?
%p= t "user_mailer.warning.explanation.#{@warning.action}"
- unless @warning.text.blank?
= Formatter.instance.linkify(@warning.text)
%table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.email-body
.email-container
%table.content-section{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.content-cell
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.button-cell
%table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.button-primary
= link_to about_more_url do
%span= t 'user_mailer.warning.review_server_policies'

@ -0,0 +1,9 @@
<%= t "user_mailer.warning.title.#{@warning.action}" %>
===
<% unless @warning.none_action? %>
<%= t "user_mailer.warning.explanation.#{@warning.action}" %>
<% end %>
<%= @warning.text %>

@ -2,7 +2,7 @@
===
<%= t 'user_mailer.welcome.full_handle' %> (<%= "@#{@resource.account.username}@#{@instance}" %>)
<%= t 'user_mailer.welcome.full_handle' %> (<%= "@#{@resource.account.local_username_and_domain}" %>)
<%= t 'user_mailer.welcome.full_handle_hint', instance: @instance %>
---

@ -455,11 +455,6 @@ ar:
last_delivery: آخر إيداع
title: WebSub
topic: الموضوع
suspensions:
bad_acct_msg: قيمة التأكيد غير متطابقة. متأكد مِن أنك بصدد تعليق الحساب الصحيح؟
hint_html: 'لتأكيد إجراء تعليق الحساب، يُرجى إدخال %{value} في الحقل التالي:'
proceed: مواصلة
title: تعليق الحساب %{acct}
tags:
accounts: الحسابات
hidden: المخفية

@ -121,8 +121,6 @@ ast:
failed_to_execute: Fallu al executar
subscriptions:
title: WebSub
suspensions:
warning_html: 'El suspender esta cuenta va desaniciar <strong>de mou irreversible</strong> los sos datos qu''inclúin:'
title: Alministración
admin_mailer:
new_report:

@ -439,12 +439,6 @@ ca:
last_delivery: Últim lliurament
title: WebSub
topic: Tema
suspensions:
bad_acct_msg: El valor de confirmació no s'ha trobat. Estàs suspenen el compte correcte?
hint_html: 'Per confirmar la suspensió del compte, introdueix %{value} al camp següent:'
proceed: Procedeix
title: Suspèn %{acct}
warning_html: 'Suspenen aquest compte esborrarà <strong>irreversiblement</strong> les dades del compte, incloent:'
tags:
accounts: Comptes
hidden: Amagat

@ -439,12 +439,6 @@ co:
last_delivery: Ultima arricata
title: WebSub
topic: Sughjettu
suspensions:
bad_acct_msg: U valore di cunfirmazione ùn era micca curretta. Site sicuru·a di suspende u bonu contu?
hint_html: 'Per cunfirmà a suspensione di u contu, entrate %{value} quì sottu:'
proceed: Cuntinuà
title: Suspende %{acct}
warning_html: 'A suspensione di u contu sguasserà di manera <strong>irreversibile</strong> i so dati, cum''è:'
tags:
accounts: Conti
hidden: Piattatu

@ -444,12 +444,6 @@ cs:
last_delivery: Poslední doručení
title: WebSub
topic: Téma
suspensions:
bad_acct_msg: Hodnota pro potvrzení neodpovídá. Suspendujete správný účet?
hint_html: 'Pro potvrzení suspenzace účtu prosím zadejte do pole níže %{value}:'
proceed: Pokračovat
title: Suspendovat účet %{acct}
warning_html: 'Suspenzace tohoto účtu <strong>nenávratně</strong> smaže z tohoto účtu data, včetně:'
tags:
accounts: Účty
hidden: Skryté

@ -423,12 +423,6 @@ cy:
last_delivery: Danfoniad diwethaf
title: WebSub
topic: Pwnc
suspensions:
bad_acct_msg: Nid yw'r gwerthoedd cadarnhau yn cyfateb. Ydych chi'n atal y cyfrif cywir?
hint_html: 'I gadarnhau atal y cyfrif, mewnbynwch %{value} yn y maes isod:'
proceed: Parhau
title: Atal %{acct}
warning_html: 'Mi fydd atal y cyfrif hwn yn dileu data <strong>am byth</strong> o''r cyfrif hwn, gan gynnwys:'
title: Gweinyddiaeth
admin_mailer:
new_report:

@ -427,12 +427,6 @@ da:
last_delivery: Sidste levering
title: Websub
topic: Emne
suspensions:
bad_acct_msg: Bekræftelsværdien stemte ikke overens. Er du ved at udelukke den rigtige konto?
hint_html: 'For at bekræfte udelukkelsen af kontoen, indtast venligst %{value} i nedenstående felt:'
proceed: Fortsæt
title: Udeluk %{acct}
warning_html: 'Udelukkelse af denne konto vil <strong>uigenkaldeligt</strong> slette al data fra denne konto, hvilket indebærer:'
title: Administration
admin_mailer:
new_report:

@ -439,12 +439,6 @@ de:
last_delivery: Letzte Zustellung
title: WebSub
topic: Thema
suspensions:
bad_acct_msg: Der Bestätigungswert stimmt nicht überein. Sperrst du das richtige Benutzerkonto?
hint_html: 'Um die Sperrung des Benutzerkontos zu genehmigen tippe %{value} in das Feld unten ein:'
proceed: Fortfahren
title: "%{acct} sperren"
warning_html: 'Die Sperrung des Benutzerkontos wird <strong>unwiederrufliche</strong> Schäden hervorrufen und alle Daten löschen, die folgendes beinhalten:'
tags:
accounts: Konten
hidden: Versteckt

@ -439,12 +439,6 @@ el:
last_delivery: Τελευταία παράδοση
title: WebSub
topic: Θέμα
suspensions:
bad_acct_msg: Η τιμή επιβεβαίωσης δεν ταιριάζει. Σίγουρα αναστέλλεις το σωστό λογαριασμό;
hint_html: 'Για να επιβεβαιώσεις την αναστολή του λογαριασμού, γράψε %{value} στο ακόλουθο πεδίο:'
proceed: Συνέχεια
title: Αναστολή %{acct}
warning_html: 'Αναστέλλοντας αυτό το λογαριασμό θα διαγραφούν <strong>αμετάκλητα</strong> δεδομένα του, μεταξύ των οποίων:'
tags:
accounts: Λογαριασμοί
hidden: Κρυμμένες

@ -70,6 +70,9 @@ en:
moderator: Mod
unfollow: Unfollow
admin:
account_actions:
action: Perform action
title: Perform moderation action on %{acct}
account_moderation_notes:
create: Leave note
created_msg: Moderation note successfully created!
@ -173,6 +176,7 @@ en:
assigned_to_self_report: "%{name} assigned report %{target} to themselves"
change_email_user: "%{name} changed the e-mail address of user %{target}"
confirm_user: "%{name} confirmed e-mail address of user %{target}"
create_account_warning: "%{name} sent a warning to %{target}"
create_custom_emoji: "%{name} uploaded new emoji %{target}"
create_domain_block: "%{name} blocked domain %{target}"
create_email_domain_block: "%{name} blacklisted e-mail domain %{target}"
@ -441,12 +445,6 @@ en:
last_delivery: Last delivery
title: WebSub
topic: Topic
suspensions:
bad_acct_msg: The confirmation value didn't match up. Are you suspending the right account?
hint_html: 'To confirm the suspension of the account, please enter %{value} into the field below:'
proceed: Proceed
title: Suspend %{acct}
warning_html: 'Suspending this account will <strong>irreversibly</strong> delete data from this account, which includes:'
tags:
accounts: Accounts
hidden: Hidden
@ -456,6 +454,12 @@ en:
unhide: Show in directory
visible: Visible
title: Administration
warning_presets:
add_new: Add new
delete: Delete
edit: Edit
edit_preset: Edit warning preset
title: Manage warning presets
admin_mailer:
new_report:
body: "%{reporter} has reported %{target}"
@ -922,6 +926,22 @@ en:
explanation: You requested a full backup of your Mastodon account. It's now ready for download!
subject: Your archive is ready for download
title: Archive takeout
warning:
explanation:
disable: While your account is frozen, your account data remains intact, but you cannot perform any actions until it is unlocked.
silence: While your account is limited, only people who are already following you will see your toots on this server, and you may be excluded from various public listings. However, others may still manually follow you.
suspend: Your account has been suspended, and all of your toots and your uploaded media files have been irreversibly removed from this server, and servers where you had followers.
review_server_policies: Review server policies
subject:
disable: Your account %{acct} has been frozen
none: Warning for %{acct}
silence: Your account %{acct} has been limited
suspend: Your account %{acct} has been suspended
title:
disable: Account frozen
none: Warning
silence: Account limited
suspend: Account suspended
welcome:
edit_profile_action: Setup profile
edit_profile_step: You can customize your profile by uploading an avatar, header, changing your display name and more. If you’d like to review new followers before they’re allowed to follow you, you can lock your account.

@ -427,11 +427,6 @@ eo:
last_delivery: Lasta livero
title: WebSub
topic: Temo
suspensions:
hint_html: 'Por konformi la haltigo de la konto, bonvolu enigi %{value} en la kampo sube:'
proceed: Daŭrigita
title: Haltigi %{acct}
warning_html: 'Haltigi ĉi tiu konton forigos <strong>senrevene</strong> datumojn de ĉi tiu konto, inklusive de:'
title: Administrado
admin_mailer:
new_report:

@ -433,12 +433,6 @@ es:
last_delivery: Última entrega
title: WebSub
topic: Tópico
suspensions:
bad_acct_msg: El valor de confirmación no cuadra. ¿Estás suspendiendo la cuenta correcta?
hint_html: 'Para confirmar las suspensión de la cuenta, por favor introduce %{value} en el campo de abajo:'
proceed: Proceder
title: Suspender %{acct}
warning_html: 'Suspender esta cuenta borrará <strong>irreversiblemente</strong> los datos de stra cuenta que incluyen:'
title: Administración
admin_mailer:
new_report:

@ -435,12 +435,6 @@ eu:
last_delivery: Azken bidalketa
title: WebSub
topic: Mintzagaia
suspensions:
bad_acct_msg: Berrespen balioa ez dator bat. Dagokion kontua kanporatzen ari zara?
hint_html: 'Kontuaren kanporatzea berresteko, sartu %{value} beheko eremuan:'
proceed: Jarraitu
title: Kanporatu %{acct}
warning_html: 'Kontu hau kanporatzeak <strong>behin betiko</strong> ezabatuko ditu kontu honetako datuak, hauek barne:'
tags:
accounts: Kontuak
hidden: Ezkutatuta

@ -433,12 +433,6 @@ fa:
last_delivery: آخرین ارسال
title: WebSub
topic: موضوع
suspensions:
bad_acct_msg: محتوایی که برای تأیید وارد کردید منطبق نبود. آیا دارید حساب درستی را معلق میکنید؟
hint_html: 'برای تأیید معلقکردن حساب، لطفاً در کادر زیر %{value} را وارد کنید:'
proceed: ادامه
title: معلقکردن %{acct}
warning_html: 'معلقکردن این حساب <strong>برای همیشه</strong> دادههایش را پاک میکند. دادههایی شامل:'
title: مدیریت سرور
admin_mailer:
new_report:

@ -439,12 +439,6 @@ fr:
last_delivery: Dernière livraison
title: WebSub
topic: Sujet
suspensions:
bad_acct_msg: La valeur de confirmation n'a pas correspondu. Êtes-vous certain de suspendre le bon compte ?
hint_html: 'Pour confirmer la suspension du compte, veuillez entrer %{value} dans le champ ci-dessous :'
proceed: Confirmer
title: Suspension de %{acct}
warning_html: 'Suspendre ce compte effacera <strong>irréversiblement</strong> les données de ce compte, ce qui inclut :'
tags:
accounts: Comptes
hidden: Masqué

@ -439,12 +439,6 @@ gl:
last_delivery: Última entrega
title: WebSub
topic: Asunto
suspensions:
bad_acct_msg: O valor de confirmación non é coincidente. Está a suspender a conta correcta?
hint_html: 'Para confirmar a suspensión da conta introduza %{value} no campo inferior:'
proceed: Proceder
title: Suspender %{acct}
warning_html: 'Ao suspender esta conta eliminará <strong>de xeito irreversible</strong> os datos de esta conta, que inclúe:'
tags:
accounts: Contas
hidden: Ocultas

@ -429,12 +429,6 @@ it:
confirmed: Confermato
expires_in: Scade in
topic: Argomento
suspensions:
bad_acct_msg: Il valore di conferma non corrisponde. Stai sospendendo l'account giusto?
hint_html: 'Per confermare la sospensione dell''account, inserisci %{value} nel campo qui sotto:'
proceed: Contin