mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
Log site customization changes. Use a modal to show staff action log details for site customizations.
This commit is contained in:
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
The modal for viewing the details of a staff action log record.
|
||||||
|
|
||||||
|
@class ChangeSiteCustomizationDetailsController
|
||||||
|
@extends Discourse.Controller
|
||||||
|
@namespace Discourse
|
||||||
|
@uses Discourse.ModalFunctionality
|
||||||
|
@module Discourse
|
||||||
|
**/
|
||||||
|
Discourse.ChangeSiteCustomizationDetailsController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
||||||
|
previousSelected: Ember.computed.equal('selectedTab', 'previous'),
|
||||||
|
newSelected: Ember.computed.equal('selectedTab', 'new'),
|
||||||
|
|
||||||
|
onShow: function() {
|
||||||
|
this.selectNew();
|
||||||
|
},
|
||||||
|
|
||||||
|
selectNew: function() {
|
||||||
|
this.set('selectedTab', 'new');
|
||||||
|
},
|
||||||
|
|
||||||
|
selectPrevious: function() {
|
||||||
|
this.set('selectedTab', 'previous');
|
||||||
|
}
|
||||||
|
});
|
@ -17,8 +17,10 @@ Discourse.StaffActionLog = Discourse.Model.extend({
|
|||||||
var formatted = "";
|
var formatted = "";
|
||||||
formatted += this.format('email', 'email');
|
formatted += this.format('email', 'email');
|
||||||
formatted += this.format('admin.logs.staff_actions.ip_address', 'ip_address');
|
formatted += this.format('admin.logs.staff_actions.ip_address', 'ip_address');
|
||||||
formatted += this.format('admin.logs.staff_actions.new_value', 'new_value');
|
if (!this.get('useModalForDetails')) {
|
||||||
formatted += this.format('admin.logs.staff_actions.previous_value', 'previous_value');
|
formatted += this.format('admin.logs.staff_actions.new_value', 'new_value');
|
||||||
|
formatted += this.format('admin.logs.staff_actions.previous_value', 'previous_value');
|
||||||
|
}
|
||||||
return formatted;
|
return formatted;
|
||||||
}.property('ip_address', 'email'),
|
}.property('ip_address', 'email'),
|
||||||
|
|
||||||
@ -28,7 +30,11 @@ Discourse.StaffActionLog = Discourse.Model.extend({
|
|||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
useModalForDetails: function() {
|
||||||
|
return (this.get('action_name') === 'change_site_customization');
|
||||||
|
}.property('action_name')
|
||||||
});
|
});
|
||||||
|
|
||||||
Discourse.StaffActionLog.reopenClass({
|
Discourse.StaffActionLog.reopenClass({
|
||||||
|
@ -33,6 +33,13 @@ Discourse.AdminLogsStaffActionLogsRoute = Discourse.Route.extend({
|
|||||||
return controller.show();
|
return controller.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
events: {
|
||||||
|
showDetailsModal: function(logRecord) {
|
||||||
|
Discourse.Route.showModal(this, logRecord.action_name + '_details', logRecord);
|
||||||
|
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
deactivate: function() {
|
deactivate: function() {
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
<section class="field">
|
||||||
|
<b>{{i18n admin.customize.css}}</b>: ({{i18n character_count count=stylesheet.length}})<br/>
|
||||||
|
{{textarea value=stylesheet class="plain"}}
|
||||||
|
</section>
|
||||||
|
<section class="field">
|
||||||
|
<b>{{i18n admin.customize.header}}</b>: ({{i18n character_count count=header.length}})<br/>
|
||||||
|
{{textarea value=header class="plain"}}
|
||||||
|
</section>
|
||||||
|
<section class="field">
|
||||||
|
<b>{{i18n admin.customize.enabled}}</b>: {{enabled}}
|
||||||
|
</section>
|
||||||
|
<section class="field">
|
||||||
|
<b>{{i18n admin.customize.override_default}}</b>: {{override_default_style}}
|
||||||
|
</section>
|
@ -0,0 +1,29 @@
|
|||||||
|
<div>
|
||||||
|
<ul class="nav nav-pills">
|
||||||
|
<li {{bindAttr class="newSelected:active"}}>
|
||||||
|
<a href="#" {{action selectNew}}>{{i18n admin.logs.staff_actions.new_value}}</a>
|
||||||
|
</li>
|
||||||
|
<li {{bindAttr class="previousSelected:active"}}>
|
||||||
|
<a href="#" {{action selectPrevious}}>{{i18n admin.logs.staff_actions.previous_value}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div {{bindAttr class=":modal-tab :new-tab newSelected::invisible"}}>
|
||||||
|
{{#with new_value}}
|
||||||
|
{{partial "admin/templates/logs/site_customization_change_details"}}
|
||||||
|
{{/with}}
|
||||||
|
</div>
|
||||||
|
<div {{bindAttr class=":modal-tab :previous-tab previousSelected::invisible"}}>
|
||||||
|
{{#if previous_value}}
|
||||||
|
{{#with previous_value}}
|
||||||
|
{{partial "admin/templates/logs/site_customization_change_details"}}
|
||||||
|
{{/with}}
|
||||||
|
{{else}}
|
||||||
|
{{i18n admin.logs.staff_actions.no_previous}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class='btn btn-primary' {{action closeModal}}>{{i18n close}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -17,11 +17,15 @@
|
|||||||
<div class="col value created_at">{{unboundAgeWithTooltip created_at}}</div>
|
<div class="col value created_at">{{unboundAgeWithTooltip created_at}}</div>
|
||||||
<div class="col value details">
|
<div class="col value details">
|
||||||
{{{formattedDetails}}}
|
{{{formattedDetails}}}
|
||||||
{{#if showFullDetails}}
|
{{#if useModalForDetails}}
|
||||||
<a {{action toggleFullDetails this}}>{{i18n less}}</a><br/>
|
<a {{action showDetailsModal this}}>{{i18n admin.logs.staff_actions.show}}</a>
|
||||||
{{details}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<a {{action toggleFullDetails this}}>{{i18n more}}</a>
|
{{#if showFullDetails}}
|
||||||
|
<a {{action toggleFullDetails this}}>{{i18n less}}</a><br/>
|
||||||
|
{{details}}
|
||||||
|
{{else}}
|
||||||
|
<a {{action toggleFullDetails this}}>{{i18n more}}</a>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="col value context">{{context}}</div>
|
<div class="col value context">{{context}}</div>
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
A modal view for details of a staff action log record in a modal.
|
||||||
|
|
||||||
|
@class ChangeSiteCustomizationDetailsView
|
||||||
|
@extends Discourse.ModalBodyView
|
||||||
|
@namespace Discourse
|
||||||
|
@module Discourse
|
||||||
|
**/
|
||||||
|
Discourse.ChangeSiteCustomizationDetailsView = Discourse.ModalBodyView.extend({
|
||||||
|
templateName: 'admin/templates/logs/site_customization_change_modal',
|
||||||
|
title: I18n.t('admin.logs.staff_actions.modal_title')
|
||||||
|
});
|
@ -806,3 +806,9 @@ table {
|
|||||||
border-top: solid 1px #ddd;
|
border-top: solid 1px #ddd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-details-modal {
|
||||||
|
.modal-tab {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
}
|
@ -253,6 +253,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabbed-modal {
|
||||||
|
.modal-body {
|
||||||
|
position: relative;
|
||||||
|
height: 350px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modal-tab {
|
.modal-tab {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ class Admin::SiteCustomizationsController < Admin::AdminController
|
|||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @site_customization.save
|
if @site_customization.save
|
||||||
|
log_site_customization_change(nil, site_customization_params)
|
||||||
format.json { render json: @site_customization, status: :created}
|
format.json { render json: @site_customization, status: :created}
|
||||||
else
|
else
|
||||||
format.json { render json: @site_customization.errors, status: :unprocessable_entity }
|
format.json { render json: @site_customization.errors, status: :unprocessable_entity }
|
||||||
@ -23,11 +24,13 @@ class Admin::SiteCustomizationsController < Admin::AdminController
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
@site_customization = SiteCustomization.find(params[:id])
|
@site_customization = SiteCustomization.find(params[:id])
|
||||||
|
log_record = log_site_customization_change(@site_customization, site_customization_params)
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @site_customization.update_attributes(site_customization_params)
|
if @site_customization.update_attributes(site_customization_params)
|
||||||
format.json { head :no_content }
|
format.json { head :no_content }
|
||||||
else
|
else
|
||||||
|
log_record.destroy if log_record
|
||||||
format.json { render json: @site_customization.errors, status: :unprocessable_entity }
|
format.json { render json: @site_customization.errors, status: :unprocessable_entity }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -37,6 +40,8 @@ class Admin::SiteCustomizationsController < Admin::AdminController
|
|||||||
@site_customization = SiteCustomization.find(params[:id])
|
@site_customization = SiteCustomization.find(params[:id])
|
||||||
@site_customization.destroy
|
@site_customization.destroy
|
||||||
|
|
||||||
|
# TODO: log this
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json { head :no_content }
|
format.json { head :no_content }
|
||||||
end
|
end
|
||||||
@ -48,4 +53,8 @@ class Admin::SiteCustomizationsController < Admin::AdminController
|
|||||||
params.require(:site_customization).permit(:name, :stylesheet, :header, :position, :enabled, :key, :override_default_style, :stylesheet_baked)
|
params.require(:site_customization).permit(:name, :stylesheet, :header, :position, :enabled, :key, :override_default_style, :stylesheet_baked)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_site_customization_change(old_record, new_params)
|
||||||
|
StaffActionLogger.new(current_user).log_site_customization_change(old_record, new_params)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -9,7 +9,7 @@ class StaffActionLog < ActiveRecord::Base
|
|||||||
validates_presence_of :action
|
validates_presence_of :action
|
||||||
|
|
||||||
def self.actions
|
def self.actions
|
||||||
@actions ||= Enum.new(:delete_user, :change_trust_level, :change_site_setting)
|
@actions ||= Enum.new(:delete_user, :change_trust_level, :change_site_setting, :change_site_customization)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.with_filters(filters)
|
def self.with_filters(filters)
|
||||||
@ -25,6 +25,14 @@ class StaffActionLog < ActiveRecord::Base
|
|||||||
query = query.where("subject = ?", filters[:subject]) if filters[:subject]
|
query = query.where("subject = ?", filters[:subject]) if filters[:subject]
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_value_is_json?
|
||||||
|
action == StaffActionLog.actions[:change_site_customization]
|
||||||
|
end
|
||||||
|
|
||||||
|
def previous_value_is_json?
|
||||||
|
new_value_is_json?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
@ -15,4 +15,20 @@ class StaffActionLogSerializer < ApplicationSerializer
|
|||||||
def action_name
|
def action_name
|
||||||
StaffActionLog.actions.key(object.action).to_s
|
StaffActionLog.actions.key(object.action).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_value
|
||||||
|
if object.new_value
|
||||||
|
object.new_value_is_json? ? ::JSON.parse(object.new_value) : object.new_value
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def previous_value
|
||||||
|
if object.previous_value
|
||||||
|
object.previous_value_is_json? ? ::JSON.parse(object.previous_value) : object.previous_value
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
@ -37,6 +37,17 @@ class StaffActionLogger
|
|||||||
}))
|
}))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_site_customization_change(old_record, site_customization_params, opts={})
|
||||||
|
raise Discourse::InvalidParameters.new('site_customization_params is nil') unless site_customization_params
|
||||||
|
logged_attrs = ['stylesheet', 'header', 'position', 'enabled', 'key', 'override_default_style']
|
||||||
|
StaffActionLog.create( params(opts).merge({
|
||||||
|
action: StaffActionLog.actions[:change_site_customization],
|
||||||
|
subject: site_customization_params[:name],
|
||||||
|
previous_value: old_record ? old_record.attributes.slice(*logged_attrs).to_json : nil,
|
||||||
|
new_value: site_customization_params.slice(*(logged_attrs.map(&:to_sym))).to_json
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def params(opts)
|
def params(opts)
|
||||||
|
@ -100,6 +100,9 @@ en:
|
|||||||
read_more: 'read more'
|
read_more: 'read more'
|
||||||
more: "More"
|
more: "More"
|
||||||
less: "Less"
|
less: "Less"
|
||||||
|
character_count:
|
||||||
|
one: "{{count}} character"
|
||||||
|
other: "{{count}} characters"
|
||||||
|
|
||||||
in_n_seconds:
|
in_n_seconds:
|
||||||
one: "in 1 second"
|
one: "in 1 second"
|
||||||
@ -1196,10 +1199,15 @@ en:
|
|||||||
ip_address: "IP"
|
ip_address: "IP"
|
||||||
previous_value: "Previous"
|
previous_value: "Previous"
|
||||||
new_value: "New"
|
new_value: "New"
|
||||||
|
diff: "Diff"
|
||||||
|
show: "Show"
|
||||||
|
modal_title: "Details"
|
||||||
|
no_previous: "There is no previous value."
|
||||||
actions:
|
actions:
|
||||||
delete_user: "delete user"
|
delete_user: "delete user"
|
||||||
change_trust_level: "change trust level"
|
change_trust_level: "change trust level"
|
||||||
change_site_setting: "change site setting"
|
change_site_setting: "change site setting"
|
||||||
|
change_site_customization: "change site customization"
|
||||||
screened_emails:
|
screened_emails:
|
||||||
title: "Screened Emails"
|
title: "Screened Emails"
|
||||||
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
|
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
|
||||||
|
@ -34,6 +34,11 @@ describe Admin::SiteCustomizationsController do
|
|||||||
xhr :post, :create, site_customization: {name: 'my test name'}
|
xhr :post, :create, site_customization: {name: 'my test name'}
|
||||||
::JSON.parse(response.body).should be_present
|
::JSON.parse(response.body).should be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'logs the change' do
|
||||||
|
StaffActionLogger.any_instance.expects(:log_site_customization_change).once
|
||||||
|
xhr :post, :create, site_customization: {name: 'my test name'}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -73,4 +73,31 @@ describe StaffActionLogger do
|
|||||||
expect { logger.log_site_setting_change('title', 'Discourse', 'My Site') }.to change { StaffActionLog.count }.by(1)
|
expect { logger.log_site_setting_change('title', 'Discourse', 'My Site') }.to change { StaffActionLog.count }.by(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "log_site_customization_change" do
|
||||||
|
let(:valid_params) { {name: 'Cool Theme', stylesheet: "body {\n background-color: blue;\n}\n", header: "h1 {color: white;}"} }
|
||||||
|
|
||||||
|
it "raises an error when params are invalid" do
|
||||||
|
expect { logger.log_site_customization_change(nil, nil) }.to raise_error(Discourse::InvalidParameters)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs new site customizations" do
|
||||||
|
log_record = logger.log_site_customization_change(nil, valid_params)
|
||||||
|
log_record.subject.should == valid_params[:name]
|
||||||
|
log_record.previous_value.should be_nil
|
||||||
|
log_record.new_value.should be_present
|
||||||
|
json = ::JSON.parse(log_record.new_value)
|
||||||
|
json['stylesheet'].should be_present
|
||||||
|
json['header'].should be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs updated site customizations" do
|
||||||
|
existing = SiteCustomization.new(name: 'Banana', stylesheet: "body {color: yellow;}", header: "h1 {color: brown;}")
|
||||||
|
log_record = logger.log_site_customization_change(existing, valid_params)
|
||||||
|
log_record.previous_value.should be_present
|
||||||
|
json = ::JSON.parse(log_record.previous_value)
|
||||||
|
json['stylesheet'].should == existing.stylesheet
|
||||||
|
json['header'].should == existing.header
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user