diff --git a/.circleci/config.yml b/.circleci/config.yml
index a343fc654a..dd943e3279 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -139,9 +139,10 @@ jobs:
docker:
- image: circleci/ruby:2.7-buster-node
environment: *ruby_environment
- - image: circleci/postgres:10.6-alpine
+ - image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
+ POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine
steps:
- *attach_workspace
@@ -158,9 +159,10 @@ jobs:
docker:
- image: circleci/ruby:2.7-buster-node
environment: *ruby_environment
- - image: circleci/postgres:10.6-alpine
+ - image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
+ POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine
<<: *test_steps
@@ -169,9 +171,10 @@ jobs:
docker:
- image: circleci/ruby:2.6-buster-node
environment: *ruby_environment
- - image: circleci/postgres:10.6-alpine
+ - image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
+ POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine
<<: *test_steps
@@ -180,9 +183,10 @@ jobs:
docker:
- image: circleci/ruby:2.5-buster-node
environment: *ruby_environment
- - image: circleci/postgres:10.6-alpine
+ - image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
+ POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine
<<: *test_steps
diff --git a/Vagrantfile b/Vagrantfile
index e00ed1ca64..143fa27fdb 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -91,7 +91,7 @@ VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
- config.vm.box = "ubuntu/xenial64"
+ config.vm.box = "ubuntu/bionic64"
config.vm.provider :virtualbox do |vb|
vb.name = "mastodon"
diff --git a/app/controllers/admin/action_logs_controller.rb b/app/controllers/admin/action_logs_controller.rb
index e273dfeaef..2d77620df7 100644
--- a/app/controllers/admin/action_logs_controller.rb
+++ b/app/controllers/admin/action_logs_controller.rb
@@ -2,8 +2,18 @@
module Admin
class ActionLogsController < BaseController
- def index
- @action_logs = Admin::ActionLog.page(params[:page])
+ before_action :set_action_logs
+
+ def index; end
+
+ private
+
+ def set_action_logs
+ @action_logs = Admin::ActionLogFilter.new(filter_params).results.page(params[:page])
+ end
+
+ def filter_params
+ params.slice(:page, *Admin::ActionLogFilter::KEYS).permit(:page, *Admin::ActionLogFilter::KEYS)
end
end
end
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index 6bc75aa566..88d6e4580d 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -9,79 +9,8 @@ module Admin::ActionLogsHelper
end
end
- def relevant_log_changes(log)
- if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)
- log.recorded_changes.slice('domain')
- elsif log.target_type == 'CustomEmoji' && log.action == :update
- log.recorded_changes.slice('domain', 'visible_in_picker')
- elsif log.target_type == 'User' && [:promote, :demote].include?(log.action)
- log.recorded_changes.slice('moderator', 'admin')
- elsif log.target_type == 'User' && [:change_email].include?(log.action)
- log.recorded_changes.slice('email', 'unconfirmed_email')
- elsif log.target_type == 'DomainBlock'
- log.recorded_changes.slice('severity', 'reject_media')
- elsif log.target_type == 'Status' && log.action == :update
- log.recorded_changes.slice('sensitive')
- elsif log.target_type == 'Announcement' && log.action == :update
- log.recorded_changes.slice('text', 'starts_at', 'ends_at', 'all_day')
- end
- end
-
- def log_extra_attributes(hash)
- safe_join(hash.to_a.map { |key, value| safe_join([content_tag(:span, key, class: 'diff-key'), '=', log_change(value)]) }, ' ')
- end
-
- def log_change(val)
- return content_tag(:span, val, class: 'diff-neutral') unless val.is_a?(Array)
- safe_join([content_tag(:span, val.first, class: 'diff-old'), content_tag(:span, val.last, class: 'diff-new')], '→')
- end
-
- def icon_for_log(log)
- case log.target_type
- when 'Account', 'User'
- 'user'
- when 'CustomEmoji'
- 'file'
- when 'Report'
- 'flag'
- when 'DomainBlock'
- 'lock'
- when 'DomainAllow'
- 'plus-circle'
- when 'EmailDomainBlock'
- 'envelope'
- when 'Status'
- 'pencil'
- when 'AccountWarning'
- 'warning'
- when 'Announcement'
- 'bullhorn'
- end
- end
-
- def class_for_log_icon(log)
- case log.action
- when :enable, :unsuspend, :unsilence, :confirm, :promote, :resolve
- 'positive'
- when :create
- opposite_verbs?(log) ? 'negative' : 'positive'
- when :update, :reset_password, :disable_2fa, :memorialize, :change_email
- 'neutral'
- when :demote, :silence, :disable, :suspend, :remove_avatar, :remove_header, :reopen
- 'negative'
- when :destroy
- opposite_verbs?(log) ? 'positive' : 'negative'
- else
- ''
- end
- end
-
private
- def opposite_verbs?(log)
- %w(DomainBlock EmailDomainBlock AccountWarning).include?(log.target_type)
- end
-
def linkable_log_target(record)
case record.class.name
when 'Account'
@@ -99,7 +28,7 @@ module Admin::ActionLogsHelper
when 'AccountWarning'
link_to record.target_account.acct, admin_account_path(record.target_account_id)
when 'Announcement'
- link_to "##{record.id}", edit_admin_announcement_path(record.id)
+ link_to truncate(record.text), edit_admin_announcement_path(record.id)
end
end
@@ -118,7 +47,7 @@ module Admin::ActionLogsHelper
I18n.t('admin.action_logs.deleted_status')
end
when 'Announcement'
- "##{attributes['id']}"
+ truncate(attributes['text'])
end
end
end
diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb
index 6ab92939d8..ba0ca96389 100644
--- a/app/helpers/admin/filter_helper.rb
+++ b/app/helpers/admin/filter_helper.rb
@@ -10,6 +10,7 @@ module Admin::FilterHelper
InviteFilter::KEYS,
RelationshipFilter::KEYS,
AnnouncementFilter::KEYS,
+ Admin::ActionLogFilter::KEYS,
].flatten.freeze
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js
index 09da9efd39..f2334c254b 100644
--- a/app/javascript/core/admin.js
+++ b/app/javascript/core/admin.js
@@ -32,6 +32,10 @@ delegate(document, '.media-spoiler-hide-button', 'click', () => {
});
});
+delegate(document, '.filter-subset--with-select select', 'change', ({ target }) => {
+ target.form.submit();
+});
+
const onDomainBlockSeverityChange = (target) => {
const rejectMediaDiv = document.querySelector('.input.with_label.domain_block_reject_media');
const rejectReportsDiv = document.querySelector('.input.with_label.domain_block_reject_reports');
diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.js
index 10522bf30f..efbe1a23cf 100644
--- a/app/javascript/flavours/glitch/features/follow_requests/index.js
+++ b/app/javascript/flavours/glitch/features/follow_requests/index.js
@@ -11,6 +11,7 @@ import { fetchFollowRequests, expandFollowRequests } from 'flavours/glitch/actio
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ScrollableList from 'flavours/glitch/components/scrollable_list';
+import { me } from 'flavours/glitch/util/initial_state';
const messages = defineMessages({
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
@@ -19,6 +20,8 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
+ locked: !!state.getIn(['accounts', me, 'locked']),
+ domain: state.getIn(['meta', 'domain']),
});
export default @connect(mapStateToProps)
@@ -30,6 +33,8 @@ class FollowRequests extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list,
+ locked: PropTypes.bool,
+ domain: PropTypes.string,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
};
@@ -43,7 +48,7 @@ class FollowRequests extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
- const { intl, accountIds, hasMore, multiColumn } = this.props;
+ const { intl, accountIds, hasMore, multiColumn, locked, domain } = this.props;
if (!accountIds) {
return (
@@ -54,6 +59,15 @@ class FollowRequests extends ImmutablePureComponent {
}
const emptyMessage = ;
+ const unlockedPrependMessage = locked ? null : (
+
+
+
+ );
return (
@@ -65,6 +79,7 @@ class FollowRequests extends ImmutablePureComponent {
hasMore={hasMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
+ prepend={unlockedPrependMessage}
>
{accountIds.map(id =>
,
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 26a98c66fe..0d24da4ddf 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -418,6 +418,11 @@ body,
}
}
+ &--with-select strong {
+ display: block;
+ margin-bottom: 10px;
+ }
+
a {
display: inline-block;
color: $darker-text-color;
@@ -567,19 +572,22 @@ body,
}
.log-entry {
- margin-bottom: 20px;
line-height: 20px;
+ padding: 15px 0;
+ background: $ui-base-color;
+ border-bottom: 1px solid lighten($ui-base-color, 4%);
+
+ &:last-child {
+ border-bottom: 0;
+ }
&__header {
display: flex;
justify-content: flex-start;
align-items: center;
- padding: 10px;
- background: $ui-base-color;
color: $darker-text-color;
- border-radius: 4px 4px 0 0;
font-size: 14px;
- position: relative;
+ padding: 0 10px;
}
&__avatar {
@@ -606,44 +614,6 @@ body,
color: $dark-text-color;
}
- &__extras {
- background: lighten($ui-base-color, 6%);
- border-radius: 0 0 4px 4px;
- padding: 10px;
- color: $darker-text-color;
- font-family: $font-monospace, monospace;
- font-size: 12px;
- word-wrap: break-word;
- min-height: 20px;
- }
-
- &__icon {
- font-size: 28px;
- margin-right: 10px;
- color: $dark-text-color;
- }
-
- &__icon__overlay {
- position: absolute;
- top: 10px;
- right: 10px;
- width: 10px;
- height: 10px;
- border-radius: 50%;
-
- &.positive {
- background: $success-green;
- }
-
- &.negative {
- background: lighten($error-red, 12%);
- }
-
- &.neutral {
- background: $ui-highlight-color;
- }
- }
-
a,
.username,
.target {
@@ -651,18 +621,6 @@ body,
text-decoration: none;
font-weight: 500;
}
-
- .diff-old {
- color: lighten($error-red, 12%);
- }
-
- .diff-neutral {
- color: $secondary-text-color;
- }
-
- .diff-new {
- color: $success-green;
- }
}
a.name-tag,
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index 525dcaf90f..3269638eb7 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -452,7 +452,8 @@
}
.empty-column-indicator,
-.error-column {
+.error-column,
+.follow_requests-unlocked_explanation {
color: $dark-text-color;
background: $ui-base-color;
text-align: center;
@@ -482,6 +483,11 @@
}
}
+.follow_requests-unlocked_explanation {
+ background: darken($ui-base-color, 4%);
+ contain: initial;
+}
+
.error-column {
flex-direction: column;
}
diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js
index bef56fab5f..7078e4e6cc 100644
--- a/app/javascript/mastodon/features/follow_requests/index.js
+++ b/app/javascript/mastodon/features/follow_requests/index.js
@@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountAuthorizeContainer from './containers/account_authorize_container';
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
import ScrollableList from '../../components/scrollable_list';
+import { me } from '../../initial_state';
const messages = defineMessages({
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
@@ -19,6 +20,8 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
+ locked: !!state.getIn(['accounts', me, 'locked']),
+ domain: state.getIn(['meta', 'domain']),
});
export default @connect(mapStateToProps)
@@ -31,6 +34,8 @@ class FollowRequests extends ImmutablePureComponent {
shouldUpdateScroll: PropTypes.func,
hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list,
+ locked: PropTypes.bool,
+ domain: PropTypes.string,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
};
@@ -44,7 +49,7 @@ class FollowRequests extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
- const { intl, shouldUpdateScroll, accountIds, hasMore, multiColumn } = this.props;
+ const { intl, shouldUpdateScroll, accountIds, hasMore, multiColumn, locked, domain } = this.props;
if (!accountIds) {
return (
@@ -55,6 +60,15 @@ class FollowRequests extends ImmutablePureComponent {
}
const emptyMessage = ;
+ const unlockedPrependMessage = locked ? null : (
+
+
+
+ );
return (
@@ -66,6 +80,7 @@ class FollowRequests extends ImmutablePureComponent {
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
+ prepend={unlockedPrependMessage}
>
{accountIds.map(id =>
,
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 225b7ade2a..993347273d 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -1532,6 +1532,10 @@
{
"defaultMessage": "You don't have any follow requests yet. When you receive one, it will show up here.",
"id": "empty_column.follow_requests"
+ },
+ {
+ "defaultMessage": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
+ "id": "follow_requests.unlocked_explanation"
}
],
"path": "app/javascript/mastodon/features/follow_requests/index.json"
@@ -2961,4 +2965,4 @@
],
"path": "app/javascript/mastodon/features/video/index.json"
}
-]
\ No newline at end of file
+]
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 703bbcaac4..c7153b7b13 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -168,6 +168,7 @@
"errors.unexpected_crash.report_issue": "Report issue",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
+ "follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
"getting_started.developers": "Developers",
"getting_started.directory": "Profile directory",
"getting_started.documentation": "Documentation",
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index fb136d1a3c..7bff2daa13 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -418,6 +418,11 @@ body,
}
}
+ &--with-select strong {
+ display: block;
+ margin-bottom: 10px;
+ }
+
a {
display: inline-block;
color: $darker-text-color;
@@ -583,19 +588,22 @@ body,
}
.log-entry {
- margin-bottom: 20px;
line-height: 20px;
+ padding: 15px 0;
+ background: $ui-base-color;
+ border-bottom: 1px solid lighten($ui-base-color, 4%);
+
+ &:last-child {
+ border-bottom: 0;
+ }
&__header {
display: flex;
justify-content: flex-start;
align-items: center;
- padding: 10px;
- background: $ui-base-color;
color: $darker-text-color;
- border-radius: 4px 4px 0 0;
font-size: 14px;
- position: relative;
+ padding: 0 10px;
}
&__avatar {
@@ -622,44 +630,6 @@ body,
color: $dark-text-color;
}
- &__extras {
- background: lighten($ui-base-color, 6%);
- border-radius: 0 0 4px 4px;
- padding: 10px;
- color: $darker-text-color;
- font-family: $font-monospace, monospace;
- font-size: 12px;
- word-wrap: break-word;
- min-height: 20px;
- }
-
- &__icon {
- font-size: 28px;
- margin-right: 10px;
- color: $dark-text-color;
- }
-
- &__icon__overlay {
- position: absolute;
- top: 10px;
- right: 10px;
- width: 10px;
- height: 10px;
- border-radius: 50%;
-
- &.positive {
- background: $success-green;
- }
-
- &.negative {
- background: lighten($error-red, 12%);
- }
-
- &.neutral {
- background: $ui-highlight-color;
- }
- }
-
a,
.username,
.target {
@@ -667,18 +637,6 @@ body,
text-decoration: none;
font-weight: 500;
}
-
- .diff-old {
- color: lighten($error-red, 12%);
- }
-
- .diff-neutral {
- color: $secondary-text-color;
- }
-
- .diff-new {
- color: $success-green;
- }
}
a.name-tag,
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index aea2ae6cb3..dd82b08240 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3800,7 +3800,8 @@ a.status-card.compact:hover {
}
.empty-column-indicator,
-.error-column {
+.error-column,
+.follow_requests-unlocked_explanation {
color: $dark-text-color;
background: $ui-base-color;
text-align: center;
@@ -3831,6 +3832,11 @@ a.status-card.compact:hover {
}
}
+.follow_requests-unlocked_explanation {
+ background: darken($ui-base-color, 4%);
+ contain: initial;
+}
+
.error-column {
flex-direction: column;
}
diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb
new file mode 100644
index 0000000000..0ba7e16094
--- /dev/null
+++ b/app/models/admin/action_log_filter.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+class Admin::ActionLogFilter
+ KEYS = %i(
+ action_type
+ account_id
+ target_account_id
+ ).freeze
+
+ ACTION_TYPE_MAP = {
+ 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,
+ 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,
+ create_domain_allow: { target_type: 'DomainAllow', action: 'create' }.freeze,
+ create_domain_block: { target_type: 'DomainBlock', action: 'create' }.freeze,
+ create_email_domain_block: { target_type: 'EmailDomainBlock', action: 'create' }.freeze,
+ demote_user: { target_type: 'User', action: 'demote' }.freeze,
+ destroy_announcement: { target_type: 'Announcement', action: 'destroy' }.freeze,
+ destroy_custom_emoji: { target_type: 'CustomEmoji', action: 'destroy' }.freeze,
+ destroy_domain_allow: { target_type: 'DomainAllow', action: 'destroy' }.freeze,
+ destroy_domain_block: { target_type: 'DomainBlock', action: 'destroy' }.freeze,
+ destroy_email_domain_block: { target_type: 'EmailDomainBlock', action: 'destroy' }.freeze,
+ destroy_status: { target_type: 'Status', action: 'destroy' }.freeze,
+ disable_2fa_user: { target_type: 'User', action: 'disable' }.freeze,
+ disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
+ disable_user: { target_type: 'User', action: 'disable' }.freeze,
+ enable_custom_emoji: { target_type: 'CustomEmoji', action: 'enable' }.freeze,
+ enable_user: { target_type: 'User', action: 'enable' }.freeze,
+ memorialize_account: { target_type: 'Account', action: 'memorialize' }.freeze,
+ promote_user: { target_type: 'User', action: 'promote' }.freeze,
+ remove_avatar_user: { target_type: 'User', action: 'remove_avatar' }.freeze,
+ reopen_report: { target_type: 'Report', action: 'reopen' }.freeze,
+ reset_password_user: { target_type: 'User', action: 'reset_password' }.freeze,
+ resolve_report: { target_type: 'Report', action: 'resolve' }.freeze,
+ silence_account: { target_type: 'Account', action: 'silence' }.freeze,
+ suspend_account: { target_type: 'Account', action: 'suspend' }.freeze,
+ unassigned_report: { target_type: 'Report', action: 'unassigned' }.freeze,
+ unsilence_account: { target_type: 'Account', action: 'unsilence' }.freeze,
+ unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze,
+ update_announcement: { target_type: 'Announcement', action: 'update' }.freeze,
+ update_custom_emoji: { target_type: 'CustomEmoji', action: 'update' }.freeze,
+ update_status: { target_type: 'Status', action: 'update' }.freeze,
+ }.freeze
+
+ attr_reader :params
+
+ def initialize(params)
+ @params = params
+ end
+
+ def results
+ scope = Admin::ActionLog.includes(:target)
+
+ params.each do |key, value|
+ next if key.to_s == 'page'
+
+ scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present?
+ end
+
+ scope
+ end
+
+ private
+
+ def scope_for(key, value)
+ case key
+ when 'action_type'
+ Admin::ActionLog.where(ACTION_TYPE_MAP[value.to_sym])
+ when 'account_id'
+ Admin::ActionLog.where(account_id: value)
+ when 'target_account_id'
+ account = Account.find(value)
+ Admin::ActionLog.where(target: [account, account.user].compact)
+ else
+ raise "Unknown filter: #{key}"
+ end
+ end
+end
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 744d17d1fa..965fd6fb63 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -53,7 +53,7 @@
.dashboard__counters__num= number_with_delimiter @account.targeted_reports.count
.dashboard__counters__label= t '.targeted_reports'
%div
- %div
+ = link_to admin_action_logs_path(target_account_id: @account.id) do
.dashboard__counters__text
- if @account.local? && @account.user.nil?
%span.neutral= t('admin.accounts.deleted')
diff --git a/app/views/admin/action_logs/_action_log.html.haml b/app/views/admin/action_logs/_action_log.html.haml
index a545e189ef..59905f3418 100644
--- a/app/views/admin/action_logs/_action_log.html.haml
+++ b/app/views/admin/action_logs/_action_log.html.haml
@@ -7,9 +7,3 @@
= t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}", name: content_tag(:span, action_log.account.username, class: 'username'), target: content_tag(:span, log_target(action_log), class: 'target')).html_safe
.log-entry__timestamp
%time.formatted{ datetime: action_log.created_at.iso8601 }
- .spacer
- .log-entry__icon
- = fa_icon icon_for_log(action_log)
- .log-entry__icon__overlay{ class: class_for_log_icon(action_log) }
- .log-entry__extras
- = log_extra_attributes relevant_log_changes(action_log)
diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml
index a4d3871a90..937664c4b9 100644
--- a/app/views/admin/action_logs/index.html.haml
+++ b/app/views/admin/action_logs/index.html.haml
@@ -1,6 +1,25 @@
- content_for :page_title do
= t('admin.action_logs.title')
-= render @action_logs
+= form_tag admin_action_logs_url, method: 'GET', class: 'simple_form' do
+ = hidden_field_tag :target_account_id, params[:target_account_id] if params[:target_account_id].present?
+
+ .filters
+ .filter-subset.filter-subset--with-select
+ %strong= t('admin.action_logs.filter_by_user')
+ .input.select.optional
+ = select_tag :account_id, options_from_collection_for_select(Account.joins(:user).merge(User.staff), :id, :username, params[:account_id]), prompt: I18n.t('admin.accounts.moderation.all')
+
+ .filter-subset.filter-subset--with-select
+ %strong= t('admin.action_logs.filter_by_action')
+ .input.select.optional
+ = select_tag :action_type, options_for_select(Admin::ActionLogFilter::ACTION_TYPE_MAP.keys.map { |key| [I18n.t("admin.action_logs.action_types.#{key}"), key]}, params[:action_type]), prompt: I18n.t('admin.accounts.moderation.all')
+
+- if @action_logs.empty?
+ %div.muted-hint.center-text
+ = t 'admin.action_logs.empty'
+- else
+ .announcements-list
+ = render @action_logs
= paginate @action_logs
diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb
index aa15172566..4fec4320c2 100644
--- a/config/initializers/kaminari_config.rb
+++ b/config/initializers/kaminari_config.rb
@@ -2,6 +2,6 @@
Kaminari.configure do |config|
config.default_per_page = 40
- config.window = 1
+ config.window = 2
config.outer_window = 1
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 4d4308560a..5a43026060 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -195,6 +195,42 @@ en:
web: Web
whitelisted: Whitelisted
action_logs:
+ action_types:
+ assigned_to_self_report: Assign Report
+ change_email_user: Change E-mail for User
+ confirm_user: Confirm User
+ create_account_warning: Create Warning
+ create_announcement: Create Announcement
+ create_custom_emoji: Create Custom Emoji
+ create_domain_allow: Create Domain Allow
+ create_domain_block: Create Domain Block
+ create_email_domain_block: Create E-mail Domain Block
+ demote_user: Demote User
+ destroy_announcement: Delete Announcement
+ destroy_custom_emoji: Delete Custom Emoji
+ destroy_domain_allow: Delete Domain Allow
+ destroy_domain_block: Delete Domain Block
+ destroy_email_domain_block: Delete e-mail domain block
+ destroy_status: Delete Status
+ disable_2fa_user: Disable 2FA
+ disable_custom_emoji: Disable Custom Emoji
+ disable_user: Disable User
+ enable_custom_emoji: Enable Custom Emoji
+ enable_user: Enable User
+ memorialize_account: Memorialize Account
+ promote_user: Promote User
+ remove_avatar_user: Remove Avatar
+ reopen_report: Reopen Report
+ reset_password_user: Reset Password
+ resolve_report: Resolve Report
+ silence_account: Silence Account
+ suspend_account: Suspend Account
+ unassigned_report: Unassign Report
+ unsilence_account: Unsilence Account
+ unsuspend_account: Unsuspend Account
+ update_announcement: Update Announcement
+ update_custom_emoji: Update Custom Emoji
+ update_status: Update Status
actions:
assigned_to_self_report: "%{name} assigned report %{target} to themselves"
change_email_user: "%{name} changed the e-mail address of user %{target}"
@@ -232,6 +268,9 @@ en:
update_custom_emoji: "%{name} updated emoji %{target}"
update_status: "%{name} updated status by %{target}"
deleted_status: "(deleted status)"
+ empty: No logs found.
+ filter_by_action: Filter by action
+ filter_by_user: Filter by user
title: Audit log
announcements:
destroyed_msg: Announcement successfully deleted!
diff --git a/spec/helpers/admin/action_log_helper_spec.rb b/spec/helpers/admin/action_log_helper_spec.rb
index d7af6b939e..60f5ecdcca 100644
--- a/spec/helpers/admin/action_log_helper_spec.rb
+++ b/spec/helpers/admin/action_log_helper_spec.rb
@@ -31,242 +31,4 @@ RSpec.describe Admin::ActionLogsHelper, type: :helper do
end
end
end
-
- describe '#relevant_log_changes' do
- let(:log) { double(target_type: target_type, action: log_action, recorded_changes: recorded_changes) }
- let(:recorded_changes) { double }
-
- after do
- hoge.relevant_log_changes(log)
- end
-
- context "log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)" do
- let(:target_type) { 'CustomEmoji' }
- let(:log_action) { :enable }
-
- it "calls log.recorded_changes.slice('domain')" do
- expect(recorded_changes).to receive(:slice).with('domain')
- end
- end
-
- context "log.target_type == 'CustomEmoji' && log.action == :update" do
- let(:target_type) { 'CustomEmoji' }
- let(:log_action) { :update }
-
- it "calls log.recorded_changes.slice('domain', 'visible_in_picker')" do
- expect(recorded_changes).to receive(:slice).with('domain', 'visible_in_picker')
- end
- end
-
- context "log.target_type == 'User' && [:promote, :demote].include?(log.action)" do
- let(:target_type) { 'User' }
- let(:log_action) { :promote }
-
- it "calls log.recorded_changes.slice('moderator', 'admin')" do
- expect(recorded_changes).to receive(:slice).with('moderator', 'admin')
- end
- end
-
- context "log.target_type == 'User' && [:change_email].include?(log.action)" do
- let(:target_type) { 'User' }
- let(:log_action) { :change_email }
-
- it "calls log.recorded_changes.slice('email', 'unconfirmed_email')" do
- expect(recorded_changes).to receive(:slice).with('email', 'unconfirmed_email')
- end
- end
-
- context "log.target_type == 'DomainBlock'" do
- let(:target_type) { 'DomainBlock' }
- let(:log_action) { nil }
-
- it "calls log.recorded_changes.slice('severity', 'reject_media')" do
- expect(recorded_changes).to receive(:slice).with('severity', 'reject_media')
- end
- end
-
- context "log.target_type == 'Status' && log.action == :update" do
- let(:target_type) { 'Status' }
- let(:log_action) { :update }
-
- it "log.recorded_changes.slice('sensitive')" do
- expect(recorded_changes).to receive(:slice).with('sensitive')
- end
- end
- end
-
- describe '#log_extra_attributes' do
- after do
- hoge.log_extra_attributes(hoge: 'hoge')
- end
-
- it "calls content_tag(:span, key, class: 'diff-key')" do
- allow(hoge).to receive(:log_change).with(anything)
- expect(hoge).to receive(:content_tag).with(:span, :hoge, class: 'diff-key')
- end
-
- it 'calls safe_join twice' do
- expect(hoge).to receive(:safe_join).with(
- ['hoge',
- '=',
- 'hoge']
- )
-
- expect(hoge).to receive(:safe_join).with([nil], ' ')
- end
- end
-
- describe '#log_change' do
- after do
- hoge.log_change(val)
- end
-
- context '!val.is_a?(Array)' do
- let(:val) { 'hoge' }
-
- it "calls content_tag(:span, val, class: 'diff-neutral')" do
- expect(hoge).to receive(:content_tag).with(:span, val, class: 'diff-neutral')
- end
- end
-
- context 'val.is_a?(Array)' do
- let(:val) { %w(foo bar) }
-
- it 'calls #content_tag twice and #safe_join' do
- expect(hoge).to receive(:content_tag).with(:span, 'foo', class: 'diff-old')
- expect(hoge).to receive(:content_tag).with(:span, 'bar', class: 'diff-new')
- expect(hoge).to receive(:safe_join).with([nil, nil], '→')
- end
- end
- end
-
- describe '#icon_for_log' do
- subject { hoge.icon_for_log(log) }
-
- context "log.target_type == 'Account'" do
- let(:log) { double(target_type: 'Account') }
-
- it 'returns "user"' do
- expect(subject).to be 'user'
- end
- end
-
- context "log.target_type == 'User'" do
- let(:log) { double(target_type: 'User') }
-
- it 'returns "user"' do
- expect(subject).to be 'user'
- end
- end
-
- context "log.target_type == 'CustomEmoji'" do
- let(:log) { double(target_type: 'CustomEmoji') }
-
- it 'returns "file"' do
- expect(subject).to be 'file'
- end
- end
-
- context "log.target_type == 'Report'" do
- let(:log) { double(target_type: 'Report') }
-
- it 'returns "flag"' do
- expect(subject).to be 'flag'
- end
- end
-
- context "log.target_type == 'DomainBlock'" do
- let(:log) { double(target_type: 'DomainBlock') }
-
- it 'returns "lock"' do
- expect(subject).to be 'lock'
- end
- end
-
- context "log.target_type == 'EmailDomainBlock'" do
- let(:log) { double(target_type: 'EmailDomainBlock') }
-
- it 'returns "envelope"' do
- expect(subject).to be 'envelope'
- end
- end
-
- context "log.target_type == 'Status'" do
- let(:log) { double(target_type: 'Status') }
-
- it 'returns "pencil"' do
- expect(subject).to be 'pencil'
- end
- end
- end
-
- describe '#class_for_log_icon' do
- subject { hoge.class_for_log_icon(log) }
-
- %i(enable unsuspend unsilence confirm promote resolve).each do |action|
- context "log.action == #{action}" do
- let(:log) { double(action: action) }
-
- it 'returns "positive"' do
- expect(subject).to be 'positive'
- end
- end
- end
-
- context 'log.action == :create' do
- context 'opposite_verbs?(log)' do
- let(:log) { double(action: :create, target_type: 'DomainBlock') }
-
- it 'returns "negative"' do
- expect(subject).to be 'negative'
- end
- end
-
- context '!opposite_verbs?(log)' do
- let(:log) { double(action: :create, target_type: '') }
-
- it 'returns "positive"' do
- expect(subject).to be 'positive'
- end
- end
- end
-
- %i(update reset_password disable_2fa memorialize change_email).each do |action|
- context "log.action == #{action}" do
- let(:log) { double(action: action) }
-
- it 'returns "neutral"' do
- expect(subject).to be 'neutral'
- end
- end
- end
-
- %i(demote silence disable suspend remove_avatar remove_header reopen).each do |action|
- context "log.action == #{action}" do
- let(:log) { double(action: action) }
-
- it 'returns "negative"' do
- expect(subject).to be 'negative'
- end
- end
- end
-
- context 'log.action == :destroy' do
- context 'opposite_verbs?(log)' do
- let(:log) { double(action: :destroy, target_type: 'DomainBlock') }
-
- it 'returns "positive"' do
- expect(subject).to be 'positive'
- end
- end
-
- context '!opposite_verbs?(log)' do
- let(:log) { double(action: :destroy, target_type: '') }
-
- it 'returns "negative"' do
- expect(subject).to be 'negative'
- end
- end
- end
- end
end