Refactor of dashboards
* Remove dashboards from Configs().Dashboards * Remove dashboard.config XML file & associated code reading from XML * Load Dashboards from TypeLoader into a CollectionBuilder * Load merge/concat C# Dashboard Types with package.manifest * Add buildercolletion to an extension method on Composition
This commit is contained in:
@@ -10,6 +10,7 @@ using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Tour;
|
||||
using Umbraco.Web.Trees;
|
||||
using Umbraco.Web.Dashboards;
|
||||
|
||||
// the namespace here is intentional - although defined in Umbraco.Web assembly,
|
||||
// this class should be visible when using Umbraco.Core.Components, alongside
|
||||
@@ -92,6 +93,13 @@ namespace Umbraco.Core.Components
|
||||
public static BackOfficeSectionCollectionBuilder Sections(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<BackOfficeSectionCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backoffice dashboards collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
public static DashboardCollectionBuilder Dashboards(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<DashboardCollectionBuilder>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Uniques
|
||||
|
||||
38
src/Umbraco.Web/Dashboards/ContentDashboard.cs
Normal file
38
src/Umbraco.Web/Dashboards/ContentDashboard.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(10)]
|
||||
//[HideFromTypeFinder]
|
||||
[DataContract]
|
||||
public class ContentDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Get Started";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "contentIntro";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "content" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/default/startupdashboardintro.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules
|
||||
{
|
||||
get
|
||||
{
|
||||
//TODO: WB Not convinced these rules work correctly?!
|
||||
var rules = new List<IAccessRule>();
|
||||
rules.Add(new AccessRule { Type = AccessRuleType.Deny, Value = "translator" });
|
||||
rules.Add(new AccessRule { Type = AccessRuleType.Grant, Value = "admin" });
|
||||
return rules.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/Umbraco.Web/Dashboards/DashboardCollection.cs
Normal file
13
src/Umbraco.Web/Dashboards/DashboardCollection.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
public class DashboardCollection : BuilderCollectionBase<IDashboardSection>
|
||||
{
|
||||
public DashboardCollection(IEnumerable<IDashboardSection> items)
|
||||
: base(items)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
24
src/Umbraco.Web/Dashboards/DashboardCollectionBuilder.cs
Normal file
24
src/Umbraco.Web/Dashboards/DashboardCollectionBuilder.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Core.Manifest;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
public class DashboardCollectionBuilder : WeightedCollectionBuilderBase<DashboardCollectionBuilder, DashboardCollection, IDashboardSection>
|
||||
{
|
||||
protected override DashboardCollectionBuilder This => this;
|
||||
|
||||
protected override IEnumerable<IDashboardSection> CreateItems(IFactory factory)
|
||||
{
|
||||
// get the manifest parser just-in-time - injecting it in the ctor would mean that
|
||||
// simply getting the builder in order to configure the collection, would require
|
||||
// its dependencies too, and that can create cycles or other oddities
|
||||
var manifestParser = factory.GetInstance<ManifestParser>();
|
||||
|
||||
//TODO WB: We will need to re-sort items from package manifest with the C# Types
|
||||
return base.CreateItems(factory).Concat(manifestParser.Manifest.Dashboards);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Umbraco.Web/Dashboards/ExamineDashboard.cs
Normal file
29
src/Umbraco.Web/Dashboards/ExamineDashboard.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(20)]
|
||||
[DataContract]
|
||||
public class ExamineDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Examine Management";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "settingsExamine";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "settings" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/settings/examinemanagement.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
27
src/Umbraco.Web/Dashboards/FormsDashboard.cs
Normal file
27
src/Umbraco.Web/Dashboards/FormsDashboard.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(10)]
|
||||
[DataContract]
|
||||
public class FormsDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Install Umbraco Forms";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "formsInstall";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "forms" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/forms/formsdashboardintro.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
}
|
||||
29
src/Umbraco.Web/Dashboards/HealthCheckDashboard.cs
Normal file
29
src/Umbraco.Web/Dashboards/HealthCheckDashboard.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(50)]
|
||||
[DataContract]
|
||||
public class HealthCheckDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name="name")]
|
||||
public string Name => "Health Check";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "settingsHealthCheck";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "settings" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/settings/healthcheck.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
27
src/Umbraco.Web/Dashboards/MediaDashboard.cs
Normal file
27
src/Umbraco.Web/Dashboards/MediaDashboard.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(10)]
|
||||
[DataContract]
|
||||
public class MediaDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Content";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "mediaFolderBrowser";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "media" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/media/mediafolderbrowser.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
}
|
||||
27
src/Umbraco.Web/Dashboards/MembersDashboard.cs
Normal file
27
src/Umbraco.Web/Dashboards/MembersDashboard.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(10)]
|
||||
[DataContract]
|
||||
public class MembersDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Get Started";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "memberIntro";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "member" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/members/membersdashboardvideos.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
}
|
||||
29
src/Umbraco.Web/Dashboards/ModelsBuilderDashboard.cs
Normal file
29
src/Umbraco.Web/Dashboards/ModelsBuilderDashboard.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(40)]
|
||||
[DataContract]
|
||||
public class ModelsBuilderDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Models Builder";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "settingsModelsBuilder";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "settings" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "/App_Plugins/ModelsBuilder/modelsbuilder.htm";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
29
src/Umbraco.Web/Dashboards/PublishedStatusDashboard.cs
Normal file
29
src/Umbraco.Web/Dashboards/PublishedStatusDashboard.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(30)]
|
||||
[DataContract]
|
||||
public class PublishedStatusDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Published Status";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "settingsPublishedStatus";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "settings" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/settings/publishedstatus.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
27
src/Umbraco.Web/Dashboards/RedirectUrlDashboard.cs
Normal file
27
src/Umbraco.Web/Dashboards/RedirectUrlDashboard.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(20)]
|
||||
[DataContract]
|
||||
public class RedirectUrlDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Redirect URL Management";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "contentRedirectManager";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "content" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/content/redirecturls.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
}
|
||||
27
src/Umbraco.Web/Dashboards/SettingsDashboards.cs
Normal file
27
src/Umbraco.Web/Dashboards/SettingsDashboards.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
|
||||
namespace Umbraco.Web.Dashboards
|
||||
{
|
||||
[Weight(10)]
|
||||
[DataContract]
|
||||
public class SettingsDashboard : IDashboardSection
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name => "Welcome";
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias => "settingsWelcome";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string[] Sections => new string[] { "settings" };
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View => "views/dashboard/settings/settingsdashboardintro.html";
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ using Umbraco.Web.WebApi.Filters;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Web.Services;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
@@ -27,7 +29,7 @@ namespace Umbraco.Web.Editors
|
||||
[WebApi.UmbracoAuthorize]
|
||||
public class DashboardController : UmbracoApiController
|
||||
{
|
||||
private readonly Dashboards _dashboards;
|
||||
private readonly IDashboardService _dashboardService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DashboardController"/> with auto dependencies.
|
||||
@@ -38,10 +40,10 @@ namespace Umbraco.Web.Editors
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DashboardController"/> with all its dependencies.
|
||||
/// </summary>
|
||||
public DashboardController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, Dashboards dashboards)
|
||||
public DashboardController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, IDashboardService dashboardService)
|
||||
: base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState)
|
||||
{
|
||||
_dashboards = dashboards;
|
||||
_dashboardService = dashboardService;
|
||||
}
|
||||
|
||||
//we have just one instance of HttpClient shared for the entire application
|
||||
@@ -79,7 +81,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard content from '{Url}'", url);
|
||||
Logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard content from {Url}", url);
|
||||
|
||||
//it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings
|
||||
AppCaches.RuntimeCache.InsertCacheItem<JObject>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
@@ -117,7 +119,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard CSS from '{Url}'", url);
|
||||
Logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard CSS from {Url}", url);
|
||||
|
||||
//it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings
|
||||
AppCaches.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
@@ -132,9 +134,9 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
[OutgoingEditorModelEvent]
|
||||
public IEnumerable<Tab<DashboardControl>> GetDashboard(string section)
|
||||
public IEnumerable<Tab<IDashboardSection>> GetDashboard(string section)
|
||||
{
|
||||
return _dashboards.GetDashboards(section, Security.CurrentUser);
|
||||
return _dashboardService.GetDashboards(section, Security.CurrentUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Dashboard;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Services;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
public class Dashboards
|
||||
{
|
||||
private readonly ISectionService _sectionService;
|
||||
private readonly IDashboardSection _dashboardSection;
|
||||
private readonly ManifestParser _manifestParser;
|
||||
|
||||
public Dashboards(ISectionService sectionService, IDashboardSection dashboardSection, ManifestParser manifestParser)
|
||||
{
|
||||
_sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService));
|
||||
_dashboardSection = dashboardSection;
|
||||
_manifestParser = manifestParser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all dashboards, organized by section, for a user.
|
||||
/// </summary>
|
||||
public IDictionary<string, IEnumerable<Tab<DashboardControl>>> GetDashboards(IUser currentUser)
|
||||
{
|
||||
return _sectionService.GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns dashboards for a specific section, for a user.
|
||||
/// </summary>
|
||||
public IEnumerable<Tab<DashboardControl>> GetDashboards(string section, IUser currentUser)
|
||||
{
|
||||
var tabId = 1;
|
||||
var configDashboards = GetDashboardsFromConfig(ref tabId, section, currentUser);
|
||||
var pluginDashboards = GetDashboardsFromPlugins(ref tabId, section, currentUser);
|
||||
|
||||
// merge dashboards
|
||||
// both collections contain tab.alias -> controls
|
||||
var dashboards = configDashboards;
|
||||
|
||||
// until now, it was fine to have duplicate tab.aliases in configDashboard
|
||||
// so... the rule should be - just merge whatever we get, don't be clever
|
||||
dashboards.AddRange(pluginDashboards);
|
||||
|
||||
// re-sort by id
|
||||
dashboards.Sort((tab1, tab2) => tab1.Id > tab2.Id ? 1 : 0);
|
||||
|
||||
// re-assign ids (why?)
|
||||
var i = 1;
|
||||
foreach (var tab in dashboards)
|
||||
{
|
||||
tab.Id = i++;
|
||||
tab.IsActive = tab.Id == 1;
|
||||
}
|
||||
|
||||
return configDashboards;
|
||||
}
|
||||
|
||||
// note:
|
||||
// in dashboard.config we have 'sections' which define 'tabs' for 'areas'
|
||||
// and 'areas' are the true UI sections - and each tab can have more than
|
||||
// one control
|
||||
// in a manifest, we directly have 'dashboards' which map to a unique
|
||||
// control in a tab
|
||||
|
||||
// gets all tabs & controls from the config file
|
||||
private List<Tab<DashboardControl>> GetDashboardsFromConfig(ref int tabId, string section, IUser currentUser)
|
||||
{
|
||||
var tabs = new List<Tab<DashboardControl>>();
|
||||
|
||||
// disable packages section dashboard
|
||||
if (section == "packages") return tabs;
|
||||
|
||||
foreach (var dashboardSection in _dashboardSection.Sections.Where(x => x.Areas.InvariantContains(section)))
|
||||
{
|
||||
// validate access to this section
|
||||
if (!DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService))
|
||||
continue;
|
||||
|
||||
foreach (var tab in dashboardSection.Tabs)
|
||||
{
|
||||
// validate access to this tab
|
||||
if (!DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService))
|
||||
continue;
|
||||
|
||||
var dashboardControls = new List<DashboardControl>();
|
||||
|
||||
foreach (var control in tab.Controls)
|
||||
{
|
||||
// validate access to this control
|
||||
if (!DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService))
|
||||
continue;
|
||||
|
||||
// create and add control
|
||||
var dashboardControl = new DashboardControl
|
||||
{
|
||||
Caption = control.PanelCaption,
|
||||
Path = IOHelper.FindFile(control.ControlPath.Trim())
|
||||
};
|
||||
|
||||
if (dashboardControl.Path.InvariantEndsWith(".ascx"))
|
||||
throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported.");
|
||||
|
||||
dashboardControls.Add(dashboardControl);
|
||||
}
|
||||
|
||||
// create and add tab
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = tabId++,
|
||||
Alias = tab.Caption.ToSafeAlias(),
|
||||
Label = tab.Caption,
|
||||
Properties = dashboardControls
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
private List<Tab<DashboardControl>> GetDashboardsFromPlugins(ref int tabId, string section, IUser currentUser)
|
||||
{
|
||||
var tabs = new List<Tab<DashboardControl>>();
|
||||
|
||||
foreach (var dashboard in _manifestParser.Manifest.Dashboards.Where(x => x.Sections.InvariantContains(section)).OrderBy(x => x.Weight))
|
||||
{
|
||||
// validate access
|
||||
if (!DashboardSecurity.CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules))
|
||||
continue;
|
||||
|
||||
var dashboardControl = new DashboardControl
|
||||
{
|
||||
Caption = "",
|
||||
Path = IOHelper.FindFile(dashboard.View.Trim())
|
||||
};
|
||||
|
||||
if (dashboardControl.Path.InvariantEndsWith(".ascx"))
|
||||
throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported.");
|
||||
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = tabId++,
|
||||
Alias = dashboard.Alias.ToSafeAlias(),
|
||||
Label = dashboard.Name,
|
||||
Properties = new[] { dashboardControl }
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
@@ -14,9 +15,9 @@ namespace Umbraco.Web.Editors
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<MediaItemDisplay>> SendingMediaModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<MemberDisplay>> SendingMemberModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<UserDisplay>> SendingUserModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<IEnumerable<Tab<DashboardControl>>>> SendingDashboardModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<IEnumerable<Tab<IDashboardSection>>>> SendingDashboardModel;
|
||||
|
||||
private static void OnSendingDashboardModel(HttpActionExecutedContext sender, EditorModelEventArgs<IEnumerable<Tab<DashboardControl>>> e)
|
||||
private static void OnSendingDashboardModel(HttpActionExecutedContext sender, EditorModelEventArgs<IEnumerable<Tab<IDashboardSection>>> e)
|
||||
{
|
||||
var handler = SendingDashboardModel;
|
||||
handler?.Invoke(sender, e);
|
||||
@@ -65,8 +66,8 @@ namespace Umbraco.Web.Editors
|
||||
if (e.Model is UserDisplay)
|
||||
OnSendingUserModel(sender, new EditorModelEventArgs<UserDisplay>(e));
|
||||
|
||||
if (e.Model is IEnumerable<Tab<DashboardControl>>)
|
||||
OnSendingDashboardModel(sender, new EditorModelEventArgs<IEnumerable<Tab<DashboardControl>>>(e));
|
||||
if (e.Model is IEnumerable<IDashboardSection>)
|
||||
OnSendingDashboardModel(sender, new EditorModelEventArgs<IEnumerable<Tab<IDashboardSection>>>(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,15 +22,15 @@ namespace Umbraco.Web.Editors
|
||||
[PluginController("UmbracoApi")]
|
||||
public class SectionController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly Dashboards _dashboards;
|
||||
private readonly IDashboardService _dashboardService;
|
||||
private readonly ISectionService _sectionService;
|
||||
private readonly ITreeService _treeService;
|
||||
|
||||
public SectionController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState,
|
||||
Dashboards dashboards, ISectionService sectionService, ITreeService treeService)
|
||||
IDashboardService dashboardService, ISectionService sectionService, ITreeService treeService)
|
||||
: base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState)
|
||||
{
|
||||
_dashboards = dashboards;
|
||||
_dashboardService = dashboardService;
|
||||
_sectionService = sectionService;
|
||||
_treeService = treeService;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ namespace Umbraco.Web.Editors
|
||||
ControllerContext = ControllerContext
|
||||
};
|
||||
|
||||
var dashboards = _dashboards.GetDashboards(Security.CurrentUser);
|
||||
var dashboards = _dashboardService.GetDashboards(Security.CurrentUser);
|
||||
|
||||
//now we can add metadata for each section so that the UI knows if there's actually anything at all to render for
|
||||
//a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
[DataContract(Name = "control", Namespace = "")]
|
||||
public class DashboardControl
|
||||
{
|
||||
[DataMember(Name = "path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
[DataMember(Name = "caption")]
|
||||
public string Caption { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Microsoft.AspNet.SignalR;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
@@ -17,6 +18,7 @@ using Umbraco.Web.Actions;
|
||||
using Umbraco.Web.Cache;
|
||||
using Umbraco.Web.Composing.Composers;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Dashboards;
|
||||
using Umbraco.Web.Dictionary;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Features;
|
||||
@@ -95,14 +97,13 @@ namespace Umbraco.Web.Runtime
|
||||
composition.RegisterUnique<ITreeService, TreeService>();
|
||||
composition.RegisterUnique<ISectionService, SectionService>();
|
||||
|
||||
composition.RegisterUnique<IDashboardService, DashboardService>();
|
||||
|
||||
composition.RegisterUnique<IExamineManager>(factory => ExamineManager.Instance);
|
||||
|
||||
// configure the container for web
|
||||
composition.ConfigureForWeb();
|
||||
|
||||
|
||||
composition.RegisterUnique<Dashboards>();
|
||||
|
||||
|
||||
composition
|
||||
.ComposeUmbracoControllers(GetType().Assembly)
|
||||
.SetDefaultRenderMvcController<RenderMvcController>(); // default controller for template views
|
||||
@@ -202,6 +203,25 @@ namespace Umbraco.Web.Runtime
|
||||
.Append<MembersBackOfficeSection>()
|
||||
.Append<TranslationBackOfficeSection>();
|
||||
|
||||
|
||||
// register core CMS dashboards as types - will be ordered by weight attribute & merged with package.manifest dashboards
|
||||
// TODO WB Maybe use typeloader?!
|
||||
|
||||
composition.WithCollectionBuilder<DashboardCollectionBuilder>()
|
||||
.Add(composition.TypeLoader.GetTypes<IDashboardSection>());
|
||||
|
||||
//.Add<ContentDashboard>()
|
||||
//.Add<RedirectUrlDashboard>()
|
||||
//.Add<MediaDashboard>()
|
||||
//.Add<SettingsDashboard>()
|
||||
//.Add<ExamineDashboard>()
|
||||
//.Add<PublishedStatusDashboard>()
|
||||
//.Add<ModelsBuilderDashboard>()
|
||||
//.Add<HealthCheckDashboard>()
|
||||
//.Add<MembersDashboard>()
|
||||
//.Add<FormsDashboard>();
|
||||
|
||||
|
||||
// register back office trees
|
||||
foreach (var treeControllerType in umbracoApiControllerTypes
|
||||
.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x)))
|
||||
|
||||
@@ -1,65 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Dashboard;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Services;
|
||||
using Umbraco.Web.Dashboards;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
namespace Umbraco.Web.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility class for determine dashboard security
|
||||
/// </summary>
|
||||
internal class DashboardSecurity
|
||||
internal class DashboardService : IDashboardService
|
||||
{
|
||||
//TODO: Unit test all this!!! :/
|
||||
private readonly ISectionService _sectionService;
|
||||
private readonly DashboardCollection _dashboardCollection;
|
||||
|
||||
public static bool AuthorizeAccess(ISection dashboardSection, IUser user, ISectionService sectionService)
|
||||
public DashboardService(ISectionService sectionService, DashboardCollection dashboardCollection)
|
||||
{
|
||||
return CheckUserAccessByRules(user, sectionService, dashboardSection.AccessRights.Rules);
|
||||
_sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService));
|
||||
_dashboardCollection = dashboardCollection ?? throw new ArgumentNullException(nameof(dashboardCollection));
|
||||
}
|
||||
|
||||
public static bool AuthorizeAccess(IDashboardTab dashboardTab, IUser user, ISectionService sectionService)
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Tab<IDashboardSection>> GetDashboards(string section, IUser currentUser)
|
||||
{
|
||||
return CheckUserAccessByRules(user, sectionService, dashboardTab.AccessRights.Rules);
|
||||
}
|
||||
var tabs = new List<Tab<IDashboardSection>>();
|
||||
var tabId = 0;
|
||||
|
||||
public static bool AuthorizeAccess(IDashboardControl dashboardControl, IUser user, ISectionService sectionService)
|
||||
{
|
||||
return CheckUserAccessByRules(user, sectionService, dashboardControl.AccessRights.Rules);
|
||||
}
|
||||
|
||||
private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable<IAccessRule> rules)
|
||||
{
|
||||
IAccessRule[] denyRules = null, grantRules = null, grantBySectionRules = null;
|
||||
|
||||
var groupedRules = rules.GroupBy(x => x.Type);
|
||||
foreach (var group in groupedRules)
|
||||
foreach (var dashboard in _dashboardCollection.Where(x => x.Sections.InvariantContains(section)))
|
||||
{
|
||||
var a = group.ToArray();
|
||||
switch (group.Key)
|
||||
// validate access
|
||||
if (!CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules))
|
||||
continue;
|
||||
|
||||
if (dashboard.View.InvariantEndsWith(".ascx"))
|
||||
throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported.");
|
||||
|
||||
var dashboards = new List<IDashboardSection>();
|
||||
dashboards.Add(dashboard);
|
||||
|
||||
tabs.Add(new Tab<IDashboardSection>()
|
||||
{
|
||||
case AccessRuleType.Deny:
|
||||
denyRules = a;
|
||||
break;
|
||||
case AccessRuleType.Grant:
|
||||
grantRules = a;
|
||||
break;
|
||||
case AccessRuleType.GrantBySection:
|
||||
grantBySectionRules = a;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("panic");
|
||||
}
|
||||
Id = tabId++,
|
||||
Label = dashboard.Name,
|
||||
Alias = dashboard.Alias,
|
||||
Properties = dashboards
|
||||
});
|
||||
}
|
||||
|
||||
return (denyRules ?? Array.Empty<IAccessRule>(), grantRules ?? Array.Empty<IAccessRule>(), grantBySectionRules ?? Array.Empty<IAccessRule>());
|
||||
return tabs;
|
||||
}
|
||||
|
||||
public static bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable<IAccessRule> rules)
|
||||
/// <inheritdoc />
|
||||
public IDictionary<string, IEnumerable<Tab<IDashboardSection>>> GetDashboards(IUser currentUser)
|
||||
{
|
||||
return _sectionService.GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser));
|
||||
}
|
||||
|
||||
private bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable<IAccessRule> rules)
|
||||
{
|
||||
if (user.Id == Constants.Security.SuperUserId)
|
||||
return true;
|
||||
@@ -111,5 +109,32 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
return hasAccess;
|
||||
}
|
||||
|
||||
private (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable<IAccessRule> rules)
|
||||
{
|
||||
IAccessRule[] denyRules = null, grantRules = null, grantBySectionRules = null;
|
||||
|
||||
var groupedRules = rules.GroupBy(x => x.Type);
|
||||
foreach (var group in groupedRules)
|
||||
{
|
||||
var a = group.ToArray();
|
||||
switch (group.Key)
|
||||
{
|
||||
case AccessRuleType.Deny:
|
||||
denyRules = a;
|
||||
break;
|
||||
case AccessRuleType.Grant:
|
||||
grantRules = a;
|
||||
break;
|
||||
case AccessRuleType.GrantBySection:
|
||||
grantBySectionRules = a;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("panic");
|
||||
}
|
||||
}
|
||||
|
||||
return (denyRules ?? Array.Empty<IAccessRule>(), grantRules ?? Array.Empty<IAccessRule>(), grantBySectionRules ?? Array.Empty<IAccessRule>());
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/Umbraco.Web/Services/IDashboardService.cs
Normal file
27
src/Umbraco.Web/Services/IDashboardService.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Services
|
||||
{
|
||||
public interface IDashboardService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets dashboard for a specific section/application
|
||||
/// For a specific backoffice user
|
||||
/// </summary>
|
||||
/// <param name="section"></param>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<Tab<IDashboardSection>> GetDashboards(string section, IUser currentUser);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all dashboards, organized by section, for a user.
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <returns></returns>
|
||||
IDictionary<string, IEnumerable<Tab<IDashboardSection>>> GetDashboards(IUser currentUser);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,18 @@
|
||||
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
|
||||
<Compile Include="Components\BackOfficeUserAuditEventsComponent.cs" />
|
||||
<Compile Include="ContentApps\ListViewContentAppFactory.cs" />
|
||||
<Compile Include="Dashboards\ContentDashboard.cs" />
|
||||
<Compile Include="Dashboards\DashboardCollection.cs" />
|
||||
<Compile Include="Dashboards\DashboardCollectionBuilder.cs" />
|
||||
<Compile Include="Dashboards\ExamineDashboard.cs" />
|
||||
<Compile Include="Dashboards\FormsDashboard.cs" />
|
||||
<Compile Include="Dashboards\HealthCheckDashboard.cs" />
|
||||
<Compile Include="Dashboards\MediaDashboard.cs" />
|
||||
<Compile Include="Dashboards\MembersDashboard.cs" />
|
||||
<Compile Include="Dashboards\ModelsBuilderDashboard.cs" />
|
||||
<Compile Include="Dashboards\PublishedStatusDashboard.cs" />
|
||||
<Compile Include="Dashboards\RedirectUrlDashboard.cs" />
|
||||
<Compile Include="Dashboards\SettingsDashboards.cs" />
|
||||
<Compile Include="Editors\BackOfficePreviewModel.cs" />
|
||||
<Compile Include="Editors\PackageController.cs" />
|
||||
<Compile Include="Editors\KeepAliveController.cs" />
|
||||
@@ -169,6 +181,8 @@
|
||||
<Compile Include="Media\UploadAutoFillProperties.cs" />
|
||||
<Compile Include="Models\ContentEditing\MacroDisplay.cs" />
|
||||
<Compile Include="Models\ContentEditing\MacroParameterDisplay.cs" />
|
||||
<Compile Include="Services\DashboardService.cs" />
|
||||
<Compile Include="Services\IDashboardService.cs" />
|
||||
<Compile Include="Trees\BackOfficeSectionCollectionBuilder.cs" />
|
||||
<Compile Include="Trees\MediaBackOfficeSection.cs" />
|
||||
<Compile Include="Trees\MembersBackOfficeSection.cs" />
|
||||
@@ -244,7 +258,6 @@
|
||||
<Compile Include="WebApi\SerializeVersionAttribute.cs" />
|
||||
<Compile Include="WebApi\TrimModelBinder.cs" />
|
||||
<Compile Include="Editors\CodeFileController.cs" />
|
||||
<Compile Include="Editors\Dashboards.cs" />
|
||||
<Compile Include="Editors\DictionaryController.cs" />
|
||||
<Compile Include="Editors\EditorModelEventArgs.cs" />
|
||||
<Compile Include="Editors\EditorValidatorCollection.cs" />
|
||||
@@ -705,7 +718,6 @@
|
||||
<Compile Include="Editors\Filters\ContentSaveValidationAttribute.cs" />
|
||||
<Compile Include="Editors\ContentTypeControllerBase.cs" />
|
||||
<Compile Include="Editors\DashboardController.cs" />
|
||||
<Compile Include="Editors\DashboardSecurity.cs" />
|
||||
<Compile Include="Editors\DataTypeController.cs" />
|
||||
<Compile Include="Editors\DataTypeValidateAttribute.cs" />
|
||||
<Compile Include="Editors\ImagesController.cs" />
|
||||
@@ -826,7 +838,6 @@
|
||||
<Compile Include="Editors\EntityController.cs" />
|
||||
<Compile Include="Editors\MemberController.cs" />
|
||||
<Compile Include="Editors\CurrentUserController.cs" />
|
||||
<Compile Include="Models\ContentEditing\DashboardControl.cs" />
|
||||
<Compile Include="Models\ContentEditing\DataTypeDisplay.cs" />
|
||||
<Compile Include="Models\ContentEditing\DataTypeSave.cs" />
|
||||
<Compile Include="Models\ContentEditing\DataTypeConfigurationFieldDisplay.cs" />
|
||||
|
||||
Reference in New Issue
Block a user