From ad4a702f7c189c1a6568f68e9bb721e01d734d62 Mon Sep 17 00:00:00 2001 From: Daniel Stolt Date: Thu, 9 Jul 2015 21:49:02 +0100 Subject: [PATCH] Adding the Reports feature back. --- src/Orchard.Web/Core/Orchard.Core.csproj | 13 +++ src/Orchard.Web/Core/Reports/AdminMenu.cs | 17 ++++ .../Reports/Controllers/AdminController.cs | 42 ++++++++++ src/Orchard.Web/Core/Reports/Module.txt | 9 +++ src/Orchard.Web/Core/Reports/Routes.cs | 33 ++++++++ .../Core/Reports/Styles/Web.config | 16 ++++ .../Reports/Styles/images/menu.reports.png | Bin 0 -> 219 bytes .../Reports/Styles/menu.reports-admin.css | 6 ++ .../ViewModels/DisplayReportViewModel.cs | 7 ++ .../ViewModels/ReportsAdminIndexViewModel.cs | 8 ++ .../Core/Reports/Views/Admin/Display.cshtml | 39 +++++++++ .../Core/Reports/Views/Admin/Index.cshtml | 39 +++++++++ src/Orchard/Orchard.Framework.csproj | 9 +++ src/Orchard/Reports/Report.cs | 16 ++++ src/Orchard/Reports/ReportEntry.cs | 15 ++++ src/Orchard/Reports/ReportExtentions.cs | 35 +++++++++ .../Reports/Services/IReportsCoordinator.cs | 30 +++++++ .../Reports/Services/IReportsManager.cs | 17 ++++ .../Reports/Services/IReportsPersister.cs | 14 ++++ .../Reports/Services/ReportsCoordinator.cs | 36 +++++++++ .../Reports/Services/ReportsManager.cs | 74 ++++++++++++++++++ .../Reports/Services/ReportsPersister.cs | 67 ++++++++++++++++ 22 files changed, 542 insertions(+) create mode 100644 src/Orchard.Web/Core/Reports/AdminMenu.cs create mode 100644 src/Orchard.Web/Core/Reports/Controllers/AdminController.cs create mode 100644 src/Orchard.Web/Core/Reports/Module.txt create mode 100644 src/Orchard.Web/Core/Reports/Routes.cs create mode 100644 src/Orchard.Web/Core/Reports/Styles/Web.config create mode 100644 src/Orchard.Web/Core/Reports/Styles/images/menu.reports.png create mode 100644 src/Orchard.Web/Core/Reports/Styles/menu.reports-admin.css create mode 100644 src/Orchard.Web/Core/Reports/ViewModels/DisplayReportViewModel.cs create mode 100644 src/Orchard.Web/Core/Reports/ViewModels/ReportsAdminIndexViewModel.cs create mode 100644 src/Orchard.Web/Core/Reports/Views/Admin/Display.cshtml create mode 100644 src/Orchard.Web/Core/Reports/Views/Admin/Index.cshtml create mode 100644 src/Orchard/Reports/Report.cs create mode 100644 src/Orchard/Reports/ReportEntry.cs create mode 100644 src/Orchard/Reports/ReportExtentions.cs create mode 100644 src/Orchard/Reports/Services/IReportsCoordinator.cs create mode 100644 src/Orchard/Reports/Services/IReportsManager.cs create mode 100644 src/Orchard/Reports/Services/IReportsPersister.cs create mode 100644 src/Orchard/Reports/Services/ReportsCoordinator.cs create mode 100644 src/Orchard/Reports/Services/ReportsManager.cs create mode 100644 src/Orchard/Reports/Services/ReportsPersister.cs diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index 1fd2dc6b2..f7cec46b2 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -223,6 +223,11 @@ + + + + + @@ -310,6 +315,9 @@ + + + @@ -557,6 +565,11 @@ + + + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/Orchard.Web/Core/Reports/AdminMenu.cs b/src/Orchard.Web/Core/Reports/AdminMenu.cs new file mode 100644 index 000000000..f912d2f59 --- /dev/null +++ b/src/Orchard.Web/Core/Reports/AdminMenu.cs @@ -0,0 +1,17 @@ +using Orchard.Localization; +using Orchard.Security; +using Orchard.UI.Navigation; + +namespace Orchard.Core.Reports { + public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } + + public void GetNavigation(NavigationBuilder builder) { + builder.AddImageSet("reports") + .Add(T("Reports"), "12", + menu => menu.Add(T("View"), "0", item => item.Action("Index", "Admin", new { area = "Reports" }) + .Permission(StandardPermissions.SiteOwner))); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Reports/Controllers/AdminController.cs b/src/Orchard.Web/Core/Reports/Controllers/AdminController.cs new file mode 100644 index 000000000..a1c548d40 --- /dev/null +++ b/src/Orchard.Web/Core/Reports/Controllers/AdminController.cs @@ -0,0 +1,42 @@ +using System.Linq; +using System.Web.Mvc; +using Orchard.Core.Reports.ViewModels; +using Orchard.Localization; +using Orchard.Reports.Services; +using Orchard.Security; + +namespace Orchard.Core.Reports.Controllers { + public class AdminController : Controller { + private readonly IReportsManager _reportsManager; + + public AdminController( + IOrchardServices services, + IReportsManager reportsManager) { + Services = services; + _reportsManager = reportsManager; + T = NullLocalizer.Instance; + } + + public IOrchardServices Services { get; set; } + public Localizer T { get; set; } + + public ActionResult Index() { + if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to list reports"))) + return new HttpUnauthorizedResult(); + + var model = new ReportsAdminIndexViewModel { Reports = _reportsManager.GetReports().ToList() }; + + return View(model); + } + + public ActionResult Display(int id) { + if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to display report"))) + return new HttpUnauthorizedResult(); + + var model = new DisplayReportViewModel { Report = _reportsManager.Get(id) }; + + return View(model); + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Reports/Module.txt b/src/Orchard.Web/Core/Reports/Module.txt new file mode 100644 index 000000000..066aab8c2 --- /dev/null +++ b/src/Orchard.Web/Core/Reports/Module.txt @@ -0,0 +1,9 @@ +Name: Reports +AntiForgery: enabled +Author: The Orchard Team +Website: http://orchardproject.net +Version: 1.9.1 +OrchardVersion: 1.9 +Description: The dashboard module is providing the reports screen of the application. +FeatureDescription: Reports management (deprecated). +Category: Core diff --git a/src/Orchard.Web/Core/Reports/Routes.cs b/src/Orchard.Web/Core/Reports/Routes.cs new file mode 100644 index 000000000..4f28dc0ad --- /dev/null +++ b/src/Orchard.Web/Core/Reports/Routes.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using System.Web.Routing; +using Orchard.Mvc.Routes; + +namespace Orchard.Core.Reports { + public class Routes : IRouteProvider { + public void GetRoutes(ICollection routes) { + foreach (var routeDescriptor in GetRoutes()) + routes.Add(routeDescriptor); + } + + public IEnumerable GetRoutes() { + return new[] { + new RouteDescriptor { + Priority = -5, + Route = new Route( + "Admin/Reports", + new RouteValueDictionary { + {"area", "Reports"}, + {"controller", "Admin"}, + {"action", "Index"} + }, + new RouteValueDictionary(), + new RouteValueDictionary { + {"area", "Reports"} + }, + new MvcRouteHandler()) + } + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Reports/Styles/Web.config b/src/Orchard.Web/Core/Reports/Styles/Web.config new file mode 100644 index 000000000..11135c337 --- /dev/null +++ b/src/Orchard.Web/Core/Reports/Styles/Web.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Core/Reports/Styles/images/menu.reports.png b/src/Orchard.Web/Core/Reports/Styles/images/menu.reports.png new file mode 100644 index 0000000000000000000000000000000000000000..e10c05e853af48a1e459d1c57ab309d760c45411 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^0zhoQ#0(@;4TX*XDdu7)&kzm{j@u9Y9{{-L1;Fyx1l&avCS(I9yUzA;};2dniw(0T$pgJQ@7sn8enaK$a%*#SoOG(U-ka*C- z##6$(u9PRga% Reports { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Reports/Views/Admin/Display.cshtml b/src/Orchard.Web/Core/Reports/Views/Admin/Display.cshtml new file mode 100644 index 000000000..5dc75da1c --- /dev/null +++ b/src/Orchard.Web/Core/Reports/Views/Admin/Display.cshtml @@ -0,0 +1,39 @@ +@model DisplayReportViewModel +@using Orchard.Core.Reports.ViewModels; + +@{ Layout.Title = T("Display Report").ToString(); } + +@using(Html.BeginFormAntiForgeryPost()) { + @Html.ValidationSummary() +
+ + + + + + + + + + + + + + + + @foreach (var reportEntry in Model.Report.Entries) { + + + + + + } +
@T("Type")@T("Message")@T("Date")
+ @reportEntry.Type + + @reportEntry.Message + + @reportEntry.Utc.ToLocalTime().ToShortDateString() @reportEntry.Utc.ToLocalTime().ToShortTimeString() +
+
+} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Reports/Views/Admin/Index.cshtml b/src/Orchard.Web/Core/Reports/Views/Admin/Index.cshtml new file mode 100644 index 000000000..699ff0d76 --- /dev/null +++ b/src/Orchard.Web/Core/Reports/Views/Admin/Index.cshtml @@ -0,0 +1,39 @@ +@model ReportsAdminIndexViewModel +@using Orchard.Core.Reports.ViewModels; + +@{ Layout.Title = T("Reports").ToString(); } + +@using(Html.BeginFormAntiForgeryPost()) { + @Html.ValidationSummary() +
+ + + + + + + + + + + + + + + + @foreach (var report in Model.Reports) { + + + + + + } +
@T("Name")@T("Title")@T("Date")
+ @Html.ActionLink(report.ActivityName, "Display", new {id = report.ReportId}) + + @report.Title + + @report.Utc.ToLocalTime().ToShortDateString() @report.Utc.ToLocalTime().ToShortTimeString() +
+
+} \ No newline at end of file diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 6562bbc0c..fdd30723b 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -149,6 +149,15 @@ + + + + + + + + + diff --git a/src/Orchard/Reports/Report.cs b/src/Orchard/Reports/Report.cs new file mode 100644 index 000000000..8176bddf9 --- /dev/null +++ b/src/Orchard/Reports/Report.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Orchard.Reports { + public class Report { + public Report() { + Entries = new List(); + } + + public IList Entries { get; set;} + public int ReportId { get; set; } + public string Title { get; set; } + public string ActivityName { get; set; } + public DateTime Utc { get; set; } + } +} diff --git a/src/Orchard/Reports/ReportEntry.cs b/src/Orchard/Reports/ReportEntry.cs new file mode 100644 index 000000000..c94b2de9c --- /dev/null +++ b/src/Orchard/Reports/ReportEntry.cs @@ -0,0 +1,15 @@ +using System; + +namespace Orchard.Reports { + public enum ReportEntryType { + Information, + Warning, + Error + } + + public class ReportEntry { + public ReportEntryType Type { get; set; } + public string Message { get; set; } + public DateTime Utc { get; set; } + } +} diff --git a/src/Orchard/Reports/ReportExtentions.cs b/src/Orchard/Reports/ReportExtentions.cs new file mode 100644 index 000000000..45a66fe91 --- /dev/null +++ b/src/Orchard/Reports/ReportExtentions.cs @@ -0,0 +1,35 @@ +using Orchard.Reports; +using Orchard.Reports.Services; + +public static class ReportExtentions { + /// + /// Adds a new report entry of type information to a report that was previously registered. + /// + /// + /// Key, i.e. technical name of the report. Should be the same as the one used when registering the report. + /// The message to include in the entry. + public static void Information(this IReportsCoordinator reportCoordinator, string reportKey, string message) { + reportCoordinator.Add(reportKey, ReportEntryType.Information, message); + } + + /// + /// Adds a new report entry of type warning to a report that was previously registered. + /// + /// + /// Key, i.e. technical name of the report. Should be the same as the one used when registering the report. + /// The message to include in the entry. + public static void Warning(this IReportsCoordinator reportCoordinator, string reportKey, string message) { + reportCoordinator.Add(reportKey, ReportEntryType.Warning, message); + } + + /// + /// Adds a new report entry of type error to a report that was previously registered. + /// + /// + /// Key, i.e. technical name of the report. Should be the same as the one used when registering the report. + /// The message to include in the entry. + public static void Error(this IReportsCoordinator reportCoordinator, string reportKey, string message) { + reportCoordinator.Add(reportKey, ReportEntryType.Error, message); + } +} + diff --git a/src/Orchard/Reports/Services/IReportsCoordinator.cs b/src/Orchard/Reports/Services/IReportsCoordinator.cs new file mode 100644 index 000000000..968d0166e --- /dev/null +++ b/src/Orchard/Reports/Services/IReportsCoordinator.cs @@ -0,0 +1,30 @@ +namespace Orchard.Reports.Services { + /// + /// Exposes a simplified interface for creating reports. Reports provide user-accessible log-like functionality. + /// + /// + /// can be used too to create reports directly. + /// + public interface IReportsCoordinator : IDependency { + /// + /// Adds a new report entry to a report that was previously registered. + /// + /// + /// Entries can be only added to a report that was previously registered through Register(). + /// + /// + /// Key, i.e. technical name of the report. Should be the same as the one used when registering the report. + /// Type of the entry. + /// The message to include in the entry. + void Add(string reportKey, ReportEntryType type, string message); + + /// + /// Registers a new report so entries can be added to it. + /// + /// Key, i.e. technical name of the report. + /// Name of the activity the report is about (e.g. "Upgrade"). + /// A title better describing what the report is about (e.g. "Migrating routes of Pages, Blog Posts"). + /// The report's numerical ID. + int Register(string reportKey, string activityName, string title); + } +} diff --git a/src/Orchard/Reports/Services/IReportsManager.cs b/src/Orchard/Reports/Services/IReportsManager.cs new file mode 100644 index 000000000..cacfe4b90 --- /dev/null +++ b/src/Orchard/Reports/Services/IReportsManager.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Orchard.Reports.Services { + /// + /// Service for handling reports. Reports provide user-accessible log-like functionality. + /// + /// + /// You can use to create reports through a simplified interface. + /// + public interface IReportsManager : ISingletonDependency { + void Add(int reportId, ReportEntryType type, string message); + int CreateReport(string title, string activityName); + Report Get(int reportId); + IEnumerable GetReports(); + void Flush(); + } +} diff --git a/src/Orchard/Reports/Services/IReportsPersister.cs b/src/Orchard/Reports/Services/IReportsPersister.cs new file mode 100644 index 000000000..6ccadbc7f --- /dev/null +++ b/src/Orchard/Reports/Services/IReportsPersister.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Orchard.Reports.Services { + /// + /// Defines a service that can be used to persist reports. + /// + /// + /// Implementations of this interface are commonly used from implementations. + /// + public interface IReportsPersister : IDependency { + IEnumerable Fetch(); + void Save(IEnumerable reports); + } +} diff --git a/src/Orchard/Reports/Services/ReportsCoordinator.cs b/src/Orchard/Reports/Services/ReportsCoordinator.cs new file mode 100644 index 000000000..0fc0604ae --- /dev/null +++ b/src/Orchard/Reports/Services/ReportsCoordinator.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using Orchard.Logging; + +namespace Orchard.Reports.Services { + public class ReportsCoordinator : IReportsCoordinator, IDisposable { + private readonly IReportsManager _reportsManager; + private readonly IDictionary _reports; + + public ReportsCoordinator(IReportsManager reportsManager) { + _reportsManager = reportsManager; + Logger = NullLogger.Instance; + _reports = new Dictionary(); + } + + public ILogger Logger { get; set; } + public void Dispose() { + _reportsManager.Flush(); + } + + public void Add(string reportKey, ReportEntryType type, string message) { + if(!_reports.ContainsKey(reportKey)) { + // ignore message if no corresponding report + return; + } + + _reportsManager.Add(_reports[reportKey], type, message); + } + + public int Register(string reportKey, string activityName, string title) { + int reportId = _reportsManager.CreateReport(title, activityName); + _reports.Add(reportKey, reportId); + return reportId; + } + } +} diff --git a/src/Orchard/Reports/Services/ReportsManager.cs b/src/Orchard/Reports/Services/ReportsManager.cs new file mode 100644 index 000000000..ed358e96c --- /dev/null +++ b/src/Orchard/Reports/Services/ReportsManager.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Orchard.Logging; + +namespace Orchard.Reports.Services { + public class ReportsManager : IReportsManager { + private readonly IReportsPersister _reportsPersister; + private List _reports; + private static readonly object _synLock = new object(); + private bool _isDirty; + + public ReportsManager(IReportsPersister reportsPersister) { + _reportsPersister = reportsPersister; + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public void Add(int reportId, ReportEntryType type, string message) { + lock ( _synLock ) { + LoadReports(); + _isDirty = true; + var report = Get(reportId); + if(report == null) { + return; + } + report.Entries.Add(new ReportEntry {Message = message, Type = type, Utc = DateTime.UtcNow}); + } + } + + public int CreateReport(string title, string activityName) { + lock ( _synLock ) { + LoadReports(); + _isDirty = true; + var reportId = _reports.Count == 0 ? 1 : _reports.Max(r => r.ReportId) + 1; + var report = new Report {ActivityName = activityName, ReportId = reportId, Title = title, Utc = DateTime.UtcNow}; + _reports.Add(report); + return reportId; + } + } + + public Report Get(int reportId) { + lock(_synLock) { + LoadReports(); + return _reports.Where(r => r.ReportId == reportId).FirstOrDefault(); + } + } + + public IEnumerable GetReports() { + lock ( _synLock ) { + LoadReports(); + return _reports.ToList(); + } + } + + public void Flush() { + if ( _reports == null || !_isDirty) { + return; + } + + lock ( _synLock ) { + _reportsPersister.Save(_reports); + _isDirty = false; + } + } + + private void LoadReports() { + if(_reports == null) { + _reports = _reportsPersister.Fetch().ToList(); + } + } + } +} diff --git a/src/Orchard/Reports/Services/ReportsPersister.cs b/src/Orchard/Reports/Services/ReportsPersister.cs new file mode 100644 index 000000000..76d7bd68e --- /dev/null +++ b/src/Orchard/Reports/Services/ReportsPersister.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Xml; +using System.Xml.Linq; +using Orchard.Environment.Configuration; +using Orchard.FileSystems.AppData; +using System.IO; + +namespace Orchard.Reports.Services { + public class ReportsPersister : IReportsPersister { + private readonly IAppDataFolder _appDataFolder; + private readonly ShellSettings _shellSettings; + private readonly string _reportsFileName; + private readonly DataContractSerializer _dataContractSerializer; + private readonly object _synLock = new object(); + + public ReportsPersister(IAppDataFolder appDataFolder, ShellSettings shellSettings) { + _appDataFolder = appDataFolder; + _shellSettings = shellSettings; + _dataContractSerializer = new DataContractSerializer(typeof(Report), new [] { typeof(ReportEntry) }); + _reportsFileName = Path.Combine(Path.Combine("Sites", _shellSettings.Name), "reports.dat"); + } + + public IEnumerable Fetch() { + lock ( _synLock ) { + if ( !_appDataFolder.FileExists(_reportsFileName) ) { + yield break; + } + + var text = _appDataFolder.ReadFile(_reportsFileName); + var xmlDocument = XDocument.Parse(text); + var rootNode = xmlDocument.Root; + if (rootNode == null) { + yield break; + } + + foreach (var reportNode in rootNode.Elements()) { + var reader = new StringReader(reportNode.Value); + using (var xmlReader = XmlReader.Create(reader)) { + yield return (Report) _dataContractSerializer.ReadObject(xmlReader, true); + } + } + } + } + + public void Save(IEnumerable reports) { + lock ( _synLock ) { + var xmlDocument = new XDocument(); + xmlDocument.Add(new XElement("Reports")); + foreach (var report in reports) { + var reportNode = new XElement("Report"); + var writer = new StringWriter(); + using (var xmlWriter = XmlWriter.Create(writer)) { + _dataContractSerializer.WriteObject(xmlWriter, report); + } + reportNode.Value = writer.ToString(); + xmlDocument.Root.Add(reportNode); + } + + var saveWriter = new StringWriter(); + xmlDocument.Save(saveWriter); + _appDataFolder.CreateFile(_reportsFileName, saveWriter.ToString()); + } + } + } +} +