Skip to content
Merged
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public class OrderService(IScheduler scheduler)
### 5. Mount the dashboard

```csharp
app.UseRunWayDashboard(); // available at /scheduler
app.UseRunWayDashboard().RequireAuthorization("Admin"); // available at /scheduler
```

---
Expand Down Expand Up @@ -178,6 +178,10 @@ app.UseRunWayDashboard()
.RequireAuthorization(policy => policy.RequireRole("Admin"));
```

Outside the Development environment the dashboard and its API answer `403` until you decide who may use them: `RequireAuthorization(...)`, `AllowAnonymous()` for a public demo, or a fallback authorization policy. A dashboard nobody decided on is reported in the log at startup.

The dashboard runs in [Way.Shell](https://github.com/kododo-dev/WayShell), the UI shell shared by the Kododo Way libraries: the layout, theme, language and session handling come from the shell, and with other Ways in the same app it can also be opened from the shell's panel (`app.UseWayShell("/admin")`). Configure the shell's title, links and user with `services.AddWayShell(o => ...)`.

The dashboard shows job counts by status, a paginated job list, per-job audit timeline, recurring job schedules, and runner health.

![Dashboard overview](src/docs/screenshots/01-dashboard.png)
Expand Down
10 changes: 9 additions & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
<Nullable>enable</Nullable>
<LangVersion>Latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsNotAsErrors>NU1901;NU1902;NU1903;NU1904</WarningsNotAsErrors>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>none</DebugType>
</PropertyGroup>

<!--
RunWay.Dashboard builds against Kododo.Way.Shell from NuGet; -p:UseLocalWayShell=true builds against the WayShell sources
checked out next to this repository (../WayShell), to work on both at once.
-->
<PropertyGroup>
<UseLocalWayShell Condition="'$(UseLocalWayShell)' == ''">false</UseLocalWayShell>
<WayShellSource Condition="'$(WayShellSource)' == ''">$(MSBuildThisFileDirectory)..\..\WayShell\src</WayShellSource>
</PropertyGroup>

<PropertyGroup>
<AssemblyName>Kododo.$(MSBuildProjectName)</AssemblyName>
<RootNamespace>Kododo.$(MSBuildProjectName)</RootNamespace>
Expand Down
57 changes: 47 additions & 10 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,68 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<!-- The test host follows the runtime the tests run on. -->
<PropertyGroup>
<AspNetCoreTestVersion>10.0.13</AspNetCoreTestVersion>
<AspNetCoreTestVersion Condition="'$(TargetFramework)' == 'net8.0'">8.0.31</AspNetCoreTestVersion>
<AspNetCoreTestVersion Condition="'$(TargetFramework)' == 'net9.0'">9.0.20</AspNetCoreTestVersion>
</PropertyGroup>

<!--
EF Core follows the target framework: EF Core 10 is not binary compatible with code built against 8 (ExecuteUpdate's
SetPropertyCalls is gone), so an app on EF Core 10 needs RunWay built against 10.
-->
<PropertyGroup>
<EfCoreVersion>10.0.13</EfCoreVersion>
<NpgsqlEfCoreVersion>10.0.3</NpgsqlEfCoreVersion>
<NpgsqlVersion>10.0.3</NpgsqlVersion>
<EfCoreVersion Condition="'$(TargetFramework)' == 'net8.0'">8.0.29</EfCoreVersion>
<NpgsqlEfCoreVersion Condition="'$(TargetFramework)' == 'net8.0'">8.0.11</NpgsqlEfCoreVersion>
<NpgsqlVersion Condition="'$(TargetFramework)' == 'net8.0'">8.0.9</NpgsqlVersion>
<EfCoreVersion Condition="'$(TargetFramework)' == 'net9.0'">9.0.21</EfCoreVersion>
<NpgsqlEfCoreVersion Condition="'$(TargetFramework)' == 'net9.0'">9.0.4</NpgsqlEfCoreVersion>
<NpgsqlVersion Condition="'$(TargetFramework)' == 'net9.0'">9.0.5</NpgsqlVersion>
</PropertyGroup>

<!--
Microsoft.Data.SqlClient (through EF Core SqlServer) depends on System.Security.Cryptography.Pkcs versions with known
vulnerabilities, so RunWay.SqlServer references the patched ones directly, as ConfigWay does.
-->
<PropertyGroup>
<PkcsVersion>10.0.13</PkcsVersion>
<PkcsVersion Condition="'$(TargetFramework)' == 'net8.0'">8.0.2</PkcsVersion>
<PkcsVersion Condition="'$(TargetFramework)' == 'net9.0'">9.0.21</PkcsVersion>
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.29" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.29" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.29" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
<PackageVersion Include="Npgsql" Version="8.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.29" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="$(EfCoreVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="$(EfCoreVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(EfCoreVersion)" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="$(NpgsqlEfCoreVersion)" />
<PackageVersion Include="Npgsql" Version="$(NpgsqlVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(EfCoreVersion)" />
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="$(PkcsVersion)" />

<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.29" />
<!-- 10.x on every target: the .NET 10 SDK prunes older versions, which drops the task writing the manifest; Way.Shell needs 10.0.13. -->
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.13" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />

<PackageVersion Include="NCrontab" Version="3.4.0" />
<PackageVersion Include="Kododo.Reiho.AspNetCore" Version="1.0.0" />
<PackageVersion Include="Kododo.Reiho.AspNetCore" Version="2.1.1" />
<PackageVersion Include="Kododo.Way.Shell" Version="2.0.0-preview.1" />
</ItemGroup>

<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="$(AspNetCoreTestVersion)" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="NSubstitute" Version="6.0.0" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.13.0" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.13.0" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.16.0" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.16.0" />
</ItemGroup>

</Project>
8 changes: 7 additions & 1 deletion src/RunWay.Dashboard/.nuget/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@ app.UseRunWayDashboard("/jobs/dashboard");

## Authorization

`UseRunWayDashboard()` returns a `RouteGroupBuilder`:
`UseRunWayDashboard()` returns a `RouteGroupBuilder`. Outside the Development environment the dashboard answers `403` until you decide who may use it:

```csharp
app.UseRunWayDashboard()
.RequireAuthorization(policy => policy.RequireRole("Admin"));

app.UseRunWayDashboard().AllowAnonymous(); // a public demo
```

## Way.Shell

The dashboard runs in [Kododo.Way.Shell](https://www.nuget.org/packages/Kododo.Way.Shell): the layout, theme, language and session handling come from the shell. Configure the title, links and user with `services.AddWayShell(o => ...)`, and add `app.UseWayShell("/admin")` for one panel with every Way in the app.

## Features

- Job list with pagination and filtering by status
Expand Down
8 changes: 8 additions & 0 deletions src/RunWay.Dashboard/RunWay.Dashboard.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
</ItemGroup>

<ItemGroup Condition="'$(UseLocalWayShell)' != 'true'">
<PackageReference Include="Kododo.Way.Shell" />
</ItemGroup>

<ItemGroup Condition="'$(UseLocalWayShell)' == 'true'">
<ProjectReference Include="$(WayShellSource)\Way.Shell\Way.Shell.csproj" />
</ItemGroup>

<Target Name="_PreserveFileProvidersEmbeddedPackageReference" AfterTargets="AddPrunePackageReferences">
<ItemGroup>
<PrunePackageReference Remove="Microsoft.Extensions.FileProviders.Embedded" />
Expand Down
9 changes: 8 additions & 1 deletion src/RunWay.Dashboard/RunWayConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
using Kododo.Reiho.AspNetCore.API;
using Kododo.Way.Shell;

namespace Kododo.RunWay.Dashboard;

public static class RunWayConfigurationExtensions
{
/// <summary>
/// Adds the dashboard's API handlers and Way.Shell, which the dashboard runs in. Mount it with
/// <c>app.UseRunWayDashboard(path)</c>; configure the shell (title, links, user) with
/// <c>services.AddWayShell(o => ...)</c>.
/// </summary>
public static IRunWayConfiguration AddDashboard(this IRunWayConfiguration configuration)
{
configuration.Services.AddRequestHandlers(typeof(RunWayConfigurationExtensions).Assembly);
configuration.Services.AddWayShell();
return configuration;
}
}
}
34 changes: 21 additions & 13 deletions src/RunWay.Dashboard/RunWayDashboardApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
using Kododo.Reiho.AspNetCore.API;
using Kododo.Reiho.AspNetCore.SPA;
using Kododo.Way.Shell;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;

namespace Kododo.RunWay.Dashboard;

public static class RunWayDashboardApplicationBuilderExtensions
{
/// <summary>The dashboard as a Way.Shell module: <c>SPA/dist</c> is built with the wayModule() preset.</summary>
internal static readonly WayModule Module =
new("scheduler", "Scheduler", typeof(RunWayDashboardApplicationBuilderExtensions).Assembly, "SPA/dist") { Icon = "schedule" };

extension(IEndpointRouteBuilder endpoints)
{
/// <summary>
/// Mounts the dashboard and its API at <paramref name="path"/>, in Way.Shell. The API is at <c>{path}/api</c>.
/// </summary>
/// <returns>
/// The group of the dashboard's endpoints, for authorization and other conventions. Outside the Development
/// environment the dashboard and its API answer <c>403</c> until authorization is required here, anonymous
/// access is allowed explicitly (<c>AllowAnonymous()</c>) or <c>ShellOptions.AllowAnonymous</c> is set.
/// </returns>
/// <example>
/// <code>
/// app.UseRunWayDashboard("/scheduler").RequireAuthorization("Admin");
/// </code>
/// </example>
public RouteGroupBuilder UseRunWayDashboard(string path = "/scheduler")
{
var runwayEndpoints = endpoints.MapGroup(path);

runwayEndpoints.MapApi();
runwayEndpoints.MapEmbeddedSpa(typeof(RunWayDashboardApplicationBuilderExtensions).Assembly);

var runwayEndpoints = endpoints.MapWay(Module, path);
runwayEndpoints.MapGroup("/api").MapRequests(typeof(RunWayDashboardApplicationBuilderExtensions).Assembly);
return runwayEndpoints;
}

private void MapApi()
{
var api = endpoints.MapGroup("/api");
api.MapRequests(typeof(RunWayDashboardApplicationBuilderExtensions).Assembly);
}
}
}
}
8 changes: 2 additions & 6 deletions src/RunWay.Dashboard/SPA/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/ico" href="logo.svg" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Scheduler</title>
<base href="__BASE_PATH__" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600;700&display=swap" rel="stylesheet" />
<title>RunWay (dev)</title>
</head>
<body>
<div id="root"></div>
Expand Down
Loading
Loading