diff --git a/src/Framework/Framework/Configuration/DotvvmConfigurationPageConfiguration.cs b/src/Framework/Framework/Configuration/DotvvmConfigurationPageConfiguration.cs new file mode 100644 index 0000000000..69342ecf3c --- /dev/null +++ b/src/Framework/Framework/Configuration/DotvvmConfigurationPageConfiguration.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using Newtonsoft.Json; + +namespace DotVVM.Framework.Configuration +{ + public class DotvvmConfigurationPageConfiguration + { + public const string DefaultUrl = "_dotvvm/diagnostics/configuration"; + public const string DefaultRouteName = "_dotvvm_diagnostics_configuration"; + + /// + /// Gets or sets whether the configuration status page is enabled. + /// + /// + /// When null, the configuration page is automatically enabled if + /// is true. + /// + [JsonProperty("isEnabled", DefaultValueHandling = DefaultValueHandling.Ignore)] + [DefaultValue(null)] + public bool? IsEnabled + { + get { return _isEnabled; } + set { ThrowIfFrozen(); _isEnabled = value; } + } + private bool? _isEnabled = null; + + /// + /// Gets or sets the URL where the configuration page will be accessible from. + /// + [JsonProperty("url", DefaultValueHandling = DefaultValueHandling.Ignore)] + [DefaultValue(DefaultUrl)] + public string Url + { + get { return _url; } + set { ThrowIfFrozen(); _url = value; } + } + private string _url = DefaultUrl; + + /// + /// Gets or sets the name of the route that the configuration page will be registered as. + /// + [JsonProperty("routeName", DefaultValueHandling = DefaultValueHandling.Ignore)] + [DefaultValue(DefaultRouteName)] + public string RouteName + { + get { return _routeName; } + set { ThrowIfFrozen(); _routeName = value; } + } + private string _routeName = DefaultRouteName; + + private bool isFrozen = false; + + private void ThrowIfFrozen() + { + if (isFrozen) + throw FreezableUtils.Error(nameof(DotvvmConfigurationPageConfiguration)); + } + + public void Freeze() + { + isFrozen = true; + } + + public void Apply(DotvvmConfiguration config) + { + if (IsEnabled == true || (IsEnabled == null && config.Debug)) + { + config.RouteTable.Add( + routeName: RouteName, + url: Url, + virtualPath: "embedded://DotVVM.Framework/Diagnostics/ConfigurationPage.dothtml"); + } + } + } +} diff --git a/src/Framework/Framework/Configuration/DotvvmDiagnosticsConfiguration.cs b/src/Framework/Framework/Configuration/DotvvmDiagnosticsConfiguration.cs index 60b7741189..1afb9e83c2 100644 --- a/src/Framework/Framework/Configuration/DotvvmDiagnosticsConfiguration.cs +++ b/src/Framework/Framework/Configuration/DotvvmDiagnosticsConfiguration.cs @@ -21,6 +21,17 @@ public DotvvmCompilationPageConfiguration CompilationPage } private DotvvmCompilationPageConfiguration _compilationPage = new(); + /// + /// Gets or sets the options of the configuration status page. + /// + [JsonProperty("configurationPage")] + public DotvvmConfigurationPageConfiguration ConfigurationPage + { + get { return _configurationPage; } + set { ThrowIfFrozen(); _configurationPage = value; } + } + private DotvvmConfigurationPageConfiguration _configurationPage = new(); + /// /// Gets or sets the options for runtime warning about slow requests, too big viewmodels, ... /// @@ -44,12 +55,14 @@ public void Freeze() { isFrozen = true; CompilationPage.Freeze(); + ConfigurationPage.Freeze(); PerfWarnings.Freeze(); } public void Apply(DotvvmConfiguration config) { CompilationPage.Apply(config); + ConfigurationPage.Apply(config); } } } diff --git a/src/Framework/Framework/Diagnostics/ConfigurationPage.dothtml b/src/Framework/Framework/Diagnostics/ConfigurationPage.dothtml new file mode 100644 index 0000000000..61f5f1d6fd --- /dev/null +++ b/src/Framework/Framework/Diagnostics/ConfigurationPage.dothtml @@ -0,0 +1,52 @@ +@viewModel DotVVM.Framework.Diagnostics.ConfigurationPageViewModel + + + + + + DotVVM Configuration Page + + + +

Configuration Page

+
+ +
+
+
+ + +
+ {{value: Name}}: + {{value: Value}} +
+
+
+
+ + <%--
+

Tab 1

+
+ +
+

Tab 2

+
--%> +
+
+ + diff --git a/src/Framework/Framework/Diagnostics/ConfigurationPageViewModel.cs b/src/Framework/Framework/Diagnostics/ConfigurationPageViewModel.cs new file mode 100644 index 0000000000..8320113c96 --- /dev/null +++ b/src/Framework/Framework/Diagnostics/ConfigurationPageViewModel.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using DotVVM.Framework.Compilation; +using DotVVM.Framework.Configuration; +using DotVVM.Framework.ViewModel; +using Newtonsoft.Json; + +namespace DotVVM.Framework.Diagnostics +{ + public class ConfigurationPageViewModel : DotvvmViewModelBase + { + public int ActiveTab { get; set; } = 0; + + public List
RootSections { get; set; } = new(); + + public override Task Load() + { + RootSections = new List
{ GetSection(Context.Configuration) }; + return base.Load(); + } + + private static string? GetSettingString(object? setting) + { + if (setting is null) + { + return null; + } + + return setting.ToString(); + } + + private static Section GetSection(object config) + { + var configType = config.GetType(); + + var section = new Section { + Name = configType.Name + }; + + var props = configType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (var prop in props) + { + if (IsSetting(prop)) + { + section.Settings.Add(new Setting { + Name = prop.Name, + Value = GetSettingString(prop.GetValue(config)) + }); + } + else if (IsSubsection(prop)) + { + var subsection = prop.GetValue(config); + if (subsection is not null) + { + section.Subsections.Add(GetSection(subsection)); + } + } + } + + if (configType.GetInterfaces() + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + { + foreach (var subsection in (IEnumerable)config) + { + section.Subsections.Add(GetSection(subsection)); + } + } + return section; + } + + private static bool IsSubsection(PropertyInfo prop) + { + return prop.GetCustomAttribute() is null + && prop.GetIndexParameters().Length == 0 + && prop.PropertyType.IsClass + && prop.PropertyType.Assembly == typeof(DotvvmConfiguration).Assembly; + } + + private static bool IsSetting(PropertyInfo prop) + { + return prop.GetCustomAttribute() is null + && prop.GetIndexParameters().Length == 0 + && (prop.PropertyType.IsPrimitive + || prop.PropertyType.IsEnum + || prop.PropertyType == typeof(string)); + } + + [DebuggerDisplay("Section {Name} [{Settings.Count}, {Subsections.Count}]")] + public class Section + { + public string? Name { get; set; } + + public List Settings { get; set; } = new(); + + public List
Subsections { get; set; } = new(); + } + + [DebuggerDisplay("Setting {Name}: {Value}")] + public class Setting + { + public string? Name { get; set; } + public string? Value { get; set; } + } + } +} diff --git a/src/Framework/Framework/DotVVM.Framework.csproj b/src/Framework/Framework/DotVVM.Framework.csproj index 6b0bd5a75c..a48b218ccc 100644 --- a/src/Framework/Framework/DotVVM.Framework.csproj +++ b/src/Framework/Framework/DotVVM.Framework.csproj @@ -36,6 +36,7 @@ + @@ -120,6 +121,10 @@ + + + +