Add a way to view staff action logs in admin

This commit is contained in:
Neil Lalonde 2013-08-07 16:04:12 -04:00
parent d2fb6ec53f
commit 5c8c52482a
16 changed files with 231 additions and 24 deletions

View file

@ -13,7 +13,6 @@ Discourse.AdminLogsBlockedEmailsController = Ember.ArrayController.extend(Discou
var self = this;
this.set('loading', true);
Discourse.BlockedEmail.findAll().then(function(result) {
console.log('findAll done');
self.set('content', result);
self.set('loading', false);
});

View file

@ -0,0 +1,24 @@
/**
This controller supports the interface for listing staff action logs in the admin section.
@class AdminLogsStaffActionLogsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
Discourse.AdminLogsStaffActionLogsController = Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
show: function() {
var self = this;
this.set('loading', true);
Discourse.StaffActionLog.findAll().then(function(result) {
self.set('content', result);
self.set('loading', false);
});
},
toggleFullDetails: function(target) {
target.set('showFullDetails', !target.get('showFullDetails'));
}
});

View file

@ -9,7 +9,7 @@
**/
Discourse.BlockedEmail = Discourse.Model.extend({
actionName: function() {
return I18n.t("admin.logs.actions." + this.get('action'));
return I18n.t("admin.logs.blocked_emails.actions." + this.get('action'));
}.property('action')
});
@ -18,9 +18,7 @@ Discourse.BlockedEmail.reopenClass({
return Discourse.ajax("/admin/logs/blocked_emails.json").then(function(blocked_emails) {
return blocked_emails.map(function(b) {
return Discourse.BlockedEmail.create(b);
})
});
});
}
});

View file

@ -0,0 +1,46 @@
/**
Represents an action taken by a staff member that has been logged.
@class StaffActionLog
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.StaffActionLog = Discourse.Model.extend({
showFullDetails: false,
actionName: function() {
return I18n.t("admin.logs.staff_actions.actions." + this.get('action_name'));
}.property('action_name'),
formattedDetails: function() {
var formatted = "";
if (this.get('email')) {
formatted += "<b>Email:</b> " + this.get('email') + "<br/>";
}
if (this.get('ip_address')) {
formatted += "<b>IP:</b> " + this.get('ip_address') + "<br/>";
}
return formatted;
}.property('ip_address', 'email')
});
Discourse.StaffActionLog.reopenClass({
create: function(attrs) {
if (attrs.staff_user) {
attrs.staff_user = Discourse.AdminUser.create(attrs.staff_user);
}
if (attrs.target_user) {
attrs.target_user = Discourse.AdminUser.create(attrs.target_user);
}
return this._super(attrs);
},
findAll: function(filter) {
return Discourse.ajax("/admin/logs/staff_action_logs.json").then(function(staff_actions) {
return staff_actions.map(function(s) {
return Discourse.StaffActionLog.create(s);
});
});
}
});

View file

@ -8,7 +8,7 @@
**/
Discourse.AdminLogsIndexRoute = Discourse.Route.extend({
redirect: function() {
this.transitionTo('adminLogs.blockedEmails');
this.transitionTo('adminLogs.staffActionLogs');
}
});
@ -21,10 +21,6 @@ Discourse.AdminLogsIndexRoute = Discourse.Route.extend({
@module Discourse
**/
Discourse.AdminLogsBlockedEmailsRoute = Discourse.Route.extend({
// model: function() {
// return Discourse.BlockedEmail.findAll();
// },
renderTemplate: function() {
this.render('admin/templates/logs/blocked_emails', {into: 'adminLogs'});
},
@ -33,3 +29,21 @@ Discourse.AdminLogsBlockedEmailsRoute = Discourse.Route.extend({
return this.controllerFor('adminLogsBlockedEmails').show();
}
});
/**
The route that lists staff actions that were logged.
@class AdminLogsStaffActionLogsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminLogsStaffActionLogsRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/logs/staff_action_logs', {into: 'adminLogs'});
},
setupController: function() {
return this.controllerFor('adminLogsStaffActionLogs').show();
}
});

View file

@ -31,6 +31,7 @@ Discourse.Route.buildRoutes(function() {
this.resource('adminLogs', { path: '/logs' }, function() {
this.route('blockedEmails', { path: '/blocked_emails' });
this.route('staffActionLogs', { path: '/staff_action_logs' });
});
this.route('groups', {path: '/groups'});

View file

@ -1,7 +1,8 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#linkTo 'adminLogs.blockedEmails'}}{{i18n admin.logs.blocked_emails}}{{/linkTo}}</li>
<li>{{#linkTo 'adminLogs.staffActionLogs'}}{{i18n admin.logs.staff_actions.title}}{{/linkTo}}</li>
<li>{{#linkTo 'adminLogs.blockedEmails'}}{{i18n admin.logs.blocked_emails.title}}{{/linkTo}}</li>
</ul>
</div>
</div>

View file

@ -4,10 +4,10 @@
{{#if model.length}}
<table class='table blocked-emails'>
<thead>
<th class="email">{{i18n admin.logs.email}}</th>
<th class="email">{{i18n admin.logs.blocked_emails.email}}</th>
<th class="action">{{i18n admin.logs.action}}</th>
<th class="match_count">{{i18n admin.logs.match_count}}</th>
<th class="last_match_at">{{i18n admin.logs.last_match_at}}</th>
<th class="match_count">{{i18n admin.logs.blocked_emails.match_count}}</th>
<th class="last_match_at">{{i18n admin.logs.blocked_emails.last_match_at}}</th>
<th class="created_at">{{i18n admin.logs.created_at}}</th>
</thead>

View file

@ -0,0 +1,50 @@
{{#if loading}}
<div class='admin-loading'>{{i18n loading}}</div>
{{else}}
{{#if model.length}}
<table class='table staff-actions'>
<thead>
<th class="action">{{i18n admin.logs.action}}</th>
<th class="staff_user">{{i18n admin.logs.staff_actions.staff_user}}</th>
<th class="target_user">{{i18n admin.logs.staff_actions.target_user}}</th>
<th class="context">{{i18n admin.logs.staff_actions.context}}</th>
<th class="created_at">{{i18n admin.logs.created_at}}</th>
<th class="details">{{i18n admin.logs.staff_actions.details}}</th>
</thead>
<tbody>
{{#each model}}
<tr>
<td class="action">{{actionName}}</td>
<td class="staff_user">
{{#linkTo 'adminUser' staff_user}}{{avatar staff_user imageSize="tiny"}}{{/linkTo}}
{{#linkTo 'adminUser' staff_user}}{{staff_user.username}}{{/linkTo}}
</td>
<td class="target_user">
{{#if target_user}}
{{#linkTo 'adminUser' target_user}}{{avatar target_user imageSize="tiny"}}{{/linkTo}}
{{#linkTo 'adminUser' target_user}}{{target_user.username}}{{/linkTo}}
{{else}}
&mdash;
{{/if}}
</td>
<td class="context">{{context}}</td>
<td class="created_at">{{unboundAgeWithTooltip created_at}}</td>
<td class="details">
{{{formattedDetails}}}
{{#if showFullDetails}}
{{details}}
<br/>
<a {{action toggleFullDetails this}}>Less</a>
{{else}}
<a {{action toggleFullDetails this}}>More</a>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
No results.
{{/if}}
{{/if}}

View file

@ -701,3 +701,21 @@ table {
text-align: center;
}
}
.staff-actions {
.action {
width: 120px;
}
.staff_user, .target_user {
white-space: nowrap;
}
.created_at {
text-align: center;
}
.details {
width: 500px;
a {
text-decoration: underline;
}
}
}

View file

@ -1,7 +1,7 @@
class Admin::BlockedEmailsController < Admin::AdminController
def index
blocked_emails = BlockedEmail.limit(50).order('created_at desc').to_a
blocked_emails = BlockedEmail.limit(50).order('last_match_at desc').to_a
render_serialized(blocked_emails, BlockedEmailSerializer)
end

View file

@ -0,0 +1,8 @@
class Admin::StaffActionLogsController < Admin::AdminController
def index
staff_actions = StaffActionLog.limit(50).order('created_at desc').to_a
render_serialized(staff_actions, StaffActionLogSerializer)
end
end

View file

@ -0,0 +1,15 @@
class StaffActionLogSerializer < ApplicationSerializer
attributes :action_name,
:details,
:context,
:ip_address,
:email,
:created_at
has_one :staff_user, serializer: BasicUserSerializer, embed: :objects
has_one :target_user, serializer: BasicUserSerializer, embed: :objects
def action_name
StaffActionLog.actions.key(object.action).to_s
end
end

View file

@ -1162,15 +1162,25 @@ en:
logs:
title: "Logs"
blocked_emails: "Blocked Emails"
email: "Email Address"
action: "Action"
last_match_at: "Last Matched"
match_count: "Matches"
created_at: "Created"
actions:
block: "block"
do_nothing: "do nothing"
blocked_emails:
title: "Blocked Emails"
email: "Email Address"
last_match_at: "Last Matched"
match_count: "Matches"
actions:
block: "block"
do_nothing: "do nothing"
staff_actions:
title: "Staff Actions"
staff_user: "Staff User"
target_user: "Target User"
context: "Context"
details: "Details"
actions:
delete_user: "delete user"
change_trust_level: "change trust level"
impersonate:
title: "Impersonate User"

View file

@ -63,7 +63,8 @@ Discourse::Application.routes.draw do
end
scope '/logs' do
resources :blocked_emails, only: [:index, :create, :update, :destroy]
resources :blocked_emails, only: [:index, :create, :update, :destroy]
resources :staff_action_logs, only: [:index, :create, :update, :destroy]
end
get 'customize' => 'site_customizations#index', constraints: AdminConstraint.new

View file

@ -0,0 +1,22 @@
require 'spec_helper'
describe Admin::StaffActionLogsController do
it "is a subclass of AdminController" do
(Admin::StaffActionLogsController < Admin::AdminController).should be_true
end
let!(:user) { log_in(:admin) }
context '.index' do
before do
xhr :get, :index
end
subject { response }
it { should be_success }
it 'returns JSON' do
::JSON.parse(subject.body).should be_a(Array)
end
end
end