diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index 605240c7ad543..3fa2a0b72e15f 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -20,6 +20,7 @@ def create @custom_emoji = CustomEmoji.new(resource_params) if @custom_emoji.save + log_action :create, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg') else render :new @@ -30,6 +31,7 @@ def update authorize @custom_emoji, :update? if @custom_emoji.update(resource_params) + log_action :update, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg') else redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg') @@ -38,7 +40,8 @@ def update def destroy authorize @custom_emoji, :destroy? - @custom_emoji.destroy + @custom_emoji.destroy! + log_action :destroy, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg') end @@ -49,6 +52,7 @@ def copy emoji.image = @custom_emoji.image if emoji.save + log_action :create, emoji flash[:notice] = I18n.t('admin.custom_emojis.copied_msg') else flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg') diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index e383dc8314d5c..64de2cbf0cfd0 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -21,6 +21,7 @@ def create if @domain_block.save DomainBlockWorker.perform_async(@domain_block.id) + log_action :create, @domain_block redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg') else render :new @@ -34,6 +35,7 @@ def show def destroy authorize @domain_block, :destroy? UnblockDomainService.new.call(@domain_block, retroactive_unblock?) + log_action :destroy, @domain_block redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb index d05035b8ff1c9..9fe85064e34e3 100644 --- a/app/controllers/admin/email_domain_blocks_controller.rb +++ b/app/controllers/admin/email_domain_blocks_controller.rb @@ -20,6 +20,7 @@ def create @email_domain_block = EmailDomainBlock.new(resource_params) if @email_domain_block.save + log_action :create, @email_domain_block redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg') else render :new @@ -29,6 +30,7 @@ def create def destroy authorize @email_domain_block, :destroy? @email_domain_block.destroy! + log_action :destroy, @email_domain_block redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index 4f66ce708ae2e..535bd11d487d2 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -8,7 +8,7 @@ class ReportedStatusesController < BaseController def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_report_path(@report) @@ -16,13 +16,15 @@ def create def update authorize @status, :update? - @status.update(status_params) + @status.update!(status_params) + log_action :update, @status redirect_to admin_report_path(@report) end def destroy authorize @status, :destroy? RemovalWorker.perform_async(@status.id) + log_action :destroy, @status render json: @status end diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index b54a9b8247649..5d4325f574424 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -26,7 +26,7 @@ def index def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_account_statuses_path(@account.id, current_params) @@ -34,13 +34,15 @@ def create def update authorize @status, :update? - @status.update(status_params) + @status.update!(status_params) + log_action :update, @status redirect_to admin_account_statuses_path(@account.id, current_params) end def destroy authorize @status, :destroy? RemovalWorker.perform_async(@status.id) + log_action :destroy, @status render json: @status end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 0b627501f0f97..c7e2d5091fe06 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -8,9 +8,48 @@ def linkable_log_target(record) when 'User' link_to "@#{record.account.acct}", admin_account_path(record.account_id) when 'CustomEmoji' - [":#{record.shortcode}:", record.domain].compact.join('@') + ":#{record.shortcode}:" when 'Report' link_to "##{record.id}", admin_report_path(record) + when 'DomainBlock', 'EmailDomainBlock' + link_to record.domain, "https://#{record.domain}" + when 'Status' + link_to ActivityPub::TagManager.instance.uri_for(record), TagManager.instance.url_for(record) end end + + def log_target_from_history(type, attributes) + case type + when 'CustomEmoji' + ":#{attributes['shortcode']}:" + when 'DomainBlock', 'EmailDomainBlock' + link_to attributes['domain'], "https://#{attributes['domain']}" + when 'Status' + tmp_status = Status.new(attributes) + link_to ActivityPub::TagManager.instance.uri_for(tmp_status), TagManager.instance.url_for(tmp_status) + 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 == 'DomainBlock' + log.recorded_changes.slice('severity', 'reject_media') + elsif log.target_type == 'Status' && log.action == :update + log.recorded_changes.slice('sensitive') + 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 end diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index a29428ce626e3..8f472dd7a692d 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -357,9 +357,12 @@ padding: 14px; color: $ui-secondary-color; line-height: 20px; + height: 60vh; + overflow: auto; li { line-height: 140%; + white-space: nowrap; } .user { @@ -382,4 +385,20 @@ color: inherit; } } + + .extras { + color: $ui-primary-color; + } + + .diff-old { + color: $error-red; + } + + .diff-neutral { + color: $ui-secondary-color; + } + + .diff-new { + color: $success-green; + } } diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb index 46b56876017ea..0f7047bbe0e4f 100644 --- a/app/models/admin/action_log.rb +++ b/app/models/admin/action_log.rb @@ -3,16 +3,19 @@ # # Table name: admin_action_logs # -# id :integer not null, primary key -# account_id :integer -# action :string default(""), not null -# target_type :string -# target_id :integer -# created_at :datetime not null -# updated_at :datetime not null +# id :integer not null, primary key +# account_id :integer +# action :string default(""), not null +# target_type :string +# target_id :integer +# recorded_changes :text default(""), not null +# created_at :datetime not null +# updated_at :datetime not null # class Admin::ActionLog < ApplicationRecord + serialize :recorded_changes + belongs_to :account, required: true belongs_to :target, required: true, polymorphic: true @@ -22,7 +25,30 @@ def action super.to_sym end + def decorative_action + if action == :create && %w(DomainBlock EmailDomainBlock).include?(target_type) + :block + elsif action == :destroy && %w(DomainBlock EmailDomainBlock).include?(target_type) + :unblock + else + action + end + end + def destructive? - [:silence, :disable, :suspend].include?(action) + [:silence, :disable, :suspend, :block].include?(decorative_action) + end + + before_validation :set_changes + + private + + def set_changes + case action + when :destroy, :create + self.recorded_changes = target.attributes + when :update, :promote, :demote + self.recorded_changes = target.previous_changes + end end end diff --git a/app/models/form/status_batch.rb b/app/models/form/status_batch.rb index a97b4aa2865d9..4f08a30497321 100644 --- a/app/models/form/status_batch.rb +++ b/app/models/form/status_batch.rb @@ -2,8 +2,9 @@ class Form::StatusBatch include ActiveModel::Model + include AccountableConcern - attr_accessor :status_ids, :action + attr_accessor :status_ids, :action, :current_account ACTION_TYPE = %w(nsfw_on nsfw_off delete).freeze @@ -20,11 +21,14 @@ def save def change_sensitive(sensitive) media_attached_status_ids = MediaAttachment.where(status_id: status_ids).pluck(:status_id) + ApplicationRecord.transaction do Status.where(id: media_attached_status_ids).find_each do |status| status.update!(sensitive: sensitive) + log_action :update, status end end + true rescue ActiveRecord::RecordInvalid false @@ -33,7 +37,9 @@ def change_sensitive(sensitive) def delete_statuses Status.where(id: status_ids).find_each do |status| RemovalWorker.perform_async(status.id) + log_action :destroy, status end + true end end diff --git a/app/views/admin/action_logs/_action_log.html.haml b/app/views/admin/action_logs/_action_log.html.haml index fd78250da44eb..ff6c4e9ae1e5e 100644 --- a/app/views/admin/action_logs/_action_log.html.haml +++ b/app/views/admin/action_logs/_action_log.html.haml @@ -1,5 +1,10 @@ %li %time= l action_log.created_at %span.user= action_log.account.username - %span.action{ class: action_log.destructive? ? 'destructive' : '' }= action_log.action - %span.target= linkable_log_target action_log.target + %span.action{ class: action_log.destructive? ? 'destructive' : '' }= action_log.decorative_action + %span.target + - if action_log.target + = linkable_log_target action_log.target + - else + = log_target_from_history action_log.target_type, action_log.recorded_changes + %span.extras= log_extra_attributes relevant_log_changes(action_log) diff --git a/db/migrate/20171119172437_create_admin_action_logs.rb b/db/migrate/20171119172437_create_admin_action_logs.rb index 25753f2b8435f..0c2b6c623d1c9 100644 --- a/db/migrate/20171119172437_create_admin_action_logs.rb +++ b/db/migrate/20171119172437_create_admin_action_logs.rb @@ -4,6 +4,7 @@ def change t.belongs_to :account, foreign_key: { on_delete: :cascade } t.string :action, null: false, default: '' t.references :target, polymorphic: true + t.text :recorded_changes, null: false, default: '' t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index b242269a5f9b9..77f6a2d107e2a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -85,6 +85,7 @@ t.string "action", default: "", null: false t.string "target_type" t.bigint "target_id" + t.text "recorded_changes", default: "", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["account_id"], name: "index_admin_action_logs_on_account_id"