From 0fb9536d3888cd7b6013c239d5be85f095a6e8ad Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Sun, 5 Dec 2021 21:48:39 +0100
Subject: [PATCH] Add batch suspend for accounts in admin UI (#17009)
---
app/controllers/admin/accounts_controller.rb | 33 ++++++-
.../admin/pending_accounts_controller.rb | 52 -----------
.../concerns/accountable_concern.rb | 4 +-
app/helpers/admin/action_logs_helper.rb | 2 +
app/helpers/admin/dashboard_helper.rb | 39 +++++++-
app/javascript/styles/mastodon/accounts.scss | 30 +++++-
app/javascript/styles/mastodon/tables.scss | 5 +
app/javascript/styles/mastodon/widgets.scss | 18 ++++
app/models/account.rb | 2 +
app/models/account_filter.rb | 91 +++++++++++--------
app/models/admin/action_log.rb | 2 +-
app/models/admin/action_log_filter.rb | 2 +
app/models/form/account_batch.rb | 51 +++++++++--
app/views/admin/accounts/_account.html.haml | 59 +++++++-----
app/views/admin/accounts/index.html.haml | 56 +++++++-----
app/views/admin/dashboard/index.html.haml | 2 +-
app/views/admin/instances/show.html.haml | 2 +-
app/views/admin/ip_blocks/_ip_block.html.haml | 6 +-
.../admin/pending_accounts/_account.html.haml | 16 ----
.../admin/pending_accounts/index.html.haml | 33 -------
.../admin_mailer/new_pending_account.text.erb | 2 +-
config/locales/en.yml | 11 +--
config/navigation.rb | 2 +-
config/routes.rb | 12 +--
.../admin/accounts_controller_spec.rb | 14 +--
spec/models/account_filter_spec.rb | 42 +--------
26 files changed, 311 insertions(+), 277 deletions(-)
delete mode 100644 app/controllers/admin/pending_accounts_controller.rb
delete mode 100644 app/views/admin/pending_accounts/_account.html.haml
delete mode 100644 app/views/admin/pending_accounts/index.html.haml
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index 1dd7430e09..948e70d5b5 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -2,13 +2,24 @@
module Admin
class AccountsController < BaseController
- before_action :set_account, except: [:index]
+ before_action :set_account, except: [:index, :batch]
before_action :require_remote_account!, only: [:redownload]
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
def index
authorize :account, :index?
+
@accounts = filtered_accounts.page(params[:page])
+ @form = Form::AccountBatch.new
+ end
+
+ def batch
+ @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
+ @form.save
+ rescue ActionController::ParameterMissing
+ flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+ ensure
+ redirect_to admin_accounts_path(filter_params)
end
def show
@@ -38,13 +49,13 @@ module Admin
def approve
authorize @account.user, :approve?
@account.user.approve!
- redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
+ redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
end
def reject
authorize @account.user, :reject?
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
- redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
+ redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
end
def destroy
@@ -121,11 +132,25 @@ module Admin
end
def filtered_accounts
- AccountFilter.new(filter_params).results
+ AccountFilter.new(filter_params.with_defaults(order: 'recent')).results
end
def filter_params
params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS)
end
+
+ def form_account_batch_params
+ params.require(:form_account_batch).permit(:action, account_ids: [])
+ end
+
+ def action_from_button
+ if params[:suspend]
+ 'suspend'
+ elsif params[:approve]
+ 'approve'
+ elsif params[:reject]
+ 'reject'
+ end
+ end
end
end
diff --git a/app/controllers/admin/pending_accounts_controller.rb b/app/controllers/admin/pending_accounts_controller.rb
deleted file mode 100644
index b62a9bc846..0000000000
--- a/app/controllers/admin/pending_accounts_controller.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-module Admin
- class PendingAccountsController < BaseController
- before_action :set_accounts, only: :index
-
- def index
- @form = Form::AccountBatch.new
- end
-
- def batch
- @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
- @form.save
- rescue ActionController::ParameterMissing
- flash[:alert] = I18n.t('admin.accounts.no_account_selected')
- ensure
- redirect_to admin_pending_accounts_path(current_params)
- end
-
- def approve_all
- Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'approve').save
- redirect_to admin_pending_accounts_path(current_params)
- end
-
- def reject_all
- Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'reject').save
- redirect_to admin_pending_accounts_path(current_params)
- end
-
- private
-
- def set_accounts
- @accounts = Account.joins(:user).merge(User.pending.recent).includes(user: :invite_request).page(params[:page])
- end
-
- def form_account_batch_params
- params.require(:form_account_batch).permit(:action, account_ids: [])
- end
-
- def action_from_button
- if params[:approve]
- 'approve'
- elsif params[:reject]
- 'reject'
- end
- end
-
- def current_params
- params.slice(:page).permit(:page)
- end
- end
-end
diff --git a/app/controllers/concerns/accountable_concern.rb b/app/controllers/concerns/accountable_concern.rb
index 3cdcffc51c..87d62478da 100644
--- a/app/controllers/concerns/accountable_concern.rb
+++ b/app/controllers/concerns/accountable_concern.rb
@@ -3,7 +3,7 @@
module AccountableConcern
extend ActiveSupport::Concern
- def log_action(action, target)
- Admin::ActionLog.create(account: current_account, action: action, target: target)
+ def log_action(action, target, options = {})
+ Admin::ActionLog.create(account: current_account, action: action, target: target, recorded_changes: options.stringify_keys)
end
end
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index e9a298a248..ae96f7a344 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -36,6 +36,8 @@ module Admin::ActionLogsHelper
def log_target_from_history(type, attributes)
case type
+ when 'User'
+ attributes['username']
when 'CustomEmoji'
attributes['shortcode']
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb
index 4ee2cdef47..32aaf9f5e7 100644
--- a/app/helpers/admin/dashboard_helper.rb
+++ b/app/helpers/admin/dashboard_helper.rb
@@ -1,10 +1,41 @@
# frozen_string_literal: true
module Admin::DashboardHelper
- def feature_hint(feature, enabled)
- indicator = safe_join([enabled ? t('simple_form.yes') : t('simple_form.no'), fa_icon('power-off fw')], ' ')
- class_names = enabled ? 'pull-right positive-hint' : 'pull-right neutral-hint'
+ def relevant_account_ip(account, ip_query)
+ default_ip = [account.user_current_sign_in_ip || account.user_sign_up_ip]
- safe_join([feature, content_tag(:span, indicator, class: class_names)])
+ matched_ip = begin
+ ip_query_addr = IPAddr.new(ip_query)
+ account.user.recent_ips.find { |(_, ip)| ip_query_addr.include?(ip) } || default_ip
+ rescue IPAddr::Error
+ default_ip
+ end.last
+
+ if matched_ip
+ link_to matched_ip, admin_accounts_path(ip: matched_ip)
+ else
+ '-'
+ end
+ end
+
+ def relevant_account_timestamp(account)
+ timestamp, exact = begin
+ if account.user_current_sign_in_at && account.user_current_sign_in_at < 24.hours.ago
+ [account.user_current_sign_in_at, true]
+ elsif account.user_current_sign_in_at
+ [account.user_current_sign_in_at, false]
+ elsif account.user_pending?
+ [account.user_created_at, true]
+ elsif account.last_status_at.present?
+ [account.last_status_at, true]
+ else
+ [nil, false]
+ end
+ end
+
+ return '-' if timestamp.nil?
+ return t('generic.today') unless exact
+
+ content_tag(:time, l(timestamp), class: 'time-ago', datetime: timestamp.iso8601, title: l(timestamp))
end
end
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index b8a6c80188..485fe4a9dc 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -326,7 +326,12 @@
}
}
-.batch-table__row--muted .pending-account__header {
+.batch-table__row--muted {
+ color: lighten($ui-base-color, 26%);
+}
+
+.batch-table__row--muted .pending-account__header,
+.batch-table__row--muted .accounts-table {
&,
a,
strong {
@@ -334,10 +339,31 @@
}
}
-.batch-table__row--attention .pending-account__header {
+.batch-table__row--muted .accounts-table {
+ tbody td.accounts-table__extra,
+ &__count,
+ &__count small {
+ color: lighten($ui-base-color, 26%);
+ }
+}
+
+.batch-table__row--attention {
+ color: $gold-star;
+}
+
+.batch-table__row--attention .pending-account__header,
+.batch-table__row--attention .accounts-table {
&,
a,
strong {
color: $gold-star;
}
}
+
+.batch-table__row--attention .accounts-table {
+ tbody td.accounts-table__extra,
+ &__count,
+ &__count small {
+ color: $gold-star;
+ }
+}
diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index 62f5554ffc..36bc07a72b 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -237,6 +237,11 @@ a.table-action-link {
flex: 1 1 auto;
}
+ &__quote {
+ padding: 12px;
+ padding-top: 0;
+ }
+
&__extra {
flex: 0 0 auto;
text-align: right;
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index 4e03868a68..43284eb482 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -443,6 +443,24 @@
}
}
+ tbody td.accounts-table__extra {
+ width: 120px;
+ text-align: right;
+ color: $darker-text-color;
+ padding-right: 16px;
+
+ a {
+ text-decoration: none;
+ color: inherit;
+
+ &:focus,
+ &:hover,
+ &:active {
+ text-decoration: underline;
+ }
+ }
+ }
+
&__comment {
width: 50%;
vertical-align: initial !important;
diff --git a/app/models/account.rb b/app/models/account.rb
index d289c5e539..238ea1d655 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -125,6 +125,8 @@ class Account < ApplicationRecord
:unconfirmed_email,
:current_sign_in_ip,
:current_sign_in_at,
+ :created_at,
+ :sign_up_ip,
:confirmed?,
:approved?,
:pending?,
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index 2b001385f2..defd531acb 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -2,18 +2,15 @@
class AccountFilter
KEYS = %i(
- local
- remote
- by_domain
- active
- pending
- silenced
- suspended
+ origin
+ status
+ permissions
username
+ by_domain
display_name
email
ip
- staff
+ invited_by
order
).freeze
@@ -21,11 +18,10 @@ class AccountFilter
def initialize(params)
@params = params
- set_defaults!
end
def results
- scope = Account.includes(:user).reorder(nil)
+ scope = Account.includes(:account_stat, user: [:session_activations, :invite_request]).without_instance_actor.reorder(nil)
params.each do |key, value|
scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
@@ -36,30 +32,16 @@ class AccountFilter
private
- def set_defaults!
- params['local'] = '1' if params['remote'].blank?
- params['active'] = '1' if params['suspended'].blank? && params['silenced'].blank? && params['pending'].blank?
- params['order'] = 'recent' if params['order'].blank?
- end
-
def scope_for(key, value)
case key.to_s
- when 'local'
- Account.local.without_instance_actor
- when 'remote'
- Account.remote
+ when 'origin'
+ origin_scope(value)
+ when 'permissions'
+ permissions_scope(value)
+ when 'status'
+ status_scope(value)
when 'by_domain'
Account.where(domain: value)
- when 'active'
- Account.without_suspended
- when 'pending'
- accounts_with_users.merge(User.pending)
- when 'disabled'
- accounts_with_users.merge(User.disabled)
- when 'silenced'
- Account.silenced
- when 'suspended'
- Account.suspended
when 'username'
Account.matches_username(value)
when 'display_name'
@@ -68,8 +50,8 @@ class AccountFilter
accounts_with_users.merge(User.matches_email(value))
when 'ip'
valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value)) : Account.none
- when 'staff'
- accounts_with_users.merge(User.staff)
+ when 'invited_by'
+ invited_by_scope(value)
when 'order'
order_scope(value)
else
@@ -77,21 +59,56 @@ class AccountFilter
end
end
+ def origin_scope(value)
+ case value.to_s
+ when 'local'
+ Account.local
+ when 'remote'
+ Account.remote
+ else
+ raise "Unknown origin: #{value}"
+ end
+ end
+
+ def status_scope(value)
+ case value.to_s
+ when 'active'
+ Account.without_suspended
+ when 'pending'
+ accounts_with_users.merge(User.pending)
+ when 'suspended'
+ Account.suspended
+ else
+ raise "Unknown status: #{value}"
+ end
+ end
+
def order_scope(value)
- case value
+ case value.to_s
when 'active'
- params['remote'] ? Account.joins(:account_stat).by_recent_status : Account.joins(:user).by_recent_sign_in
+ accounts_with_users.left_joins(:account_stat).order(Arel.sql('coalesce(users.current_sign_in_at, account_stats.last_status_at, to_timestamp(0)) desc, accounts.id desc'))
when 'recent'
Account.recent
- when 'alphabetic'
- Account.alphabetic
else
raise "Unknown order: #{value}"
end
end
+ def invited_by_scope(value)
+ Account.left_joins(user: :invite).merge(Invite.where(user_id: value.to_s))
+ end
+
+ def permissions_scope(value)
+ case value.to_s
+ when 'staff'
+ accounts_with_users.merge(User.staff)
+ else
+ raise "Unknown permissions: #{value}"
+ end
+ end
+
def accounts_with_users
- Account.joins(:user)
+ Account.left_joins(:user)
end
def valid_ip?(value)
diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb
index 1d1db1b7a9..852bff7134 100644
--- a/app/models/admin/action_log.rb
+++ b/app/models/admin/action_log.rb
@@ -17,7 +17,7 @@ class Admin::ActionLog < ApplicationRecord
serialize :recorded_changes
belongs_to :account
- belongs_to :target, polymorphic: true
+ belongs_to :target, polymorphic: true, optional: true
default_scope -> { order('id desc') }
diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb
index 6e19dcf708..2af9d7c9c6 100644
--- a/app/models/admin/action_log_filter.rb
+++ b/app/models/admin/action_log_filter.rb
@@ -11,6 +11,8 @@ class Admin::ActionLogFilter
assigned_to_self_report: { target_type: 'Report', action: 'assigned_to_self' }.freeze,
change_email_user: { target_type: 'User', action: 'change_email' }.freeze,
confirm_user: { target_type: 'User', action: 'confirm' }.freeze,
+ approve_user: { target_type: 'User', action: 'approve' }.freeze,
+ reject_user: { target_type: 'User', action: 'reject' }.freeze,
create_account_warning: { target_type: 'AccountWarning', action: 'create' }.freeze,
create_announcement: { target_type: 'Announcement', action: 'create' }.freeze,
create_custom_emoji: { target_type: 'CustomEmoji', action: 'create' }.freeze,
diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb
index f1e1c8a655..4bf1775bb9 100644
--- a/app/models/form/account_batch.rb
+++ b/app/models/form/account_batch.rb
@@ -3,6 +3,7 @@
class Form::AccountBatch
include ActiveModel::Model
include Authorization
+ include AccountableConcern
include Payloadable
attr_accessor :account_ids, :action, :current_account
@@ -25,19 +26,21 @@ class Form::AccountBatch
suppress_follow_recommendation!
when 'unsuppress_follow_recommendation'
unsuppress_follow_recommendation!
+ when 'suspend'
+ suspend!
end
end
private
def follow!
- accounts.find_each do |target_account|
+ accounts.each do |target_account|
FollowService.new.call(current_account, target_account)
end
end
def unfollow!
- accounts.find_each do |target_account|
+ accounts.each do |target_account|
UnfollowService.new.call(current_account, target_account)
end
end
@@ -61,23 +64,31 @@ class Form::AccountBatch
end
def approve!
- users = accounts.includes(:user).map(&:user)
-
- users.each { |user| authorize(user, :approve?) }
- .each(&:approve!)
+ accounts.includes(:user).find_each do |account|
+ approve_account(account)
+ end
end
def reject!
- records = accounts.includes(:user)
+ accounts.includes(:user).find_each do |account|
+ reject_account(account)
+ end
+ end
- records.each { |account| authorize(account.user, :reject?) }
- .each { |account| DeleteAccountService.new.call(account, reserve_email: false, reserve_username: false) }
+ def suspend!
+ accounts.find_each do |account|
+ if account.user_pending?
+ reject_account(account)
+ else
+ suspend_account(account)
+ end
+ end
end
def suppress_follow_recommendation!
authorize(:follow_recommendation, :suppress?)
- accounts.each do |account|
+ accounts.find_each do |account|
FollowRecommendationSuppression.create(account: account)
end
end
@@ -87,4 +98,24 @@ class Form::AccountBatch
FollowRecommendationSuppression.where(account_id: account_ids).destroy_all
end
+
+ def reject_account(account)
+ authorize(account.user, :reject?)
+ log_action(:reject, account.user, username: account.username)
+ account.suspend!(origin: :local)
+ AccountDeletionWorker.perform_async(account.id, reserve_username: false)
+ end
+
+ def suspend_account(account)
+ authorize(account, :suspend?)
+ log_action(:suspend, account)
+ account.suspend!(origin: :local)
+ Admin::SuspensionWorker.perform_async(account.id)
+ end
+
+ def approve_account(account)
+ authorize(account.user, :approve?)
+ log_action(:approve, account.user)
+ account.user.approve!
+ end
end
diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml
index c9bd8c686c..2df91301ed 100644
--- a/app/views/admin/accounts/_account.html.haml
+++ b/app/views/admin/accounts/_account.html.haml
@@ -1,24 +1,35 @@
-%tr
- %td
- = admin_account_link_to(account)
- %td
- %div.account-badges= account_badge(account, all: true)
- %td
- - if account.user_current_sign_in_ip
- %samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip
- - else
- \-
- %td
- - if account.user_current_sign_in_at
- %time.time-ago{ datetime: account.user_current_sign_in_at.iso8601, title: l(account.user_current_sign_in_at) }= l account.user_current_sign_in_at
- - elsif account.last_status_at.present?
- %time.time-ago{ datetime: account.last_status_at.iso8601, title: l(account.last_status_at) }= l account.last_status_at
- - else
- \-
- %td
- - if account.local? && account.user_pending?
- = table_link_to 'check', t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:approve, account.user)
- = table_link_to 'times', t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:reject, account.user)
- - else
- = table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}")
- = table_link_to 'globe', t('admin.accounts.public'), ActivityPub::TagManager.instance.url_for(account)
+.batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', account.suspended? && 'batch-table__row--muted'] }
+ %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
+ = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
+ .batch-table__row__content.batch-table__row__content--unpadded
+ %table.accounts-table
+ %tbody
+ %tr
+ %td
+ = account_link_to account, path: admin_account_path(account.id)
+ %td.accounts-table__count.optional
+ - if account.suspended? || account.user_pending?
+ \-
+ - else
+ = friendly_number_to_human account.statuses_count
+ %small= t('accounts.posts', count: account.statuses_count).downcase
+ %td.accounts-table__count.optional
+ - if account.suspended? || account.user_pending?
+ \-
+ - else
+ = friendly_number_to_human account.followers_count
+ %small= t('accounts.followers', count: account.followers_count).downcase
+ %td.accounts-table__count
+ = relevant_account_timestamp(account)
+ %small= t('accounts.last_active')
+ %td.accounts-table__extra
+ - if account.local?
+ - if account.user_email
+ = link_to account.user_email.split('@').last, admin_accounts_path(email: "%@#{account.user_email.split('@').last}"), title: account.user_email
+ - else
+ \-
+ %br/
+ %samp.ellipsized-ip= relevant_account_ip(account, params[:ip])
+ - if !account.suspended? && account.user_pending? && account.user&.invite_request&.text&.present?
+ .batch-table__row__content__quote
+ %p= account.user&.invite_request&.text
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index 398ab4bb46..7c00451450 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -1,34 +1,37 @@
- content_for :page_title do
= t('admin.accounts.title')
+- content_for :header_tags do
+ = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
.filters
.filter-subset
%strong= t('admin.accounts.location.title')
%ul
- %li= filter_link_to t('admin.accounts.location.local'), remote: nil
- %li= filter_link_to t('admin.accounts.location.remote'), remote: '1'
+ %li= filter_link_to t('generic.all'), origin: nil
+ %li= filter_link_to t('admin.accounts.location.local'), origin: 'local'
+ %li= filter_link_to t('admin.accounts.location.remote'), origin: 'remote'
.filter-subset
%strong= t('admin.accounts.moderation.title')
%ul
- %li= link_to safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), admin_pending_accounts_path
- %li= filter_link_to t('admin.accounts.moderation.active'), silenced: nil, suspended: nil, pending: nil
- %li= filter_link_to t('admin.accounts.moderation.silenced'), silenced: '1', suspended: nil, pending: nil
- %li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', silenced: nil, pending: nil
+ %li= filter_link_to t('generic.all'), status: nil
+ %li= filter_link_to t('admin.accounts.moderation.active'), status: 'active'
+ %li= filter_link_to t('admin.accounts.moderation.suspended'), status: 'suspended'
+ %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), status: 'pending'
.filter-subset
%strong= t('admin.accounts.role')
%ul
- %li= filter_link_to t('admin.accounts.moderation.all'), staff: nil
- %li= filter_link_to t('admin.accounts.roles.staff'), staff: '1'
+ %li= filter_link_to t('admin.accounts.moderation.all'), permissions: nil
+ %li= filter_link_to t('admin.accounts.roles.staff'), permissions: 'staff'
.filter-subset
%strong= t 'generic.order_by'
%ul
%li= filter_link_to t('relationships.most_recent'), order: nil
- %li= filter_link_to t('admin.accounts.username'), order: 'alphabetic'
%li= filter_link_to t('relationships.last_active'), order: 'active'
= form_tag admin_accounts_url, method: 'GET', class: 'simple_form' do
.fields-group
- - AccountFilter::KEYS.each do |key|
+ - (AccountFilter::KEYS - %i(origin status permissions)).each do |key|
- if params[key].present?
= hidden_field_tag key, params[key]
@@ -41,16 +44,27 @@
%button.button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'
-.table-wrapper
- %table.table
- %thead
- %tr
- %th= t('admin.accounts.username')
- %th= t('admin.accounts.role')
- %th= t('admin.accounts.most_recent_ip')
- %th= t('admin.accounts.most_recent_activity')
- %th
- %tbody
- = render partial: 'account', collection: @accounts
+= form_for(@form, url: batch_admin_accounts_path) do |f|
+ = hidden_field_tag :page, params[:page] || 1
+
+ - AccountFilter::KEYS.each do |key|
+ = hidden_field_tag key, params[key] if params[key].present?
+
+ .batch-table
+ .batch-table__toolbar
+ %label.batch-table__toolbar__select.batch-checkbox-all
+ = check_box_tag :batch_checkbox_all, nil, false
+ .batch-table__toolbar__actions
+ - if @accounts.any? { |account| account.user_pending? }
+ = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+ = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+ = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), name: :suspend, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+ .batch-table__body
+ - if @accounts.empty?
+ = nothing_here 'nothing-here--under-tabs'
+ - else
+ = render partial: 'account', collection: @accounts, locals: { f: f }
= paginate @accounts
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 895333a585..4b581f5ea6 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -38,7 +38,7 @@
%span= t('admin.dashboard.pending_reports_html', count: @pending_reports_count)
= fa_icon 'chevron-right fw'
- = link_to admin_pending_accounts_path, class: 'dashboard__quick-access' do
+ = link_to admin_accounts_path(status: 'pending'), class: 'dashboard__quick-access' do
%span= t('admin.dashboard.pending_users_html', count: @pending_users_count)
= fa_icon 'chevron-right fw'
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index 4625293389..d6542ac3e2 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -15,7 +15,7 @@
.dashboard__counters
%div
- = link_to admin_accounts_path(remote: '1', by_domain: @instance.domain) do
+ = link_to admin_accounts_path(origin: 'remote', by_domain: @instance.domain) do
.dashboard__counters__num= number_with_delimiter @instance.accounts_count
.dashboard__counters__label= t 'admin.accounts.title'
%div
diff --git a/app/views/admin/ip_blocks/_ip_block.html.haml b/app/views/admin/ip_blocks/_ip_block.html.haml
index e07e2b4448..b8d3ac0e86 100644
--- a/app/views/admin/ip_blocks/_ip_block.html.haml
+++ b/app/views/admin/ip_blocks/_ip_block.html.haml
@@ -1,9 +1,9 @@
.batch-table__row
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
= f.check_box :ip_block_ids, { multiple: true, include_hidden: false }, ip_block.id
- .batch-table__row__content
- .batch-table__row__content__text
- %samp= "#{ip_block.ip}/#{ip_block.ip.prefix}"
+ .batch-table__row__content.pending-account
+ .pending-account__header
+ %samp= link_to "#{ip_block.ip}/#{ip_block.ip.prefix}", admin_accounts_path(ip: "#{ip_block.ip}/#{ip_block.ip.prefix}")
- if ip_block.comment.present?
•
= ip_block.comment
diff --git a/app/views/admin/pending_accounts/_account.html.haml b/app/views/admin/pending_accounts/_account.html.haml
deleted file mode 100644
index 5b475b59a9..0000000000
--- a/app/views/admin/pending_accounts/_account.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-.batch-table__row
- %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
- = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
- .batch-table__row__content.pending-account
- .pending-account__header
- = link_to admin_account_path(account.id) do
- %strong= account.user_email
- = "(@#{account.username})"
- %br/
- %samp= account.user_current_sign_in_ip
- •
- = t 'admin.accounts.time_in_queue', time: time_ago_in_words(account.user&.created_at)
-
- - if account.user&.invite_request&.text&.present?
- .pending-account__body
- %p= account.user&.invite_request&.text
diff --git a/app/views/admin/pending_accounts/index.html.haml b/app/views/admin/pending_accounts/index.html.haml
deleted file mode 100644
index 8384a1c9f0..0000000000
--- a/app/views/admin/pending_accounts/index.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-- content_for :page_title do
- = t('admin.pending_accounts.title', count: User.pending.count)
-
-- content_for :header_tags do
- = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
-
-= form_for(@form, url: batch_admin_pending_accounts_path) do |f|
- = hidden_field_tag :page, params[:page] || 1
-
- .batch-table
- .batch-table__toolbar
- %label.batch-table__toolbar__select.batch-checkbox-all
- = check_box_tag :batch_checkbox_all, nil, false
- .batch-table__toolbar__actions
- = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
-
- = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
- .batch-table__body
- - if @accounts.empty?
- = nothing_here 'nothing-here--under-tabs'
- - else
- = render partial: 'account', collection: @accounts, locals: { f: f }
-
-= paginate @accounts
-
-%hr.spacer/
-
-%div.action-buttons
- %div
- = link_to t('admin.accounts.approve_all'), approve_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
-
- %div
- = link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
diff --git a/app/views/admin_mailer/new_pending_account.text.erb b/app/views/admin_mailer/new_pending_account.text.erb
index a466ee2de7..bcc2518190 100644
--- a/app/views/admin_mailer/new_pending_account.text.erb
+++ b/app/views/admin_mailer/new_pending_account.text.erb
@@ -9,4 +9,4 @@
<%= quote_wrap(@account.user&.invite_request&.text) %>
<% end %>
-<%= raw t('application_mailer.view')%> <%= admin_pending_accounts_url %>
+<%= raw t('application_mailer.view')%> <%= admin_accounts_url(status: 'pending') %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 1aa96ba0f7..0aa25ae863 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -99,7 +99,6 @@ en:
accounts:
add_email_domain_block: Block e-mail domain
approve: Approve
- approve_all: Approve all
approved_msg: Successfully approved %{username}'s sign-up application
are_you_sure: Are you sure?
avatar: Avatar
@@ -153,7 +152,6 @@ en:
active: Active
all: All
pending: Pending
- silenced: Limited
suspended: Suspended
title: Moderation
moderation_notes: Moderation notes
@@ -171,7 +169,6 @@ en:
redownload: Refresh profile
redownloaded_msg: Successfully refreshed %{username}'s profile from origin
reject: Reject
- reject_all: Reject all
rejected_msg: Successfully rejected %{username}'s sign-up application
remove_avatar: Remove avatar
remove_header: Remove header
@@ -210,7 +207,6 @@ en:
suspended: Suspended
suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable but it will not recover any data it previously had.
suspension_reversible_hint_html: The account has been suspended, and the data will be fully removed on %{date}. Until then, the account can be restored without any ill effects. If you wish to remove all of the account's data immediately, you can do so below.
- time_in_queue: Waiting in queue %{time}
title: Accounts
unconfirmed_email: Unconfirmed email
undo_sensitized: Undo force-sensitive
@@ -226,6 +222,7 @@ en:
whitelisted: Allowed for federation
action_logs:
action_types:
+ approve_user: Approve User
assigned_to_self_report: Assign Report
change_email_user: Change E-mail for User
confirm_user: Confirm User
@@ -255,6 +252,7 @@ en:
enable_user: Enable User
memorialize_account: Memorialize Account
promote_user: Promote User
+ reject_user: Reject User
remove_avatar_user: Remove Avatar
reopen_report: Reopen Report
reset_password_user: Reset Password
@@ -271,6 +269,7 @@ en:
update_domain_block: Update Domain Block
update_status: Update Post
actions:
+ approve_user_html: "%{name} approved sign-up from %{target}"
assigned_to_self_report_html: "%{name} assigned report %{target} to themselves"
change_email_user_html: "%{name} changed the e-mail address of user %{target}"
confirm_user_html: "%{name} confirmed e-mail address of user %{target}"
@@ -300,6 +299,7 @@ en:
enable_user_html: "%{name} enabled login for user %{target}"
memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page"
promote_user_html: "%{name} promoted user %{target}"
+ reject_user_html: "%{name} rejected sign-up from %{target}"
remove_avatar_user_html: "%{name} removed %{target}'s avatar"
reopen_report_html: "%{name} reopened report %{target}"
reset_password_user_html: "%{name} reset password of user %{target}"
@@ -519,8 +519,6 @@ en:
title: Create new IP rule
no_ip_block_selected: No IP rules were changed as none were selected
title: IP rules
- pending_accounts:
- title: Pending accounts (%{count})
relationships:
title: "%{acct}'s relationships"
relays:
@@ -980,6 +978,7 @@ en:
none: None
order_by: Order by
save_changes: Save changes
+ today: today
validation_errors:
one: Something isn't quite right yet! Please review the error below
other: Something isn't quite right yet! Please review %{count} errors below
diff --git a/config/navigation.rb b/config/navigation.rb
index 99743c222a..fc03a2a778 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -41,7 +41,7 @@ SimpleNavigation::Configuration.run do |navigation|
n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |s|
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
- s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts}
+ s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts}
s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path
s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations}
s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.admin? }
diff --git a/config/routes.rb b/config/routes.rb
index 5f73129eac..31b398e2c2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -251,6 +251,10 @@ Rails.application.routes.draw do
post :reject
end
+ collection do
+ post :batch
+ end
+
resource :change_email, only: [:show, :update]
resource :reset, only: [:create]
resource :action, only: [:new, :create], controller: 'account_actions'
@@ -271,14 +275,6 @@ Rails.application.routes.draw do
end
end
- resources :pending_accounts, only: [:index] do
- collection do
- post :approve_all
- post :reject_all
- post :batch
- end
- end
-
resources :users, only: [] do
resource :two_factor_authentication, only: [:destroy]
resource :sign_in_token_authentication, only: [:create, :destroy]
diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb
index 608606ff90..a5ef396ae4 100644
--- a/spec/controllers/admin/accounts_controller_spec.rb
+++ b/spec/controllers/admin/accounts_controller_spec.rb
@@ -21,12 +21,9 @@ RSpec.describe Admin::AccountsController, type: :controller do
expect(AccountFilter).to receive(:new) do |params|
h = params.to_h
- expect(h[:local]).to eq '1'
- expect(h[:remote]).to eq '1'
+ expect(h[:origin]).to eq 'local'
expect(h[:by_domain]).to eq 'domain'
- expect(h[:active]).to eq '1'
- expect(h[:silenced]).to eq '1'
- expect(h[:suspended]).to eq '1'
+ expect(h[:status]).to eq 'active'
expect(h[:username]).to eq 'username'
expect(h[:display_name]).to eq 'display name'
expect(h[:email]).to eq 'local-part@domain'
@@ -36,12 +33,9 @@ RSpec.describe Admin::AccountsController, type: :controller do
end
get :index, params: {
- local: '1',
- remote: '1',
+ origin: 'local',
by_domain: 'domain',
- active: '1',
- silenced: '1',
- suspended: '1',
+ status: 'active',
username: 'username',
display_name: 'display name',
email: 'local-part@domain',
diff --git a/spec/models/account_filter_spec.rb b/spec/models/account_filter_spec.rb
index 0cdb373f6a..c2bd8c2202 100644
--- a/spec/models/account_filter_spec.rb
+++ b/spec/models/account_filter_spec.rb
@@ -2,10 +2,10 @@ require 'rails_helper'
describe AccountFilter do
describe 'with empty params' do
- it 'defaults to recent local not-suspended account list' do
+ it 'excludes instance actor by default' do
filter = described_class.new({})
- expect(filter.results).to eq Account.local.without_instance_actor.recent.without_suspended
+ expect(filter.results).to eq Account.without_instance_actor
end
end
@@ -16,42 +16,4 @@ describe AccountFilter do
expect { filter.results }.to raise_error(/wrong/)
end
end
-
- describe 'with valid params' do
- it 'combines filters on Account' do
- filter = described_class.new(
- by_domain: 'test.com',
- silenced: true,
- username: 'test',
- display_name: 'name',
- email: 'user@example.com',
- )
-
- allow(Account).to receive(:where).and_return(Account.none)
- allow(Account).to receive(:silenced).and_return(Account.none)
- allow(Account).to receive(:matches_display_name).and_return(Account.none)
- allow(Account).to receive(:matches_username).and_return(Account.none)
- allow(User).to receive(:matches_email).and_return(User.none)
-
- filter.results
-
- expect(Account).to have_received(:where).with(domain: 'test.com')
- expect(Account).to have_received(:silenced)
- expect(Account).to have_received(:matches_username).with('test')
- expect(Account).to have_received(:matches_display_name).with('name')
- expect(User).to have_received(:matches_email).with('user@example.com')
- end
-
- describe 'that call account methods' do
- %i(local remote silenced suspended).each do |option|
- it "delegates the #{option} option" do
- allow(Account).to receive(option).and_return(Account.none)
- filter = described_class.new({ option => true })
- filter.results
-
- expect(Account).to have_received(option).at_least(1)
- end
- end
- end
- end
end
--
GitLab