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}}
+
+
+
+
+
+ <%--
+
+ --%>
+
+
+
+
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