Skip to content

Commit

Permalink
Log creates, updates and destroys with history of changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Gargron committed Nov 22, 2017
1 parent 2bef3ec commit b5d5063
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 17 deletions.
6 changes: 5 additions & 1 deletion app/controllers/admin/custom_emojis_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
Expand All @@ -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

Expand All @@ -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')
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/admin/domain_blocks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
2 changes: 2 additions & 0 deletions app/controllers/admin/email_domain_blocks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
6 changes: 4 additions & 2 deletions app/controllers/admin/reported_statuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@ 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)
end

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

Expand Down
6 changes: 4 additions & 2 deletions app/controllers/admin/statuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,23 @@ 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)
end

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

Expand Down
41 changes: 40 additions & 1 deletion app/helpers/admin/action_logs_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 19 additions & 0 deletions app/javascript/styles/mastodon/admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
}
}
42 changes: 34 additions & 8 deletions app/models/admin/action_log.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
8 changes: 7 additions & 1 deletion app/models/form/status_batch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
9 changes: 7 additions & 2 deletions app/views/admin/action_logs/_action_log.html.haml
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions db/migrate/20171119172437_create_admin_action_logs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit b5d5063

Please sign in to comment.