Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuration page #1524

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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";

/// <summary>
/// Gets or sets whether the configuration status page is enabled.
/// </summary>
/// <remarks>
/// When null, the configuration page is automatically enabled if <see cref="DotvvmConfiguration.Debug"/>
/// is true.
/// </remarks>
[JsonProperty("isEnabled", DefaultValueHandling = DefaultValueHandling.Ignore)]
[DefaultValue(null)]
public bool? IsEnabled
{
get { return _isEnabled; }
set { ThrowIfFrozen(); _isEnabled = value; }
}
private bool? _isEnabled = null;

/// <summary>
/// Gets or sets the URL where the configuration page will be accessible from.
/// </summary>
[JsonProperty("url", DefaultValueHandling = DefaultValueHandling.Ignore)]
[DefaultValue(DefaultUrl)]
public string Url
{
get { return _url; }
set { ThrowIfFrozen(); _url = value; }
}
private string _url = DefaultUrl;

/// <summary>
/// Gets or sets the name of the route that the configuration page will be registered as.
/// </summary>
[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");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ public DotvvmCompilationPageConfiguration CompilationPage
}
private DotvvmCompilationPageConfiguration _compilationPage = new();

/// <summary>
/// Gets or sets the options of the configuration status page.
/// </summary>
[JsonProperty("configurationPage")]
public DotvvmConfigurationPageConfiguration ConfigurationPage
{
get { return _configurationPage; }
set { ThrowIfFrozen(); _configurationPage = value; }
}
private DotvvmConfigurationPageConfiguration _configurationPage = new();

/// <summary>
/// Gets or sets the options for runtime warning about slow requests, too big viewmodels, ...
/// </summary>
Expand All @@ -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);
}
}
}
52 changes: 52 additions & 0 deletions src/Framework/Framework/Diagnostics/ConfigurationPage.dothtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@viewModel DotVVM.Framework.Diagnostics.ConfigurationPageViewModel

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DotVVM Configuration Page</title>
<dot:RequiredResource Name="dotvvm.internal-css" />
</head>
<body ClientIDMode="Static">
<h1>Configuration Page</h1>
<hr />
<nav>
<%--<dot:Button Click="{staticCommand: ActiveTab = 0}"
Text="Tab 0"
class="nav"
Class-active="{value: ActiveTab == 0}" />
<dot:Button Click="{staticCommand: ActiveTab = 1}"
Text="Tab 1"
class="nav"
Class-active="{value: ActiveTab == 1}" />
<dot:Button Click="{staticCommand: ActiveTab = 2}"
Text="Tab 2"
class="nav"
Class-active="{value: ActiveTab == 2}" />--%>
</nav>
<hr />
<main>
<section>
<dot:HierarchyRepeater DataSource="{value: RootSections}"
ItemChildrenBinding="{value: Subsections}"
RenderSettings.Mode="Server">
<dot:Repeater DataSource="{value: Settings}" style="padding-left: 1rem">
<div>
<strong>{{value: Name}}:</strong>
<span>{{value: Value}}</span>
</div>
</dot:Repeater>
</dot:HierarchyRepeater>
</section>

<%--<section Visible="{value: ActiveTab == 1}">
<h2>Tab 1</h2>
</section>

<section Visible="{value: ActiveTab == 2}">
<h2>Tab 2</h2>
</section>--%>
</main>
<hr />
</body>
</html>
110 changes: 110 additions & 0 deletions src/Framework/Framework/Diagnostics/ConfigurationPageViewModel.cs
Original file line number Diff line number Diff line change
@@ -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<Section> RootSections { get; set; } = new();

public override Task Load()
{
RootSections = new List<Section> { 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<object>)config)
{
section.Subsections.Add(GetSection(subsection));
}
}
return section;
}

private static bool IsSubsection(PropertyInfo prop)
{
return prop.GetCustomAttribute<JsonIgnoreAttribute>() is null
&& prop.GetIndexParameters().Length == 0
&& prop.PropertyType.IsClass
&& prop.PropertyType.Assembly == typeof(DotvvmConfiguration).Assembly;
}

private static bool IsSetting(PropertyInfo prop)
{
return prop.GetCustomAttribute<JsonIgnoreAttribute>() 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<Setting> Settings { get; set; } = new();

public List<Section> Subsections { get; set; } = new();
}

[DebuggerDisplay("Setting {Name}: {Value}")]
public class Setting
{
public string? Name { get; set; }
public string? Value { get; set; }
}
}
}
5 changes: 5 additions & 0 deletions src/Framework/Framework/DotVVM.Framework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Diagnostics\CompilationPage.dothtml" />
<EmbeddedResource Include="Diagnostics\ConfigurationPage.dothtml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Core/DotVVM.Core.csproj" />
Expand Down Expand Up @@ -120,6 +121,10 @@
<TypescriptFile Include="Resources/Scripts/**/*.ts" />
</ItemGroup>

<ItemGroup>
<None Remove="Diagnostics\ConfigurationPage.dothtml" />
</ItemGroup>

<!-- BeforeBuild is ran for every target framework. However, unless its input files change, this target is skipped. -->
<Target Name="CompileJS" Inputs="@(TypescriptFile)" Outputs="obj/javascript/root-only/dotvvm-root.js;obj/javascript/root-only-debug/dotvvm-root.js;obj/javascript/root-spa/dotvvm-root.js;obj/javascript/root-spa-debug/dotvvm-root.js" BeforeTargets="DispatchToInnerBuilds;BeforeBuild">

Expand Down