151: Implemented pluggable filter UI.

153: Added Order By Category.
This commit is contained in:
Sipke Schoorstra
2014-07-01 16:34:29 -07:00
parent 3ec88e55be
commit c1f91d41b8
48 changed files with 819 additions and 121 deletions

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Web.Mvc;
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
using Orchard.AuditTrail.ViewModels;
using Orchard.Localization.Services;
using Orchard.Security;
@@ -27,20 +28,16 @@ namespace Orchard.AuditTrail.Controllers {
public dynamic New { get; private set; }
public ActionResult Index(PagerParameters pagerParameters, AuditTrailFilterViewModel filterParameters) {
public ActionResult Index(PagerParameters pagerParameters, AuditTrailOrderBy? orderBy = null) {
if(!_authorizer.Authorize(Permissions.ViewAuditTrail))
return new HttpUnauthorizedResult();
var pager = new Pager(_services.WorkContext.CurrentSite, pagerParameters);
var pageOfData = _auditTrailManager.GetRecords(pager.Page, pager.PageSize, new AuditTrailFilterParameters {
UserName = filterParameters.UserName,
FilterKey = filterParameters.FilterKey,
FilterValue = filterParameters.FilterValue,
From = _dateServices.ConvertFromLocalString(filterParameters.From.Date, filterParameters.From.Time),
To = _dateServices.ConvertFromLocalString(filterParameters.To.Date, filterParameters.To.Time),
}, filterParameters.OrderBy);
var filters = Filters.From(Request.QueryString);
var pageOfData = _auditTrailManager.GetRecords(pager.Page, pager.PageSize, filters, orderBy ?? AuditTrailOrderBy.DateDescending);
var pagerShape = New.Pager(pager).TotalItemCount(pageOfData.TotalItemCount);
var filterLayout = _auditTrailManager.BuildFilterDisplays(filters);
var eventDescriptorsQuery =
from c in _auditTrailManager.DescribeCategories()
from e in c.Events
@@ -60,7 +57,8 @@ namespace Orchard.AuditTrail.Controllers {
var viewModel = new AuditTrailViewModel {
Records = recordViewModelsQuery.ToArray(),
Pager = pagerShape,
Filter = filterParameters
OrderBy = orderBy ?? AuditTrailOrderBy.DateDescending,
FilterLayout = filterLayout
};
return View(viewModel);

View File

@@ -2,7 +2,9 @@
using System.Globalization;
using System.Linq;
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Providers.Content;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
using Orchard.AuditTrail.Settings;
using Orchard.AuditTrail.ViewModels;
using Orchard.ContentManagement;
@@ -45,10 +47,7 @@ namespace Orchard.AuditTrail.Drivers {
if (settings.ShowAuditTrail) {
results.Add(ContentShape("Parts_AuditTrail", () => {
var pager = new Pager(_services.WorkContext.CurrentSite, null, null);
var pageOfData = _auditTrailManager.GetRecords(pager.Page, pager.PageSize, new AuditTrailFilterParameters {
FilterKey = "content",
FilterValue = part.Id.ToString(CultureInfo.InvariantCulture)
});
var pageOfData = _auditTrailManager.GetRecords(pager.Page, pager.PageSize, ContentAuditTrailEventProvider.CreateFilters(part.Id));
var pagerShape = shapeHelper.Pager(pager).TotalItemCount(pageOfData.TotalItemCount);
var eventDescriptors = from c in _auditTrailManager.DescribeCategories()
from e in c.Events

View File

@@ -0,0 +1,21 @@
using System;
namespace Orchard.AuditTrail.Helpers {
public static class DateTimeHelper {
public static DateTime? Earliest(this DateTime? value) {
if (value == null)
return null;
var v = value.Value;
return new DateTime(v.Year, v.Month, v.Day, 0, 0, 0, 0, v.Kind);
}
public static DateTime? Latest(this DateTime? value) {
if (value == null)
return null;
var v = value.Value;
return new DateTime(v.Year, v.Month, v.Day, 23, 59, 59, 999, v.Kind);
}
}
}

View File

@@ -0,0 +1,12 @@
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.Helpers {
public static class FiltersHelper {
public static string Get(this Filters filters, string key) {
if (!filters.ContainsKey(key))
return null;
return filters[key];
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace Orchard.AuditTrail.Helpers {
public static class StringConversionHelper {
public static int? ToInt32(this string value) {
if (String.IsNullOrWhiteSpace(value))
return null;
int i;
if(!Int32.TryParse(value, out i))
return null;
return i;
}
}
}

View File

@@ -1,30 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Orchard.Localization;
namespace Orchard.AuditTrail.Models {
public class DescribeContext {
private readonly Dictionary<string, DescribeFor> _describes = new Dictionary<string, DescribeFor>();
public IEnumerable<AuditTrailCategoryDescriptor> Describe() {
var query =
from d in _describes.Values
select new AuditTrailCategoryDescriptor {
Category = d.Category,
Name = d.Name,
Events = d.Events
};
return query.ToArray();
}
public DescribeFor For(string category, LocalizedString name) {
DescribeFor describeFor;
if (!_describes.TryGetValue(category, out describeFor)) {
describeFor = new DescribeFor(category, name);
_describes[category] = describeFor;
}
return describeFor;
}
}
}

View File

@@ -80,6 +80,7 @@
<Content Include="Recipes\audit-trail.recipe.xml" />
<Content Include="Scripts\audit-trail-admin.js" />
<Content Include="Styles\admin.css" />
<Content Include="Styles\custom-grid.css" />
<Content Include="Styles\menu.audit-trail-admin.css" />
<Content Include="Styles\menu.audit-trail.png" />
<Content Include="Web.config" />
@@ -128,6 +129,10 @@
<Content Include="Views\AuditTrailEvent-User.cshtml" />
<Content Include="Views\EditorTemplates\Parts.AuditTrailSettings.cshtml" />
<Content Include="Views\AuditTrailEvent-User-LoginFailed..cshtml" />
<Content Include="Views\AuditTrailFilter-Common-User.cshtml" />
<Content Include="Views\AuditTrailFilter-ContentType.cshtml" />
<Content Include="Views\AuditTrailFilter-Common-Date.cshtml" />
<Content Include="Views\AuditTrailFilter-ContentItem.cshtml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
@@ -149,17 +154,26 @@
<Compile Include="Drivers\AuditTrailTrimmingSettingsPartDriver.cs" />
<Compile Include="Drivers\AuditTrailSettingsPartDriver.cs" />
<Compile Include="Handlers\AuditTrailTrimmingSettingsPartHandler.cs" />
<Compile Include="Helpers\FiltersHelper.cs" />
<Compile Include="Helpers\DateTimeHelper.cs" />
<Compile Include="Helpers\StringConversionHelper.cs" />
<Compile Include="ImportExport\AuditTrailExportStep.cs" />
<Compile Include="ImportExport\AuditTrailExportHandler.cs" />
<Compile Include="ImportExport\AuditTrailImportHandler.cs" />
<Compile Include="Models\AuditTrailEventRecordResult.cs" />
<Compile Include="Models\AuditTrailTrimmingSettingsPart.cs" />
<Compile Include="Services\CommonAuditTrailEventHandler.cs" />
<Compile Include="Services\AuditTrailEventHandlerBase.cs" />
<Compile Include="Services\Models\DisplayFilterContext.cs" />
<Compile Include="Services\Models\QueryFilterContext.cs" />
<Compile Include="Providers\Content\ContentAuditTrailEventShapes.cs" />
<Compile Include="Providers\Content\DiffGramAnalyzer.cs" />
<Compile Include="Providers\Content\DiffNode.cs" />
<Compile Include="Providers\Content\DiffType.cs" />
<Compile Include="Services\Models\Filters.cs" />
<Compile Include="Providers\Content\IDiffGramAnalyzer.cs" />
<Compile Include="Services\AuditTrailTrimmingBackgroundTask.cs" />
<Compile Include="Shapes\AuditTrailShapes.cs" />
<Compile Include="ViewModels\AuditTrailCategorySettingsViewModel.cs" />
<Compile Include="ViewModels\AuditTrailEventSettingsViewModel.cs" />
<Compile Include="ViewModels\AuditTrailTrimmingSettingsViewModel.cs" />
@@ -175,7 +189,7 @@
<Compile Include="Providers\ContentDefinition\IContentDefinitionEventHandler.cs" />
<Compile Include="Providers\Content\AuditTrailEventHandler.cs" />
<Compile Include="Helpers\EventDataHelper.cs" />
<Compile Include="Models\AuditTrailFilterParameters.cs" />
<Compile Include="Services\Models\AuditTrailFilterParameters.cs" />
<Compile Include="Providers\Role\IRoleEventHandler.cs" />
<Compile Include="Providers\Role\RoleEventHandler.cs" />
<Compile Include="Providers\Role\RoleAuditTrailEventProvider.cs" />
@@ -194,18 +208,18 @@
<Compile Include="Services\IEventDataSerializer.cs" />
<Compile Include="ViewModels\AuditTrailDetailsViewModel.cs" />
<Compile Include="Providers\Content\GlobalContentHandler.cs" />
<Compile Include="Models\AuditTrailEventDescriptor.cs" />
<Compile Include="Services\Models\AuditTrailEventDescriptor.cs" />
<Compile Include="Services\Models\AuditTrailCategoryDescriptor.cs" />
<Compile Include="Models\DescribeContext.cs" />
<Compile Include="Models\DescribeFor.cs" />
<Compile Include="Models\AuditTrailContext.cs" />
<Compile Include="Models\AuditTrailCreateContext.cs" />
<Compile Include="Services\Models\DescribeContext.cs" />
<Compile Include="Services\Models\DescribeFor.cs" />
<Compile Include="Services\Models\AuditTrailContext.cs" />
<Compile Include="Services\Models\AuditTrailCreateContext.cs" />
<Compile Include="Services\AuditTrailEventProviderBase.cs" />
<Compile Include="Providers\Content\ContentAuditTrailEventProvider.cs" />
<Compile Include="Services\IAuditTrailEventHandler.cs" />
<Compile Include="Services\IAuditTrailEventProvider.cs" />
<Compile Include="ViewModels\AuditTrailFilterViewModel.cs" />
<Compile Include="Models\AuditTrailOrderBy.cs" />
<Compile Include="ViewModels\CommonAuditTrailFilterViewModel.cs" />
<Compile Include="Services\Models\AuditTrailOrderBy.cs" />
<Compile Include="ViewModels\AuditTrailViewModel.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Models\AuditTrailEventRecord.cs" />

View File

@@ -1,10 +1,11 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
using Orchard.ContentManagement;
namespace Orchard.AuditTrail.Providers.Content {
public class AuditTrailEventHandler : IAuditTrailEventHandler {
public void Create(AuditTrailCreateContext context) {
public class ContentAuditTrailEventHandler : AuditTrailEventHandlerBase {
public override void Create(AuditTrailCreateContext context) {
var content = context.Properties.ContainsKey("Content") ? (IContent)context.Properties["Content"] : default(IContent);
var auditTrailPart = content != null ? content.As<AuditTrailPart>() : default(AuditTrailPart);

View File

@@ -1,14 +1,29 @@
using Orchard.AuditTrail.Models;
using System.Globalization;
using System.Linq;
using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
using Orchard.ContentManagement;
namespace Orchard.AuditTrail.Providers.Content {
public class ContentAuditTrailEventProvider : AuditTrailEventProviderBase {
private readonly IContentManager _contentManager;
public ContentAuditTrailEventProvider(IContentManager contentManager) {
_contentManager = contentManager;
}
public const string Created = "Created";
public const string Saved = "Saved";
public const string Published = "Published";
public const string Unpublished = "Unpublished";
public const string Removed = "Removed";
public static Filters CreateFilters(int contentId) {
return new Filters {
{"content", contentId.ToString(CultureInfo.InvariantCulture)}
};
}
public override void Describe(DescribeContext context) {
context.For("Content", T("Content"))
.Event(this, Created, T("Created"), T("Content was created."), enableByDefault: true)
@@ -16,6 +31,25 @@ namespace Orchard.AuditTrail.Providers.Content {
.Event(this, Published, T("Published"), T("Content was published."), enableByDefault: true)
.Event(this, Unpublished, T("Unpublished"), T("Content was unpublished."), enableByDefault: true)
.Event(this, Removed, T("Removed"), T("Content was deleted."), enableByDefault: true);
context.QueryFilter(QueryFilter);
context.DisplayFilter(DisplayFilter);
}
private void QueryFilter(QueryFilterContext context) {
if (!context.Filters.ContainsKey("content"))
return;
var contentId = context.Filters["content"].ToInt32();
context.Query = context.Query.Where(x => x.EventFilterKey == "content" && x.EventFilterData == contentId.ToString());
}
private void DisplayFilter(DisplayFilterContext context) {
var contentItemId = context.Filters.Get("content").ToInt32();
var contentItem = contentItemId != null ? _contentManager.Get(contentItemId.Value, VersionOptions.Latest) : default(ContentItem);
var filterDisplay = context.ShapeFactory.AuditTrailFilter__ContentItem(ContentItem: contentItem);
context.FilterLayout.TripleSecond.Add(filterDisplay);
}
}
}

View File

@@ -56,7 +56,13 @@ namespace Orchard.AuditTrail.Providers.Content {
eventData["PreviousContentItemVersionId"] = previousContentItemVersion.Id;
}
_auditTrailManager.CreateRecord<ContentAuditTrailEventProvider>(eventName, _wca.GetContext().CurrentUser, properties, eventData, eventFilterKey: "content", eventFilterData: content.Id.ToString(CultureInfo.InvariantCulture));
_auditTrailManager.CreateRecord<ContentAuditTrailEventProvider>(
eventName,
_wca.GetContext().CurrentUser,
properties,
eventData,
eventFilterKey: "content",
eventFilterData: content.Id.ToString(CultureInfo.InvariantCulture));
}
}
}

View File

@@ -1,5 +1,6 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.Providers.ContentDefinition {
public class ContentPartAuditTrailEventProvider : AuditTrailEventProviderBase {

View File

@@ -1,8 +1,18 @@
using Orchard.AuditTrail.Models;
using System;
using System.Linq;
using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
using Orchard.ContentManagement.MetaData;
namespace Orchard.AuditTrail.Providers.ContentDefinition {
public class ContentTypeAuditTrailEventProvider : AuditTrailEventProviderBase {
private readonly IContentDefinitionManager _contentDefinitionManager;
public ContentTypeAuditTrailEventProvider(IContentDefinitionManager contentDefinitionManager) {
_contentDefinitionManager = contentDefinitionManager;
}
public const string Created = "Created";
public const string Removed = "Removed";
public const string PartAdded = "PartAdded";
@@ -18,6 +28,26 @@ namespace Orchard.AuditTrail.Providers.ContentDefinition {
.Event(this, PartRemoved, T("Part removed"), T("Content Part was removed."), enableByDefault: true)
.Event(this, TypeSettingsUpdated, T("Type Settings updated"), T("Content Type settings were updated."), enableByDefault: true)
.Event(this, PartSettingsUpdated, T("Part Settings updated"), T("Content Part settings were updated."), enableByDefault: true);
context.QueryFilter(QueryFilter);
context.DisplayFilter(DisplayFilter);
}
private void QueryFilter(QueryFilterContext context) {
var contentType = context.Filters.Get("contenttype");
if(String.IsNullOrWhiteSpace(contentType))
return;
context.Query = context.Query.Where(x => x.EventFilterKey == "contenttype" && x.EventFilterData == contentType);
}
private void DisplayFilter(DisplayFilterContext context) {
var filterDisplay = context.ShapeFactory.AuditTrailFilter__ContentType(
ContentType: context.Filters.Get("contenttype"),
ContentTypes: _contentDefinitionManager.ListTypeDefinitions().OrderBy(x => x.DisplayName).ToArray());
context.FilterLayout.TripleFirst.Add(filterDisplay);
}
}
}

View File

@@ -1,5 +1,6 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.Providers.Role {
public class RoleAuditTrailEventProvider : AuditTrailEventProviderBase {

View File

@@ -1,5 +1,6 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services;
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.Providers.User {
public class UserAuditTrailEventProvider : AuditTrailEventProviderBase {

View File

@@ -0,0 +1,9 @@
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.Services {
public class AuditTrailEventHandlerBase : Component, IAuditTrailEventHandler {
public virtual void Create(AuditTrailCreateContext context) {}
public virtual void Filter(QueryFilterContext context) {}
public virtual void DisplayFilter(DisplayFilterContext context) { }
}
}

View File

@@ -1,4 +1,4 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.Services {
public abstract class AuditTrailEventProviderBase : Component, IAuditTrailEventProvider {

View File

@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services.Models;
using Orchard.Caching;
using Orchard.Collections;
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.DisplayManagement;
using Orchard.Security;
using Orchard.Services;
using Orchard.Settings;
@@ -22,6 +23,7 @@ namespace Orchard.AuditTrail.Services {
private readonly ICacheManager _cacheManager;
private readonly ISiteService _siteService;
private readonly ISignals _signals;
private readonly IShapeFactory _shapeFactory;
public AuditTrailManager(
IRepository<AuditTrailEventRecord> auditTrailRepository,
@@ -31,7 +33,8 @@ namespace Orchard.AuditTrail.Services {
IEventDataSerializer serializer,
ICacheManager cacheManager,
ISiteService siteService,
ISignals signals) {
ISignals signals,
IShapeFactory shapeFactory) {
_auditTrailRepository = auditTrailRepository;
_providers = providers;
@@ -41,24 +44,40 @@ namespace Orchard.AuditTrail.Services {
_cacheManager = cacheManager;
_siteService = siteService;
_signals = signals;
_shapeFactory = shapeFactory;
}
public IPageOfItems<AuditTrailEventRecord> GetRecords(int page, int pageSize, AuditTrailFilterParameters filter = null, AuditTrailOrderBy orderBy = AuditTrailOrderBy.DateDescending) {
public IPageOfItems<AuditTrailEventRecord> GetRecords(
int page,
int pageSize,
Filters filters = null,
AuditTrailOrderBy orderBy = AuditTrailOrderBy.DateDescending) {
var query = _auditTrailRepository.Table;
if (filter != null) {
if (!String.IsNullOrWhiteSpace(filter.FilterKey)) query = query.Where(x => x.EventFilterKey == filter.FilterKey);
if (!String.IsNullOrWhiteSpace(filter.FilterValue)) query = query.Where(x => x.EventFilterData == filter.FilterValue);
if (!String.IsNullOrWhiteSpace(filter.UserName)) query = query.Where(x => x.UserName == filter.UserName);
if (filter.From != null) query = query.Where(x => x.CreatedUtc >= filter.From);
if (filter.To != null) query = query.Where(x => x.CreatedUtc <= filter.To);
if (filters != null) {
var filterContext = new QueryFilterContext(query, filters);
// Invoke event handlers.
_auditTrailEventHandlers.Filter(filterContext);
// Give each provider a chance to modify the query.
var providersContext = DescribeProviders();
foreach (var queryFilter in providersContext.QueryFilters) {
queryFilter(filterContext);
}
query = filterContext.Query;
}
switch (orderBy) {
case AuditTrailOrderBy.EventAscending:
query = query.OrderBy(x => x.Event).ThenByDescending(x => x.Id);
break;
case AuditTrailOrderBy.CategoryAscending:
query = query.OrderBy(x => x.Category).ThenByDescending(x => x.Id);
break;
default:
query = query.OrderByDescending(x => x.CreatedUtc).ThenByDescending(x => x.Id);
break;
@@ -79,6 +98,27 @@ namespace Orchard.AuditTrail.Services {
return _auditTrailRepository.Get(id);
}
public dynamic BuildFilterDisplays(Filters filters) {
var layout = (dynamic)_shapeFactory.Create("AuditTrailFilters", Arguments.From(new {
TripleFirst = _shapeFactory.Create("AuditTrailFilters_TripleFirst"),
TripleSecond = _shapeFactory.Create("AuditTrailFilters_TripleSecond"),
TripleThird = _shapeFactory.Create("AuditTrailFilters_TripleThird")
}));
var displayContext = new DisplayFilterContext(_shapeFactory, filters, layout);
// Invoke event handlers.
_auditTrailEventHandlers.DisplayFilter(displayContext);
// Give each provider a chance to provide a filter display.
var providersContext = DescribeProviders();
foreach (var action in providersContext.FilterDisplays) {
action(displayContext);
}
return layout;
}
public AuditTrailEventRecordResult CreateRecord<T>(string eventName, IUser user, IDictionary<string, object> properties = null, IDictionary<string, object> eventData = null, string eventFilterKey = null, string eventFilterData = null) where T:IAuditTrailEventProvider {
var eventDescriptor = DescribeEvent<T>(eventName);
if(!IsEventEnabled(eventDescriptor))
@@ -129,9 +169,14 @@ namespace Orchard.AuditTrail.Services {
}
public IEnumerable<AuditTrailCategoryDescriptor> DescribeCategories() {
var context = DescribeProviders();
return context.Describe();
}
public DescribeContext DescribeProviders() {
var context = new DescribeContext();
_providers.Describe(context);
return context.Describe();
return context;
}
public AuditTrailEventDescriptor DescribeEvent<T>(string eventName) where T:IAuditTrailEventProvider {

View File

@@ -0,0 +1,50 @@
using System;
using System.Linq;
using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Services.Models;
using Orchard.Core.Common.ViewModels;
using Orchard.Localization.Services;
namespace Orchard.AuditTrail.Services {
public class CommonAuditTrailEventHandler : AuditTrailEventHandlerBase {
private readonly Lazy<IAuditTrailManager> _auditTrailManager;
private readonly IDateServices _dateServices;
public CommonAuditTrailEventHandler(Lazy<IAuditTrailManager> auditTrailManager, IDateServices dateServices) {
_auditTrailManager = auditTrailManager;
_dateServices = dateServices;
}
public override void Filter(QueryFilterContext context) {
// Common filters (username, from and to).
var userName = context.Filters.Get("username");
var fromDate = context.Filters.Get("from.Date");
var fromTime = context.Filters.Get("from.Time");
var toDate = context.Filters.Get("to.Date");
var toTime = context.Filters.Get("to.Time");
var from = _dateServices.ConvertFromLocalString(fromDate, fromTime).Earliest();
var to = _dateServices.ConvertFromLocalString(toDate, toTime).Latest();
var query = context.Query;
if (!String.IsNullOrWhiteSpace(userName)) query = query.Where(x => x.UserName == userName);
if (from != null) query = query.Where(x => x.CreatedUtc >= from);
if (to != null) query = query.Where(x => x.CreatedUtc <= to);
context.Query = query;
}
public override void DisplayFilter(DisplayFilterContext context) {
// Common filters (username, from and to).
var userName = context.Filters.Get("username");
var fromDate = context.Filters.Get("from.Date");
var toDate = context.Filters.Get("to.Date");
var userNameFilterDisplay = context.ShapeFactory.AuditTrailFilter__Common__User(UserName: userName);
var dateFilterDisplay = context.ShapeFactory.AuditTrailFilter__Common__Date(
From: new DateTimeEditor {Date = fromDate, ShowDate = true},
To: new DateTimeEditor {Date = toDate, ShowDate = true});
context.FilterLayout.TripleFirst.Add(dateFilterDisplay);
context.FilterLayout.TripleSecond.Add(userNameFilterDisplay);
}
}
}

View File

@@ -1,8 +1,10 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services.Models;
using Orchard.Events;
namespace Orchard.AuditTrail.Services {
public interface IAuditTrailEventHandler : IEventHandler {
void Create(AuditTrailCreateContext context);
void Filter(QueryFilterContext context);
void DisplayFilter(DisplayFilterContext context);
}
}

View File

@@ -1,4 +1,5 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services.Models;
using Orchard.Events;
namespace Orchard.AuditTrail.Services {

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services.Models;
using Orchard.Collections;
using Orchard.Security;
@@ -12,9 +13,9 @@ namespace Orchard.AuditTrail.Services {
/// <param name="page">The page number to get records from.</param>
/// <param name="pageSize">The number of records to get.</param>
/// <param name="orderBy">The value to order by.</param>
/// <param name="filter">Optional. An object to filter the records on.</param>
/// <param name="filters">Optional. An object to filter the records on.</param>
/// <returns>Returns a page of event records.</returns>
IPageOfItems<AuditTrailEventRecord> GetRecords(int page, int pageSize, AuditTrailFilterParameters filter = null, AuditTrailOrderBy orderBy = AuditTrailOrderBy.DateDescending);
IPageOfItems<AuditTrailEventRecord> GetRecords(int page, int pageSize, Filters filters = null, AuditTrailOrderBy orderBy = AuditTrailOrderBy.DateDescending);
/// <summary>
/// Returns a single event record by ID.
@@ -22,6 +23,13 @@ namespace Orchard.AuditTrail.Services {
/// <param name="id">The event record ID.</param>
/// <returns>Returns a single event record by ID.</returns>
AuditTrailEventRecord GetRecord(int id);
/// <summary>
/// Builds a shape tree of filter displays.
/// </summary>
/// <param name="filters">Input for each filter builder.</param>
/// <returns>Returns a tree of shapes.</returns>
dynamic BuildFilterDisplays(Filters filters);
/// <summary>
/// Records an audit trail event.
@@ -42,6 +50,11 @@ namespace Orchard.AuditTrail.Services {
/// <returns>Returns a list of audit trail category descriptors.</returns>
IEnumerable<AuditTrailCategoryDescriptor> DescribeCategories();
/// <summary>
/// Describes all audit trail event providers.
/// </summary>
DescribeContext DescribeProviders();
/// <summary>
/// Describes a single audit trail event.
/// </summary>
@@ -62,5 +75,6 @@ namespace Orchard.AuditTrail.Services {
/// </summary>
/// <returns>Returns the deleted records.</returns>
IEnumerable<AuditTrailEventRecord> Trim(TimeSpan threshold);
}
}

View File

@@ -1,7 +1,8 @@
using System.Collections.Generic;
using Orchard.AuditTrail.Models;
using Orchard.Localization;
namespace Orchard.AuditTrail.Models {
namespace Orchard.AuditTrail.Services.Models {
public class AuditTrailCategoryDescriptor {
public string Category { get; set; }
public LocalizedString Name { get; set; }

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Orchard.Security;
namespace Orchard.AuditTrail.Models {
namespace Orchard.AuditTrail.Services.Models {
public class AuditTrailContext {
public AuditTrailContext() {
EventData = new Dictionary<string, object>();

View File

@@ -1,4 +1,4 @@
namespace Orchard.AuditTrail.Models {
namespace Orchard.AuditTrail.Services.Models {
public class AuditTrailCreateContext : AuditTrailContext {
public string Comment { get; set; }
}

View File

@@ -1,6 +1,6 @@
using Orchard.Localization;
namespace Orchard.AuditTrail.Models {
namespace Orchard.AuditTrail.Services.Models {
public class AuditTrailEventDescriptor {
public AuditTrailCategoryDescriptor CategoryDescriptor { get; set; }
public string Event { get; set; }

View File

@@ -1,9 +1,9 @@
using System;
using Orchard.AuditTrail.Models;
namespace Orchard.AuditTrail.Models {
namespace Orchard.AuditTrail.Services.Models {
public class AuditTrailFilterParameters {
public string FilterKey { get; set; }
public string FilterValue { get; set; }
public Filters Filters { get; set; }
public string UserName { get; set; }
public DateTime? From { get; set; }
public DateTime? To { get; set; }

View File

@@ -1,6 +1,7 @@
namespace Orchard.AuditTrail.Models {
namespace Orchard.AuditTrail.Services.Models {
public enum AuditTrailOrderBy {
DateDescending,
CategoryAscending,
EventAscending
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Localization;
namespace Orchard.AuditTrail.Services.Models {
public class DescribeContext {
private readonly IDictionary<string, DescribeFor> _describes = new Dictionary<string, DescribeFor>();
private readonly IList<Action<QueryFilterContext>> _queryFilters = new List<Action<QueryFilterContext>>();
private readonly IList<Action<DisplayFilterContext>> _filterDisplays = new List<Action<DisplayFilterContext>>();
public IEnumerable<Action<QueryFilterContext>> QueryFilters {
get { return _queryFilters; }
}
public IEnumerable<Action<DisplayFilterContext>> FilterDisplays {
get { return _filterDisplays; }
}
public IEnumerable<AuditTrailCategoryDescriptor> Describe() {
var query =
from d in _describes.Values
select new AuditTrailCategoryDescriptor {
Category = d.Category,
Name = d.Name,
Events = d.Events
};
return query.ToArray();
}
public DescribeFor For(string category, LocalizedString name) {
DescribeFor describeFor;
if (!_describes.TryGetValue(category, out describeFor)) {
describeFor = new DescribeFor(category, name);
_describes[category] = describeFor;
}
return describeFor;
}
public DescribeContext QueryFilter(Action<QueryFilterContext> queryAction) {
_queryFilters.Add(queryAction);
return this;
}
public DescribeContext DisplayFilter(Action<DisplayFilterContext> displayFilter) {
_filterDisplays.Add(displayFilter);
return this;
}
}
}

View File

@@ -1,9 +1,8 @@
using System.Collections.Generic;
using Orchard.AuditTrail.Helpers;
using Orchard.AuditTrail.Services;
using Orchard.Localization;
namespace Orchard.AuditTrail.Models {
namespace Orchard.AuditTrail.Services.Models {
public class DescribeFor {
private readonly IList<AuditTrailEventDescriptor> _events = new List<AuditTrailEventDescriptor>();

View File

@@ -0,0 +1,15 @@
using Orchard.DisplayManagement;
namespace Orchard.AuditTrail.Services.Models {
public class DisplayFilterContext {
public DisplayFilterContext(IShapeFactory shapeFactory, Filters filters, dynamic filterLayout) {
ShapeFactory = shapeFactory;
Filters = filters;
FilterLayout = filterLayout;
}
public dynamic ShapeFactory { get; set; }
public Filters Filters { get; set; }
public dynamic FilterLayout { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Collections.Specialized;
namespace Orchard.AuditTrail.Services.Models {
public class Filters : Dictionary<string, string> {
public static Filters From(NameValueCollection nameValues) {
var filters = new Filters();
foreach (string nameValue in nameValues) {
filters.Add(nameValue, nameValues[nameValue]);
}
return filters;
}
public Filters AddFilter(string key, string value) {
Add(key, value);
return this;
}
}
}

View File

@@ -0,0 +1,14 @@
using System.Linq;
using Orchard.AuditTrail.Models;
namespace Orchard.AuditTrail.Services.Models {
public class QueryFilterContext {
public QueryFilterContext(IQueryable<AuditTrailEventRecord> query, Filters filters) {
Query = query;
Filters = filters;
}
public IQueryable<AuditTrailEventRecord> Query { get; set; }
public Filters Filters { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
using System.IO;
using Orchard.DisplayManagement;
namespace Orchard.AuditTrail.Shapes {
public class AuditTrailShapes : IDependency {
[Shape]
public void AuditTrailFilters(dynamic Shape, dynamic Display, TextWriter Output) {
DispayChildren(Shape, Display, Output);
}
[Shape]
public void AuditTrailFilters_TripleFirst(dynamic Shape, dynamic Display, TextWriter Output) {
DispayChildren(Shape, Display, Output);
}
[Shape]
public void AuditTrailFilters_TripleSecond(dynamic Shape, dynamic Display, TextWriter Output) {
DispayChildren(Shape, Display, Output);
}
[Shape]
public void AuditTrailFilters_TripleThird(dynamic Shape, dynamic Display, TextWriter Output) {
DispayChildren(Shape, Display, Output);
}
private void DispayChildren(dynamic shape, dynamic display, TextWriter output) {
foreach (var child in shape) {
output.Write(display(child));
}
}
}
}

View File

@@ -2,6 +2,16 @@
margin-bottom: 1em;
}
.audit-trail-filter fieldset label {
display: inline-block;
width: 10em;
}
.audit-trail-filter fieldset label.inline {
display: inline;
width: auto;
}
.audit-trail-list .info {
line-height: 25px;
}
@@ -20,4 +30,4 @@
.audit-trail-site-settings table th.event-enabled {
width: 60px;
}
}

View File

@@ -0,0 +1,285 @@
.table, .row, .cell {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.table > .row {
width: 100%;
}
.table.fixed > .row {
margin-left: auto;
margin-right: auto;
}
.row {
display: block;
margin: 0 0 20px 0;
}
.row:after {
content: "";
display: table;
clear: both;
}
.cell {
display: block;
float: left;
padding-right: 10px;
padding-left: 10px;
}
.row > .cell:last-of-type {
padding-right: 0;
}
.row > .cell:first-of-type {
padding-left: 0;
}
/* Opt-in outside padding */
.row-pad {
padding: 20px 0 20px 20px;
}
.row-pad .cell:last-of-type {
padding-right: 20px;
}
.span-1-1 {
width: 100%;
}
.span-1-2 {
width: 50%;
}
.span-1-3 {
width: 33.33%;
}
.span-1-4 {
width: 25%;
}
.span-1-5 {
width: 20%;
}
.span-1-6 {
width: 16.66%;
}
.span-1-7 {
width: 14.28%;
}
.span-1-8 {
width: 12.5%;
}
.span-1-9 {
width: 11.11%;
}
.span-1-10 {
width: 10%;
}
.span-1-11 {
width: 9.09%;
}
.span-1-12 {
width: 8.33%;
}
.span-2-3 {
width: 66.66%;
}
.span-2-4 {
width: 50%;
}
.span-2-5 {
width: 40%;
}
.span-2-6 {
width: 33.33%;
}
.span-2-8 {
width: 25%;
}
.span-2-10 {
width: 20%;
}
.span-2-12 {
width: 16.66%;
}
.span-3-4 {
width: 75%;
}
.span-3-5 {
width: 60%;
}
.span-3-6 {
width: 50%;
}
.span-3-8 {
width: 37.5%;
}
.span-3-10 {
width: 33.33%;
}
.span-3-12 {
width: 25%;
}
.span-4-5 {
width: 80%;
}
.span-4-6 {
width: 66.66%;
}
.span-4-8 {
width: 50%;
}
.span-4-10 {
width: 40%;
}
.span-4-12 {
width: 33.33%;
}
.span-5-6 {
width: 83.33%;
}
.span-5-8 {
width: 75%;
}
.span-5-10 {
width: 50%;
}
.span-5-12 {
width: 41.66%;
}
.span-6-8 {
width: 75%;
}
.span-6-10 {
width: 60%;
}
.span-6-12 {
width: 50%;
}
.span-7-8 {
width: 87.5%;
}
.span-7-10 {
width: 70%;
}
.span-7-12 {
width: 58.33%;
}
.span-8-10 {
width: 80%;
}
.span-8-12 {
width: 66.66%;
}
.span-9-10 {
width: 90%;
}
.span-9-12 {
width: 75%;
}
.span-10-12 {
width: 83.33%;
}
.span-11-12 {
width: 91.66%;
}
/* Bootstrap compatible spans */
.span-1 { width: 8.33%; }
.span-2 { width: 16.66%; }
.span-3 { width: 25%; }
.span-4 { width: 33.33%; }
.span-5 { width: 41.66%; }
.span-6 { width: 50%; }
.span-7 { width: 58.33%; }
.span-8 { width: 66.66%; }
.span-9 { width: 75%; }
.span-10 { width: 83.33%; }
.span-11 { width: 91.66%; }
.span-12 { width: 100%; }
/* RESPONSIVENESS */
/* Large desktop */
@media (min-width: 1200px) {
.table.fixed > .row {
width: 1170px;
}
}
/* Default */
@media (min-width: 980px) and (max-width: 1199px) {
.table.fixed > .row {
width: 960px;
}
}
/* Portrait tablet to landscape and desktop */
@media (min-width: 768px) and (max-width: 979px) {
.table.fixed > .row {
width: 724px;
}
}
/* Landscape phone to portrait tablet */
@media (max-width: 767px) {
.table.fixed > .row {
width: 100%;
}
}
/* Landscape phones and down */
@media (max-width: 480px) {
.table.fixed > .row {
width: 100%;
}
}

View File

@@ -1,4 +1,5 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.ViewModels {
public class AuditTrailDetailsViewModel {

View File

@@ -1,4 +1,5 @@
using Orchard.AuditTrail.Models;
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.ViewModels {
public class AuditTrailEventSummaryViewModel {

View File

@@ -1,12 +1,10 @@
using System.Collections.Generic;
using Orchard.AuditTrail.Services.Models;
namespace Orchard.AuditTrail.ViewModels {
public class AuditTrailViewModel {
public AuditTrailViewModel() {
Filter = new AuditTrailFilterViewModel();
}
public AuditTrailFilterViewModel Filter { get; set; }
public dynamic FilterLayout { get; set; }
public AuditTrailOrderBy OrderBy { get; set; }
public IEnumerable<AuditTrailEventSummaryViewModel> Records { get; set; }
public dynamic List { get; set; }
public dynamic Pager { get; set; }

View File

@@ -1,17 +1,14 @@
using Orchard.AuditTrail.Models;
using Orchard.Core.Common.ViewModels;
using Orchard.Core.Common.ViewModels;
namespace Orchard.AuditTrail.ViewModels {
public class AuditTrailFilterViewModel {
public AuditTrailFilterViewModel() {
public class CommonAuditTrailFilterViewModel {
public CommonAuditTrailFilterViewModel() {
From = new DateTimeEditor { ShowDate = true, ShowTime = false};
To = new DateTimeEditor { ShowDate = true, ShowTime = false };
}
public string FilterKey { get; set; }
public string FilterValue { get; set; }
public string UserName { get; set; }
public DateTimeEditor From { get; set; }
public DateTimeEditor To { get; set; }
public AuditTrailOrderBy OrderBy { get; set; }
}
}

View File

@@ -1,36 +1,47 @@
@using Orchard.AuditTrail.Models
@using Orchard.AuditTrail.Services.Models
@model Orchard.AuditTrail.ViewModels.AuditTrailViewModel
@{
Style.Include("custom-grid.css");
Style.Include("admin.css");
var from = Model.Filter.From;
var to = Model.Filter.To;
var orderBy = Model.Filter.OrderBy;
var orderBy = Model.OrderBy;
var orderByItems = new List<SelectListItem> {
new SelectListItem {Text = T("Date (desc)").Text, Value = AuditTrailOrderBy.DateDescending.ToString(), Selected = Model.Filter.OrderBy == AuditTrailOrderBy.DateDescending},
new SelectListItem {Text = T("Event (asc)").Text, Value = AuditTrailOrderBy.EventAscending.ToString(), Selected = Model.Filter.OrderBy == AuditTrailOrderBy.EventAscending},
new SelectListItem {Text = T("Date (desc)").Text, Value = AuditTrailOrderBy.DateDescending.ToString(), Selected = Model.OrderBy == AuditTrailOrderBy.DateDescending},
new SelectListItem {Text = T("Category (asc)").Text, Value = AuditTrailOrderBy.CategoryAscending.ToString(), Selected = Model.OrderBy == AuditTrailOrderBy.CategoryAscending},
new SelectListItem {Text = T("Event (asc)").Text, Value = AuditTrailOrderBy.EventAscending.ToString(), Selected = Model.OrderBy == AuditTrailOrderBy.EventAscending},
};
Layout.Title = T("Audit Trail");
}
<section class="audit-trail-filter">
@using (Html.BeginForm("Index", "Admin", new { area = "Orchard.AuditTrail" }, FormMethod.Get)) {
<fieldset class="bulk-actions">
@Html.Hidden("filterkey", Model.Filter.FilterKey)
@Html.Hidden("filtervalue", Model.Filter.FilterValue)
@Html.Label("username", T("User:").Text)
@Html.TextBox("username", Model.Filter.UserName, new { @class = "text" })
@Html.LabelFor(m => from, T("From:"))
@Html.EditorFor(m => from)
@Html.LabelFor(m => to, T("To:"))
@Html.EditorFor(m => to)
@Html.LabelFor(m => orderBy, T("Ordered by:"))
@Html.DropDownListFor(m => orderBy, orderByItems)
<button type="submit" value="yes">@T("Apply")</button>
</fieldset>
<div class="table">
<div class="row">
<div class="cell span-12">
@Display(Model.FilterLayout)
</div>
</div>
<div class="row">
<div class="cell span-4">
@Display(Model.FilterLayout.TripleFirst)
<fieldset>
@Html.LabelFor(m => orderBy, T("Ordered by:"))
@Html.DropDownListFor(m => orderBy, orderByItems)
</fieldset>
</div>
<div class="cell span-4">
@Display(Model.FilterLayout.TripleSecond)
</div>
<div class="cell span-4">
@Display(Model.FilterLayout.TripleThird)
</div>
</div>
<div class="row">
<div class="cell span-12">
<button type="submit" value="yes">@T("Apply")</button>
</div>
</div>
</div>
}
<div class="manage">
<a id="auditTrailSettings" href="#" class="button primaryAction">@T("Settings")</a>
</div>
</section>
<section class="audit-trail-list">
@if (!Model.Records.Any()) {

View File

@@ -0,0 +1,11 @@
@using Orchard.Core.Common.ViewModels
@{
var from = (DateTimeEditor)Model.From;
var to = (DateTimeEditor)Model.To;
}
<fieldset>
@Html.Label("from.Date", T("From:").Text)
@Html.EditorFor(m => from)
@Html.Label("to.Date", T("To:").Text, new { @class = "inline" })
@Html.EditorFor(m => to)
</fieldset>

View File

@@ -0,0 +1,4 @@
<fieldset>
@Html.Label("username", T("User:").Text)
@Html.TextBox("username", (string)Model.UserName, new { @class = "text" })
</fieldset>

View File

@@ -0,0 +1,11 @@
@using Orchard.ContentManagement
@{
var contentItem = (ContentItem)Model.ContentItem;
}
@if (contentItem != null) {
<fieldset>
@Html.Label("contentitem", T("Content Item:").Text)
@Html.ItemEditLink(contentItem)
@Html.Hidden("content", contentItem.Id)
</fieldset>
}

View File

@@ -0,0 +1,10 @@
@using Orchard.ContentManagement.MetaData.Models
@{
var contentTypes = (IList<ContentTypeDefinition>) Model.ContentTypes;
var currentContentType = (string)Model.ContentType;
var listItems = contentTypes.Select(x => new SelectListItem {Text = x.DisplayName, Value = x.Name, Selected = x.Name == currentContentType});
}
<fieldset>
@Html.Label("contenttype", T("Content Type:").Text)
@Html.DropDownList("contenttype", listItems, "")
</fieldset>

View File

@@ -11,7 +11,7 @@
@foreach (var category in Model.Categories) {
<fieldset>
<legend>@category.Name</legend>
<input type="hidden" name="AuditTrailSiteSettingsPart.Categories[@i].Category" value="@category.Category" />
<input type="hidden" name="AuditTrailSettingsPart.Categories[@i].Category" value="@category.Category" />
<table class="items">
<thead>
<tr>
@@ -25,12 +25,12 @@
var checkboxId = String.Format("Event{0}{1}", i, j);
<tr>
<td>
<input type="hidden" name="AuditTrailSiteSettingsPart.Categories[@i].Events[@j].Event" value="@evnt.Event" />
<input type="hidden" name="AuditTrailSettingsPart.Categories[@i].Events[@j].Event" value="@evnt.Event" />
@evnt.Name
</td>
<td>@evnt.Description</td>
<td>
<input type="checkbox" id="@checkboxId" name="AuditTrailSiteSettingsPart.Categories[@i].Events[@j].IsEnabled" value="@Boolean.TrueString" @if(evnt.IsEnabled){<text>checked="checked"</text>} />
<input type="checkbox" id="@checkboxId" name="AuditTrailSettingsPart.Categories[@i].Events[@j].IsEnabled" value="@Boolean.TrueString" @if(evnt.IsEnabled){<text>checked="checked"</text>} />
</td>
</tr>
j++;

View File

@@ -3,6 +3,6 @@
}
@if (contentId > 0) {
<fieldset>
@Html.ActionLink(T("View Audit Trail").Text, "Index", "Admin", new { filterkey = "content", filtervalue = contentId, area = "Orchard.AuditTrail" }, null)
@Html.ActionLink(T("View Audit Trail").Text, "Index", "Admin", new { content = contentId, area = "Orchard.AuditTrail" }, null)
</fieldset>
}

View File

@@ -48,7 +48,7 @@
<tfoot>
<tr>
<td colspan="6">
@Html.ActionLink(T("Show more events").Text, "Index", "Admin", new { filterKey = "content", filterValue = auditTrailPart.Id, page = 2, area = "Orchard.AuditTrail" }, null)
@Html.ActionLink(T("Show more events").Text, "Index", "Admin", new { content = auditTrailPart.Id, page = 2, area = "Orchard.AuditTrail" }, null)
</td>
</tr>
</tfoot>

View File

@@ -1 +1 @@
<a href="@Url.Action("Index", "Admin", new { filterkey = "content", filtervalue = Model.ContentItem.Id, area = "Orchard.AuditTrail" })">@T("Audit Trail")</a>@T(" | ")
<a href="@Url.Action("Index", "Admin", new { content = Model.ContentItem.Id, area = "Orchard.AuditTrail" })">@T("Audit Trail")</a>@T(" | ")