Compare commits
2 Commits
v2.1
...
826714962e
| Author | SHA1 | Date | |
|---|---|---|---|
| 826714962e | |||
| 58c882603c |
@@ -34,7 +34,7 @@ jobs:
|
|||||||
# Publish frontend
|
# Publish frontend
|
||||||
# We need to build it first so the class list files generate
|
# We need to build it first so the class list files generate
|
||||||
- name: Build project
|
- name: Build project
|
||||||
run: dotnet build Hosts/Moonlight.Frontend.Host --configuration Debug
|
run: dotnet build Moonlight.Frontend --configuration Debug
|
||||||
|
|
||||||
- name: Build tailwind styles and extract class list
|
- name: Build tailwind styles and extract class list
|
||||||
working-directory: Hosts/Moonlight.Frontend.Host/Styles
|
working-directory: Hosts/Moonlight.Frontend.Host/Styles
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -400,10 +400,8 @@ FodyWeavers.xsd
|
|||||||
# Style builds
|
# Style builds
|
||||||
**/style.min.css
|
**/style.min.css
|
||||||
**/package-lock.json
|
**/package-lock.json
|
||||||
**/bun.lock
|
|
||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
**/.env
|
**/.env
|
||||||
**/appsettings.json
|
**/appsettings.json
|
||||||
**/appsettings.Development.json
|
**/appsettings.Development.json
|
||||||
**/storage
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<ItemGroup>
|
|
||||||
<!-- Put your plugin references here -->
|
|
||||||
<!-- E.g. <PackageReference Include="MoonlightServers.Api" Version="2.1.0" /> -->
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
10
Hosts/Moonlight.Api.Host/AppStartupLoader.cs
Normal file
10
Hosts/Moonlight.Api.Host/AppStartupLoader.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using MoonCore.PluginFramework;
|
||||||
|
using Moonlight.Api.Startup;
|
||||||
|
|
||||||
|
namespace Moonlight.Api.Host;
|
||||||
|
|
||||||
|
[PluginLoader]
|
||||||
|
public partial class AppStartupLoader : IAppStartup
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
# Base image
|
# Base image
|
||||||
FROM git.battlestati.one/moonlight-panel/app_base:moonlight AS base
|
FROM cgr.dev/chainguard/aspnet-runtime:latest AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
|
||||||
# Install required packages
|
# Build dependencies
|
||||||
RUN apt-get update; apt-get install unzip -y; apt-get clean
|
RUN apt-get update; apt-get install nodejs npm -y; apt-get clean
|
||||||
RUN curl -fsSL https://bun.sh/install | bash
|
|
||||||
ENV PATH="/root/.bun/bin:${PATH}"
|
|
||||||
|
|
||||||
# Build options
|
# Build options
|
||||||
ARG BUILD_CONFIGURATION=Release
|
ARG BUILD_CONFIGURATION=Release
|
||||||
@@ -17,7 +15,7 @@ WORKDIR /src/Hosts/Moonlight.Frontend.Host/Styles
|
|||||||
|
|
||||||
COPY ["Hosts/Moonlight.Frontend.Host/Styles/package.json", "package.json"]
|
COPY ["Hosts/Moonlight.Frontend.Host/Styles/package.json", "package.json"]
|
||||||
|
|
||||||
RUN bun install
|
RUN npm install
|
||||||
|
|
||||||
# Restore nuget packages
|
# Restore nuget packages
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
@@ -29,9 +27,6 @@ COPY ["Moonlight.Shared/Moonlight.Shared.csproj", "Moonlight.Shared/"]
|
|||||||
COPY ["Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj", "Hosts/Moonlight.Frontend.Host/"]
|
COPY ["Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj", "Hosts/Moonlight.Frontend.Host/"]
|
||||||
COPY ["Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj", "Hosts/Moonlight.Api.Host/"]
|
COPY ["Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj", "Hosts/Moonlight.Api.Host/"]
|
||||||
|
|
||||||
COPY ["Hosts/Moonlight.Frontend.Host/Frontend.props", "Hosts/Moonlight.Frontend.Host/"]
|
|
||||||
COPY ["Hosts/Moonlight.Api.Host/Api.props", "Hosts/Moonlight.Api.Host/"]
|
|
||||||
|
|
||||||
RUN dotnet restore "Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj"
|
RUN dotnet restore "Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj"
|
||||||
RUN dotnet restore "Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj"
|
RUN dotnet restore "Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj"
|
||||||
|
|
||||||
@@ -44,7 +39,7 @@ WORKDIR "/src/Hosts/Moonlight.Frontend.Host"
|
|||||||
RUN dotnet build "./Moonlight.Frontend.Host.csproj" -c $BUILD_CONFIGURATION -o /app/build-frontend
|
RUN dotnet build "./Moonlight.Frontend.Host.csproj" -c $BUILD_CONFIGURATION -o /app/build-frontend
|
||||||
|
|
||||||
WORKDIR "/src/Hosts/Moonlight.Frontend.Host/Styles"
|
WORKDIR "/src/Hosts/Moonlight.Frontend.Host/Styles"
|
||||||
RUN bun run build
|
RUN npm run build
|
||||||
|
|
||||||
# Build projects
|
# Build projects
|
||||||
WORKDIR "/src/Hosts/Moonlight.Api.Host"
|
WORKDIR "/src/Hosts/Moonlight.Api.Host"
|
||||||
@@ -72,6 +67,4 @@ WORKDIR /app
|
|||||||
COPY --from=publish /app/publish-api .
|
COPY --from=publish /app/publish-api .
|
||||||
COPY --from=publish /app/publish-frontend/wwwroot ./wwwroot
|
COPY --from=publish /app/publish-frontend/wwwroot ./wwwroot
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 CMD ["/usr/bin/curl", "-sf", "-o", "/dev/null", "http://localhost:8080/"]
|
|
||||||
|
|
||||||
ENTRYPOINT ["dotnet", "Moonlight.Api.Host.dll"]
|
ENTRYPOINT ["dotnet", "Moonlight.Api.Host.dll"]
|
||||||
@@ -7,13 +7,15 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.9"/>
|
||||||
|
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3"/>
|
||||||
|
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1"/>
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
||||||
<PackageReference Include="SimplePlugin" Version="1.0.2" />
|
|
||||||
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -27,5 +29,4 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Import Project="Api.props"/>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
using Moonlight.Api;
|
using Moonlight.Api.Host;
|
||||||
using SimplePlugin.Generated;
|
|
||||||
|
|
||||||
var plugins = PluginRegistry
|
var appLoader = new AppStartupLoader();
|
||||||
.Modules
|
appLoader.Initialize();
|
||||||
.OfType<MoonlightPlugin>()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
await StartupHandler.RunAsync(args, plugins);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
appLoader.PreBuild(builder);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
appLoader.PostBuild(app);
|
||||||
|
|
||||||
|
appLoader.PostMiddleware(app);
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
app.UseWebAssemblyDebugging();
|
||||||
|
|
||||||
|
app.UseBlazorFrameworkFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
await app.RunAsync();
|
||||||
10
Hosts/Moonlight.Frontend.Host/AppStartupLoader.cs
Normal file
10
Hosts/Moonlight.Frontend.Host/AppStartupLoader.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using MoonCore.PluginFramework;
|
||||||
|
using Moonlight.Frontend.Startup;
|
||||||
|
|
||||||
|
namespace Moonlight.Frontend.Host;
|
||||||
|
|
||||||
|
[PluginLoader]
|
||||||
|
public partial class AppStartupLoader : IAppStartup
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<ItemGroup>
|
|
||||||
<!-- Put your plugin references here -->
|
|
||||||
<!-- E.g. <PackageReference Include="MoonlightServers.Frontend" Version="2.1.0" /> -->
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -14,13 +14,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.1" PrivateAssets="all"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.1" PrivateAssets="all"/>
|
||||||
<PackageReference Include="SimplePlugin" Version="1.0.2" />
|
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.9"/>
|
||||||
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2" />
|
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Moonlight.Frontend\Moonlight.Frontend.csproj" />
|
<ProjectReference Include="..\..\Moonlight.Frontend\Moonlight.Frontend.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Import Project="Frontend.props"/>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
using Moonlight.Frontend;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using SimplePlugin.Generated;
|
using Moonlight.Frontend.Host;
|
||||||
|
|
||||||
var plugins = PluginRegistry
|
var appLoader = new AppStartupLoader();
|
||||||
.Modules
|
appLoader.Initialize();
|
||||||
.OfType<MoonlightPlugin>()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
await StartupHandler.RunAsync(args, plugins);
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
|
|
||||||
|
appLoader.PreBuild(builder);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
appLoader.PostBuild(app);
|
||||||
|
|
||||||
|
await app.RunAsync();
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
|
|
||||||
@import "../bin/ShadcnBlazor/scrollbar.css";
|
@import "../../../Moonlight.Frontend/bin/ShadcnBlazor/scrollbar.css";
|
||||||
@import "../bin/ShadcnBlazor/default-theme.css";
|
@import "../../../Moonlight.Frontend/bin/ShadcnBlazor/default-theme.css";
|
||||||
@import "./theme.css";
|
@import "./theme.css";
|
||||||
|
|
||||||
@source "../bin/ShadcnBlazor/ShadcnBlazor.map";
|
@source "../../../Moonlight.Frontend/bin/ShadcnBlazor/ShadcnBlazor.map";
|
||||||
|
|
||||||
@source "../../../Moonlight.Api/**/*.razor";
|
@source "../../../Moonlight.Api/**/*.razor";
|
||||||
@source "../../../Moonlight.Api/**/*.cs";
|
@source "../../../Moonlight.Api/**/*.cs";
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ namespace Moonlight.Api.Configuration;
|
|||||||
|
|
||||||
public class ApiOptions
|
public class ApiOptions
|
||||||
{
|
{
|
||||||
public TimeSpan LookupCacheL1Expiry { get; set; } = TimeSpan.FromSeconds(30);
|
public int LookupCacheMinutes { get; set; } = 3;
|
||||||
public TimeSpan LookupCacheL2Expiry { get; set; } = TimeSpan.FromMinutes(3);
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Moonlight.Api.Configuration;
|
|
||||||
|
|
||||||
public class CacheOptions
|
|
||||||
{
|
|
||||||
public bool EnableLayer2 { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Moonlight.Api.Configuration;
|
|
||||||
|
|
||||||
public class ContainerHelperOptions
|
|
||||||
{
|
|
||||||
public bool IsEnabled { get; set; }
|
|
||||||
public string Url { get; set; } = "http://helper:8080";
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ public class OidcOptions
|
|||||||
{
|
{
|
||||||
public string Authority { get; set; }
|
public string Authority { get; set; }
|
||||||
public bool RequireHttpsMetadata { get; set; } = true;
|
public bool RequireHttpsMetadata { get; set; } = true;
|
||||||
public bool DisableHttpsOnlyCookies { get; set; }
|
|
||||||
public string ResponseType { get; set; } = "code";
|
public string ResponseType { get; set; } = "code";
|
||||||
public string[]? Scopes { get; set; }
|
public string[]? Scopes { get; set; }
|
||||||
public string ClientId { get; set; }
|
public string ClientId { get; set; }
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Moonlight.Api.Configuration;
|
|
||||||
|
|
||||||
public class RedisOptions
|
|
||||||
{
|
|
||||||
public bool Enable { get; set; }
|
|
||||||
public string ConnectionString { get; set; }
|
|
||||||
}
|
|
||||||
6
Moonlight.Api/Configuration/SessionOptions.cs
Normal file
6
Moonlight.Api/Configuration/SessionOptions.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Moonlight.Api.Configuration;
|
||||||
|
|
||||||
|
public class SessionOptions
|
||||||
|
{
|
||||||
|
public int ValidationCacheMinutes { get; set; } = 3;
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Moonlight.Api.Configuration;
|
|
||||||
|
|
||||||
public class SettingsOptions
|
|
||||||
{
|
|
||||||
public TimeSpan LookupL1CacheTime { get; set; } = TimeSpan.FromMinutes(1);
|
|
||||||
public TimeSpan LookupL2CacheTime { get; set; } = TimeSpan.FromMinutes(5);
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Moonlight.Api.Configuration;
|
|
||||||
|
|
||||||
public class UserOptions
|
|
||||||
{
|
|
||||||
public TimeSpan ValidationCacheL1Expiry { get; set; } = TimeSpan.FromSeconds(30);
|
|
||||||
public TimeSpan ValidationCacheL2Expiry { get; set; } = TimeSpan.FromMinutes(3);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Moonlight.Api.Constants;
|
|
||||||
|
|
||||||
public class FrontendSettingConstants
|
|
||||||
{
|
|
||||||
public const string Name = "Moonlight.Frontend.Name";
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,6 @@ public class ApiKey : IActionTimestamps
|
|||||||
public required string Description { get; set; }
|
public required string Description { get; set; }
|
||||||
|
|
||||||
public string[] Permissions { get; set; } = [];
|
public string[] Permissions { get; set; } = [];
|
||||||
public DateTimeOffset ValidUntil { get; set; }
|
|
||||||
|
|
||||||
[MaxLength(32)]
|
[MaxLength(32)]
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Database.Entities;
|
namespace Moonlight.Api.Database.Entities;
|
||||||
|
|
||||||
@@ -11,6 +10,5 @@ public class SettingsOption
|
|||||||
public required string Key { get; set; }
|
public required string Key { get; set; }
|
||||||
|
|
||||||
[MaxLength(4096)]
|
[MaxLength(4096)]
|
||||||
[Column(TypeName = "jsonb")]
|
public required string Value { get; set; }
|
||||||
public required string ValueJson { get; set; }
|
|
||||||
}
|
}
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using Moonlight.Api.Database;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Database.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(DataContext))]
|
|
||||||
[Migration("20260129134620_SwitchedToJsonForSettingsOption")]
|
|
||||||
partial class SwitchedToJsonForSettingsOption
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasDefaultSchema("core")
|
|
||||||
.HasAnnotation("ProductVersion", "10.0.1")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.ApiKey", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(300)
|
|
||||||
.HasColumnType("character varying(300)");
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("character varying(32)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Permissions")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("ApiKeys", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(300)
|
|
||||||
.HasColumnType("character varying(300)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Permissions")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Roles", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<int>("RoleId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<int>("UserId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("RoleId");
|
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.ToTable("RoleMembers", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<string>("ValueJson")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(4096)
|
|
||||||
.HasColumnType("jsonb");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("SettingsOptions", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Theme", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("Author")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.Property<string>("CssContent")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20000)
|
|
||||||
.HasColumnType("character varying(20000)");
|
|
||||||
|
|
||||||
b.Property<bool>("IsEnabled")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.Property<string>("Version")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Themes", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(254)
|
|
||||||
.HasColumnType("character varying(254)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(50)
|
|
||||||
.HasColumnType("character varying(50)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Moonlight.Api.Database.Entities.Role", "Role")
|
|
||||||
.WithMany("Members")
|
|
||||||
.HasForeignKey("RoleId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Moonlight.Api.Database.Entities.User", "User")
|
|
||||||
.WithMany("RoleMemberships")
|
|
||||||
.HasForeignKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Role");
|
|
||||||
|
|
||||||
b.Navigation("User");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Members");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("RoleMemberships");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class SwitchedToJsonForSettingsOption : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "Value",
|
|
||||||
schema: "core",
|
|
||||||
table: "SettingsOptions");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "ValueJson",
|
|
||||||
schema: "core",
|
|
||||||
table: "SettingsOptions",
|
|
||||||
type: "jsonb",
|
|
||||||
maxLength: 4096,
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ValueJson",
|
|
||||||
schema: "core",
|
|
||||||
table: "SettingsOptions");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "Value",
|
|
||||||
schema: "core",
|
|
||||||
table: "SettingsOptions",
|
|
||||||
type: "character varying(4096)",
|
|
||||||
maxLength: 4096,
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using Moonlight.Api.Database;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Database.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(DataContext))]
|
|
||||||
[Migration("20260209114238_AddedValidUntilToApiKeys")]
|
|
||||||
partial class AddedValidUntilToApiKeys
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasDefaultSchema("core")
|
|
||||||
.HasAnnotation("ProductVersion", "10.0.1")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.ApiKey", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(300)
|
|
||||||
.HasColumnType("character varying(300)");
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("character varying(32)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Permissions")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("ValidUntil")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("ApiKeys", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(300)
|
|
||||||
.HasColumnType("character varying(300)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Permissions")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Roles", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<int>("RoleId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<int>("UserId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("RoleId");
|
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.ToTable("RoleMembers", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.SettingsOption", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<string>("ValueJson")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(4096)
|
|
||||||
.HasColumnType("jsonb");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("SettingsOptions", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Theme", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("Author")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.Property<string>("CssContent")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20000)
|
|
||||||
.HasColumnType("character varying(20000)");
|
|
||||||
|
|
||||||
b.Property<bool>("IsEnabled")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.Property<string>("Version")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(30)
|
|
||||||
.HasColumnType("character varying(30)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Themes", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(254)
|
|
||||||
.HasColumnType("character varying(254)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("InvalidateTimestamp")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(50)
|
|
||||||
.HasColumnType("character varying(50)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users", "core");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.RoleMember", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Moonlight.Api.Database.Entities.Role", "Role")
|
|
||||||
.WithMany("Members")
|
|
||||||
.HasForeignKey("RoleId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Moonlight.Api.Database.Entities.User", "User")
|
|
||||||
.WithMany("RoleMemberships")
|
|
||||||
.HasForeignKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Role");
|
|
||||||
|
|
||||||
b.Navigation("User");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.Role", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Members");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("RoleMemberships");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddedValidUntilToApiKeys : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
|
||||||
name: "ValidUntil",
|
|
||||||
schema: "core",
|
|
||||||
table: "ApiKeys",
|
|
||||||
type: "timestamp with time zone",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ValidUntil",
|
|
||||||
schema: "core",
|
|
||||||
table: "ApiKeys");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -56,9 +56,6 @@ namespace Moonlight.Api.Database.Migrations
|
|||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
b.Property<DateTimeOffset>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("ValidUntil")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("ApiKeys", "core");
|
b.ToTable("ApiKeys", "core");
|
||||||
@@ -139,10 +136,10 @@ namespace Moonlight.Api.Database.Migrations
|
|||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<string>("ValueJson")
|
b.Property<string>("Value")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(4096)
|
.HasMaxLength(4096)
|
||||||
.HasColumnType("jsonb");
|
.HasColumnType("character varying(4096)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Hybrid;
|
|
||||||
using Moonlight.Api.Database;
|
using Moonlight.Api.Database;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
using Moonlight.Api.Implementations.ApiKeyScheme;
|
|
||||||
using Moonlight.Api.Mappers;
|
using Moonlight.Api.Mappers;
|
||||||
using Moonlight.Shared;
|
using Moonlight.Shared;
|
||||||
using Moonlight.Shared.Http.Requests;
|
using Moonlight.Shared.Http.Requests;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
using Moonlight.Shared.Http.Requests.ApiKeys;
|
||||||
using Moonlight.Shared.Http.Responses;
|
using Moonlight.Shared.Http.Responses;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.ApiKeys;
|
using Moonlight.Shared.Http.Responses.ApiKeys;
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||||
|
|
||||||
@@ -20,12 +18,10 @@ namespace Moonlight.Api.Http.Controllers.Admin;
|
|||||||
public class ApiKeyController : Controller
|
public class ApiKeyController : Controller
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<ApiKey> KeyRepository;
|
private readonly DatabaseRepository<ApiKey> KeyRepository;
|
||||||
private readonly HybridCache HybridCache;
|
|
||||||
|
|
||||||
public ApiKeyController(DatabaseRepository<ApiKey> keyRepository, HybridCache hybridCache)
|
public ApiKeyController(DatabaseRepository<ApiKey> keyRepository)
|
||||||
{
|
{
|
||||||
KeyRepository = keyRepository;
|
KeyRepository = keyRepository;
|
||||||
HybridCache = hybridCache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -118,8 +114,6 @@ public class ApiKeyController : Controller
|
|||||||
ApiKeyMapper.Merge(apiKey, request);
|
ApiKeyMapper.Merge(apiKey, request);
|
||||||
await KeyRepository.UpdateAsync(apiKey);
|
await KeyRepository.UpdateAsync(apiKey);
|
||||||
|
|
||||||
await HybridCache.RemoveAsync(string.Format(ApiKeySchemeHandler.CacheKeyFormat, apiKey.Key));
|
|
||||||
|
|
||||||
return ApiKeyMapper.ToDto(apiKey);
|
return ApiKeyMapper.ToDto(apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,9 +129,6 @@ public class ApiKeyController : Controller
|
|||||||
return Problem("No API key with this id found", statusCode: 404);
|
return Problem("No API key with this id found", statusCode: 404);
|
||||||
|
|
||||||
await KeyRepository.RemoveAsync(apiKey);
|
await KeyRepository.RemoveAsync(apiKey);
|
||||||
|
|
||||||
await HybridCache.RemoveAsync(string.Format(ApiKeySchemeHandler.CacheKeyFormat, apiKey.Key));
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Moonlight.Api.Configuration;
|
|
||||||
using Moonlight.Api.Mappers;
|
|
||||||
using Moonlight.Api.Services;
|
|
||||||
using Moonlight.Shared;
|
|
||||||
using Moonlight.Shared.Http.Requests.Admin.ContainerHelper;
|
|
||||||
using Moonlight.Shared.Http.Responses.Admin;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/admin/ch")]
|
|
||||||
[Authorize(Policy = Permissions.System.Instance)]
|
|
||||||
public class ContainerHelperController : Controller
|
|
||||||
{
|
|
||||||
private readonly ContainerHelperService ContainerHelperService;
|
|
||||||
private readonly IOptions<ContainerHelperOptions> Options;
|
|
||||||
|
|
||||||
public ContainerHelperController(ContainerHelperService containerHelperService, IOptions<ContainerHelperOptions> options)
|
|
||||||
{
|
|
||||||
ContainerHelperService = containerHelperService;
|
|
||||||
Options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("status")]
|
|
||||||
public async Task<ActionResult<ContainerHelperStatusDto>> GetStatusAsync()
|
|
||||||
{
|
|
||||||
if (!Options.Value.IsEnabled)
|
|
||||||
return new ContainerHelperStatusDto(false, false);
|
|
||||||
|
|
||||||
var status = await ContainerHelperService.CheckConnectionAsync();
|
|
||||||
|
|
||||||
return new ContainerHelperStatusDto(true, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("rebuild")]
|
|
||||||
public Task<IResult> RebuildAsync([FromBody] RequestRebuildDto request)
|
|
||||||
{
|
|
||||||
var result = ContainerHelperService.RebuildAsync(request.NoBuildCache);
|
|
||||||
var mappedResult = result.Select(ContainerHelperMapper.ToDto);
|
|
||||||
|
|
||||||
return Task.FromResult<IResult>(
|
|
||||||
TypedResults.ServerSentEvents(mappedResult)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("version")]
|
|
||||||
public async Task<ActionResult> SetVersionAsync([FromBody] SetVersionDto request)
|
|
||||||
{
|
|
||||||
await ContainerHelperService.SetVersionAsync(request.Version);
|
|
||||||
return NoContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ using Moonlight.Api.Database.Entities;
|
|||||||
using Moonlight.Api.Mappers;
|
using Moonlight.Api.Mappers;
|
||||||
using Moonlight.Shared;
|
using Moonlight.Shared;
|
||||||
using Moonlight.Shared.Http.Responses;
|
using Moonlight.Shared.Http.Responses;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Users;
|
using Moonlight.Shared.Http.Responses.Users;
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using Moonlight.Api.Database.Entities;
|
|||||||
using Moonlight.Api.Mappers;
|
using Moonlight.Api.Mappers;
|
||||||
using Moonlight.Shared;
|
using Moonlight.Shared;
|
||||||
using Moonlight.Shared.Http.Requests;
|
using Moonlight.Shared.Http.Requests;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Roles;
|
using Moonlight.Shared.Http.Requests.Roles;
|
||||||
using Moonlight.Shared.Http.Responses;
|
using Moonlight.Shared.Http.Responses;
|
||||||
using Moonlight.Shared.Http.Responses.Admin;
|
using Moonlight.Shared.Http.Responses.Admin;
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Moonlight.Api.Constants;
|
|
||||||
using Moonlight.Api.Services;
|
|
||||||
using Moonlight.Shared;
|
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Settings;
|
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Settings;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers.Admin.Settings;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Authorize(Policy = Permissions.System.Settings)]
|
|
||||||
[Route("api/admin/system/settings/whiteLabeling")]
|
|
||||||
public class WhiteLabelingController : Controller
|
|
||||||
{
|
|
||||||
private readonly SettingsService SettingsService;
|
|
||||||
private readonly FrontendService FrontendService;
|
|
||||||
|
|
||||||
public WhiteLabelingController(SettingsService settingsService, FrontendService frontendService)
|
|
||||||
{
|
|
||||||
SettingsService = settingsService;
|
|
||||||
FrontendService = frontendService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public async Task<ActionResult<WhiteLabelingDto>> GetAsync()
|
|
||||||
{
|
|
||||||
var dto = new WhiteLabelingDto
|
|
||||||
{
|
|
||||||
Name = await SettingsService.GetValueAsync<string>(FrontendSettingConstants.Name) ?? "Moonlight"
|
|
||||||
};
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
public async Task<ActionResult<WhiteLabelingDto>> PostAsync([FromBody] SetWhiteLabelingDto request)
|
|
||||||
{
|
|
||||||
await SettingsService.SetValueAsync(FrontendSettingConstants.Name, request.Name);
|
|
||||||
await FrontendService.ResetCacheAsync();
|
|
||||||
|
|
||||||
var dto = new WhiteLabelingDto
|
|
||||||
{
|
|
||||||
Name = await SettingsService.GetValueAsync<string>(FrontendSettingConstants.Name) ?? "Moonlight"
|
|
||||||
};
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.Api.Database;
|
|
||||||
using Moonlight.Api.Database.Entities;
|
|
||||||
using Moonlight.Api.Services;
|
|
||||||
using Moonlight.Shared;
|
|
||||||
using Moonlight.Shared.Http.Requests.Seup;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers.Admin;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/admin/setup")]
|
|
||||||
public class SetupController : Controller
|
|
||||||
{
|
|
||||||
private readonly SettingsService SettingsService;
|
|
||||||
private readonly DatabaseRepository<User> UsersRepository;
|
|
||||||
private readonly DatabaseRepository<Role> RolesRepository;
|
|
||||||
|
|
||||||
private const string StateSettingsKey = "Moonlight.Api.Setup.State";
|
|
||||||
|
|
||||||
public SetupController(
|
|
||||||
SettingsService settingsService,
|
|
||||||
DatabaseRepository<User> usersRepository,
|
|
||||||
DatabaseRepository<Role> rolesRepository
|
|
||||||
)
|
|
||||||
{
|
|
||||||
SettingsService = settingsService;
|
|
||||||
UsersRepository = usersRepository;
|
|
||||||
RolesRepository = rolesRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<ActionResult> GetSetupAsync()
|
|
||||||
{
|
|
||||||
var hasBeenSetup = await SettingsService.GetValueAsync<bool>(StateSettingsKey);
|
|
||||||
|
|
||||||
if (hasBeenSetup)
|
|
||||||
return Problem("This instance is already configured", statusCode: 405);
|
|
||||||
|
|
||||||
return NoContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<ActionResult> ApplySetupAsync([FromBody] ApplySetupDto dto)
|
|
||||||
{
|
|
||||||
var adminRole = await RolesRepository
|
|
||||||
.Query()
|
|
||||||
.FirstOrDefaultAsync(x => x.Name == "Administrators");
|
|
||||||
|
|
||||||
if (adminRole == null)
|
|
||||||
{
|
|
||||||
adminRole = await RolesRepository.AddAsync(new Role()
|
|
||||||
{
|
|
||||||
Name = "Administrators",
|
|
||||||
Description = "Automatically generated group for full administrator permissions",
|
|
||||||
Permissions = [
|
|
||||||
Permissions.ApiKeys.View,
|
|
||||||
Permissions.ApiKeys.Create,
|
|
||||||
Permissions.ApiKeys.Edit,
|
|
||||||
Permissions.ApiKeys.Delete,
|
|
||||||
|
|
||||||
Permissions.Roles.View,
|
|
||||||
Permissions.Roles.Create,
|
|
||||||
Permissions.Roles.Edit,
|
|
||||||
Permissions.Roles.Delete,
|
|
||||||
Permissions.Roles.Members,
|
|
||||||
|
|
||||||
Permissions.Users.View,
|
|
||||||
Permissions.Users.Create,
|
|
||||||
Permissions.Users.Edit,
|
|
||||||
Permissions.Users.Delete,
|
|
||||||
Permissions.Users.Logout,
|
|
||||||
|
|
||||||
Permissions.Themes.View,
|
|
||||||
Permissions.Themes.Create,
|
|
||||||
Permissions.Themes.Edit,
|
|
||||||
Permissions.Themes.Delete,
|
|
||||||
|
|
||||||
Permissions.System.Info,
|
|
||||||
Permissions.System.Diagnose,
|
|
||||||
Permissions.System.Versions,
|
|
||||||
Permissions.System.Instance,
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var user = await UsersRepository
|
|
||||||
.Query()
|
|
||||||
.FirstOrDefaultAsync(u => u.Email == dto.AdminEmail);
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
await UsersRepository.AddAsync(new User()
|
|
||||||
{
|
|
||||||
Email = dto.AdminEmail,
|
|
||||||
Username = dto.AdminUsername,
|
|
||||||
RoleMemberships = [
|
|
||||||
new RoleMember()
|
|
||||||
{
|
|
||||||
Role = adminRole,
|
|
||||||
CreatedAt = DateTimeOffset.UtcNow,
|
|
||||||
UpdatedAt = DateTimeOffset.UtcNow
|
|
||||||
}
|
|
||||||
],
|
|
||||||
CreatedAt = DateTimeOffset.UtcNow,
|
|
||||||
UpdatedAt = DateTimeOffset.UtcNow
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
user.RoleMemberships.Add(new RoleMember()
|
|
||||||
{
|
|
||||||
Role = adminRole,
|
|
||||||
CreatedAt = DateTimeOffset.UtcNow,
|
|
||||||
UpdatedAt = DateTimeOffset.UtcNow
|
|
||||||
});
|
|
||||||
|
|
||||||
await UsersRepository.UpdateAsync(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
await SettingsService.SetValueAsync(StateSettingsKey, true);
|
|
||||||
|
|
||||||
return NoContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.Api.Database;
|
|
||||||
using Moonlight.Api.Database.Entities;
|
|
||||||
using Moonlight.Api.Mappers;
|
|
||||||
using Moonlight.Api.Models;
|
|
||||||
using Moonlight.Shared;
|
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Themes;
|
|
||||||
using VYaml.Serialization;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers.Admin.Themes;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/admin/themes")]
|
|
||||||
[Authorize(Policy = Permissions.Themes.View)]
|
|
||||||
public class TransferController : Controller
|
|
||||||
{
|
|
||||||
private readonly DatabaseRepository<Theme> ThemeRepository;
|
|
||||||
|
|
||||||
public TransferController(DatabaseRepository<Theme> themeRepository)
|
|
||||||
{
|
|
||||||
ThemeRepository = themeRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/export")]
|
|
||||||
public async Task<ActionResult> ExportAsync([FromRoute] int id)
|
|
||||||
{
|
|
||||||
var theme = await ThemeRepository
|
|
||||||
.Query()
|
|
||||||
.FirstOrDefaultAsync(x => x.Id == id);
|
|
||||||
|
|
||||||
if (theme == null)
|
|
||||||
return Problem("No theme with that id found", statusCode: 404);
|
|
||||||
|
|
||||||
var yml = YamlSerializer.Serialize(new ThemeTransferModel()
|
|
||||||
{
|
|
||||||
Name = theme.Name,
|
|
||||||
Author = theme.Author,
|
|
||||||
CssContent = theme.CssContent,
|
|
||||||
Version = theme.Version
|
|
||||||
});
|
|
||||||
|
|
||||||
return File(yml.ToArray(), "text/yaml", $"{theme.Name}.yml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("import")]
|
|
||||||
public async Task<ActionResult<ThemeDto>> ImportAsync()
|
|
||||||
{
|
|
||||||
var themeToImport = await YamlSerializer.DeserializeAsync<ThemeTransferModel>(Request.Body);
|
|
||||||
|
|
||||||
var existingTheme = await ThemeRepository
|
|
||||||
.Query()
|
|
||||||
.FirstOrDefaultAsync(x => x.Name == themeToImport.Name && x.Author == themeToImport.Author);
|
|
||||||
|
|
||||||
if (existingTheme == null)
|
|
||||||
{
|
|
||||||
var finalTheme = await ThemeRepository.AddAsync(new Theme()
|
|
||||||
{
|
|
||||||
Name = themeToImport.Name,
|
|
||||||
Author = themeToImport.Author,
|
|
||||||
CssContent = themeToImport.CssContent,
|
|
||||||
Version = themeToImport.Version
|
|
||||||
});
|
|
||||||
|
|
||||||
return ThemeMapper.ToDto(finalTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
existingTheme.CssContent = themeToImport.CssContent;
|
|
||||||
existingTheme.Version = themeToImport.Version;
|
|
||||||
|
|
||||||
await ThemeRepository.UpdateAsync(existingTheme);
|
|
||||||
|
|
||||||
return ThemeMapper.ToDto(existingTheme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,11 +7,11 @@ using Moonlight.Api.Mappers;
|
|||||||
using Moonlight.Api.Services;
|
using Moonlight.Api.Services;
|
||||||
using Moonlight.Shared;
|
using Moonlight.Shared;
|
||||||
using Moonlight.Shared.Http.Requests;
|
using Moonlight.Shared.Http.Requests;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Themes;
|
using Moonlight.Shared.Http.Requests.Themes;
|
||||||
using Moonlight.Shared.Http.Responses;
|
using Moonlight.Shared.Http.Responses;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Themes;
|
using Moonlight.Shared.Http.Responses.Themes;
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers.Admin.Themes;
|
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/admin/themes")]
|
[Route("api/admin/themes")]
|
||||||
@@ -6,9 +6,9 @@ using Moonlight.Api.Database.Entities;
|
|||||||
using Moonlight.Api.Mappers;
|
using Moonlight.Api.Mappers;
|
||||||
using Moonlight.Shared;
|
using Moonlight.Shared;
|
||||||
using Moonlight.Shared.Http.Requests;
|
using Moonlight.Shared.Http.Requests;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Users;
|
using Moonlight.Shared.Http.Requests.Users;
|
||||||
using Moonlight.Shared.Http.Responses;
|
using Moonlight.Shared.Http.Responses;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Users;
|
using Moonlight.Shared.Http.Responses.Users;
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers.Admin.Users;
|
namespace Moonlight.Api.Http.Controllers.Admin.Users;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Auth;
|
using Moonlight.Shared.Http.Responses.Auth;
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers;
|
namespace Moonlight.Api.Http.Controllers;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Moonlight.Api.Mappers;
|
using Moonlight.Api.Mappers;
|
||||||
using Moonlight.Api.Services;
|
using Moonlight.Api.Services;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Frontend;
|
using Moonlight.Shared.Http.Responses.Frontend;
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers;
|
namespace Moonlight.Api.Http.Controllers;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Controllers;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/ping")]
|
|
||||||
public class PingController : Controller
|
|
||||||
{
|
|
||||||
[HttpGet]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public IActionResult Get() => Ok("Pong");
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Services.ContainerHelper.Events;
|
|
||||||
|
|
||||||
public struct RebuildEventDto
|
|
||||||
{
|
|
||||||
[JsonPropertyName("type")]
|
|
||||||
public RebuildEventType Type { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("data")]
|
|
||||||
public string Data { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RebuildEventType
|
|
||||||
{
|
|
||||||
Log = 0,
|
|
||||||
Failed = 1,
|
|
||||||
Succeeded = 2,
|
|
||||||
Step = 3
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Moonlight.Api.Http.Services.ContainerHelper;
|
|
||||||
|
|
||||||
public class ProblemDetails
|
|
||||||
{
|
|
||||||
public string Type { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
public int Status { get; set; }
|
|
||||||
public string? Detail { get; set; }
|
|
||||||
public Dictionary<string, string[]>? Errors { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
namespace Moonlight.Api.Http.Services.ContainerHelper.Requests;
|
|
||||||
|
|
||||||
public record RequestRebuildDto(bool NoBuildCache);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
namespace Moonlight.Api.Http.Services.ContainerHelper.Requests;
|
|
||||||
|
|
||||||
public record SetVersionDto(string Version);
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Moonlight.Api.Http.Services.ContainerHelper.Events;
|
|
||||||
using Moonlight.Api.Http.Services.ContainerHelper.Requests;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Http.Services.ContainerHelper;
|
|
||||||
|
|
||||||
[JsonSerializable(typeof(SetVersionDto))]
|
|
||||||
[JsonSerializable(typeof(ProblemDetails))]
|
|
||||||
[JsonSerializable(typeof(RebuildEventDto))]
|
|
||||||
[JsonSerializable(typeof(RequestRebuildDto))]
|
|
||||||
|
|
||||||
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
|
|
||||||
public partial class SerializationContext : JsonSerializerContext
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ using System.Security.Claims;
|
|||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Hybrid;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moonlight.Api.Database;
|
using Moonlight.Api.Database;
|
||||||
@@ -14,20 +14,18 @@ namespace Moonlight.Api.Implementations.ApiKeyScheme;
|
|||||||
public class ApiKeySchemeHandler : AuthenticationHandler<ApiKeySchemeOptions>
|
public class ApiKeySchemeHandler : AuthenticationHandler<ApiKeySchemeOptions>
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<ApiKey> ApiKeyRepository;
|
private readonly DatabaseRepository<ApiKey> ApiKeyRepository;
|
||||||
private readonly HybridCache HybridCache;
|
private readonly IMemoryCache MemoryCache;
|
||||||
|
|
||||||
public const string CacheKeyFormat = $"Moonlight.Api.{nameof(ApiKeySchemeHandler)}.{{0}}";
|
|
||||||
|
|
||||||
public ApiKeySchemeHandler(
|
public ApiKeySchemeHandler(
|
||||||
IOptionsMonitor<ApiKeySchemeOptions> options,
|
IOptionsMonitor<ApiKeySchemeOptions> options,
|
||||||
ILoggerFactory logger,
|
ILoggerFactory logger,
|
||||||
UrlEncoder encoder,
|
UrlEncoder encoder,
|
||||||
DatabaseRepository<ApiKey> apiKeyRepository,
|
DatabaseRepository<ApiKey> apiKeyRepository,
|
||||||
HybridCache hybridCache
|
IMemoryCache memoryCache
|
||||||
) : base(options, logger, encoder)
|
) : base(options, logger, encoder)
|
||||||
{
|
{
|
||||||
ApiKeyRepository = apiKeyRepository;
|
ApiKeyRepository = apiKeyRepository;
|
||||||
HybridCache = hybridCache;
|
MemoryCache = memoryCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
@@ -40,30 +38,24 @@ public class ApiKeySchemeHandler : AuthenticationHandler<ApiKeySchemeOptions>
|
|||||||
if (authHeaderValue.Length > 32)
|
if (authHeaderValue.Length > 32)
|
||||||
return AuthenticateResult.Fail("Invalid api key specified");
|
return AuthenticateResult.Fail("Invalid api key specified");
|
||||||
|
|
||||||
var cacheKey = string.Format(CacheKeyFormat, authHeaderValue);
|
if (!MemoryCache.TryGetValue<ApiKeySession>(authHeaderValue, out var apiKey))
|
||||||
|
{
|
||||||
|
apiKey = await ApiKeyRepository
|
||||||
|
.Query()
|
||||||
|
.Where(x => x.Key == authHeaderValue)
|
||||||
|
.Select(x => new ApiKeySession(x.Permissions))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
var apiKey = await HybridCache.GetOrCreateAsync<ApiKeySession?>(
|
if (apiKey == null)
|
||||||
cacheKey,
|
return AuthenticateResult.Fail("Invalid api key specified");
|
||||||
async ct =>
|
|
||||||
{
|
|
||||||
return await ApiKeyRepository
|
|
||||||
.Query()
|
|
||||||
.Where(x => x.Key == authHeaderValue)
|
|
||||||
.Select(x => new ApiKeySession(x.Permissions, x.ValidUntil))
|
|
||||||
.FirstOrDefaultAsync(cancellationToken: ct);
|
|
||||||
},
|
|
||||||
new HybridCacheEntryOptions()
|
|
||||||
{
|
|
||||||
LocalCacheExpiration = Options.LookupL1CacheTime,
|
|
||||||
Expiration = Options.LookupL2CacheTime
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (apiKey == null)
|
MemoryCache.Set(authHeaderValue, apiKey, Options.LookupCacheTime);
|
||||||
return AuthenticateResult.Fail("Invalid api key specified");
|
}
|
||||||
|
else
|
||||||
if (DateTimeOffset.UtcNow > apiKey.ValidUntil)
|
{
|
||||||
return AuthenticateResult.Fail("Api key expired");
|
if (apiKey == null)
|
||||||
|
return AuthenticateResult.Fail("Invalid api key specified");
|
||||||
|
}
|
||||||
|
|
||||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||||
new ClaimsPrincipal(
|
new ClaimsPrincipal(
|
||||||
@@ -75,5 +67,5 @@ public class ApiKeySchemeHandler : AuthenticationHandler<ApiKeySchemeOptions>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ApiKeySession(string[] Permissions, DateTimeOffset ValidUntil);
|
private record ApiKeySession(string[] Permissions);
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,5 @@ namespace Moonlight.Api.Implementations.ApiKeyScheme;
|
|||||||
|
|
||||||
public class ApiKeySchemeOptions : AuthenticationSchemeOptions
|
public class ApiKeySchemeOptions : AuthenticationSchemeOptions
|
||||||
{
|
{
|
||||||
public TimeSpan LookupL1CacheTime { get; set; }
|
public TimeSpan LookupCacheTime { get; set; }
|
||||||
public TimeSpan LookupL2CacheTime { get; set; }
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
using Moonlight.Shared.Http.Requests.ApiKeys;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.ApiKeys;
|
using Moonlight.Shared.Http.Responses.ApiKeys;
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
namespace Moonlight.Api.Mappers;
|
namespace Moonlight.Api.Mappers;
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Moonlight.Shared.Http.Events;
|
|
||||||
using Riok.Mapperly.Abstractions;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Mappers;
|
|
||||||
|
|
||||||
[Mapper]
|
|
||||||
[SuppressMessage("Mapper", "RMG020:No members are mapped in an object mapping")]
|
|
||||||
[SuppressMessage("Mapper", "RMG012:No members are mapped in an object mapping")]
|
|
||||||
public static partial class ContainerHelperMapper
|
|
||||||
{
|
|
||||||
public static partial RebuildEventDto ToDto(Http.Services.ContainerHelper.Events.RebuildEventDto rebuildEventDto);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Moonlight.Api.Models;
|
using Moonlight.Api.Models;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Frontend;
|
using Moonlight.Shared.Http.Responses.Frontend;
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
namespace Moonlight.Api.Mappers;
|
namespace Moonlight.Api.Mappers;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Roles;
|
using Moonlight.Shared.Http.Requests.Roles;
|
||||||
using Moonlight.Shared.Http.Responses.Admin;
|
using Moonlight.Shared.Http.Responses.Admin;
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Themes;
|
using Moonlight.Shared.Http.Requests.Themes;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Themes;
|
using Moonlight.Shared.Http.Responses.Themes;
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
namespace Moonlight.Api.Mappers;
|
namespace Moonlight.Api.Mappers;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
using Moonlight.Shared.Http.Requests.Users;
|
||||||
|
using Moonlight.Shared.Http.Responses.Users;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Users;
|
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Users;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Mappers;
|
namespace Moonlight.Api.Mappers;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
using VYaml.Annotations;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Models;
|
|
||||||
|
|
||||||
[YamlObject]
|
|
||||||
public partial class ThemeTransferModel
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Version { get; set; }
|
|
||||||
public string Author { get; set; }
|
|
||||||
public string CssContent { get; set; }
|
|
||||||
}
|
|
||||||
@@ -25,14 +25,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.1"/>
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.1"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1"/>
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="10.3.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.3" />
|
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0"/>
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0"/>
|
||||||
<PackageReference Include="Riok.Mapperly" Version="4.3.1-next.0"/>
|
<PackageReference Include="Riok.Mapperly" Version="4.3.1-next.0"/>
|
||||||
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2" />
|
|
||||||
<PackageReference Include="VYaml" Version="1.2.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -40,8 +35,4 @@
|
|||||||
<Visible Condition="'%(NuGetItemType)' == 'Content'">false</Visible>
|
<Visible Condition="'%(NuGetItemType)' == 'Content'">false</Visible>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Http\Services\ContainerHelper\Responses\" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using SimplePlugin.Abstractions;
|
|
||||||
|
|
||||||
namespace Moonlight.Api;
|
|
||||||
|
|
||||||
public abstract class MoonlightPlugin : IPluginModule
|
|
||||||
{
|
|
||||||
protected MoonlightPlugin[] Plugins { get; private set; }
|
|
||||||
|
|
||||||
public void Initialize(MoonlightPlugin[] plugins)
|
|
||||||
{
|
|
||||||
Plugins = plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void PreBuild(WebApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void PostBuild(WebApplication application)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void PostMiddleware(WebApplication application)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
using System.Net.Http.Json;
|
|
||||||
using System.Text.Json;
|
|
||||||
using Moonlight.Api.Http.Services.ContainerHelper;
|
|
||||||
using Moonlight.Api.Http.Services.ContainerHelper.Requests;
|
|
||||||
using Moonlight.Api.Http.Services.ContainerHelper.Events;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Services;
|
|
||||||
|
|
||||||
public class ContainerHelperService
|
|
||||||
{
|
|
||||||
private readonly IHttpClientFactory HttpClientFactory;
|
|
||||||
|
|
||||||
public ContainerHelperService(IHttpClientFactory httpClientFactory)
|
|
||||||
{
|
|
||||||
HttpClientFactory = httpClientFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> CheckConnectionAsync()
|
|
||||||
{
|
|
||||||
var client = HttpClientFactory.CreateClient("ContainerHelper");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await client.GetAsync("api/ping");
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async IAsyncEnumerable<RebuildEventDto> RebuildAsync(bool noBuildCache)
|
|
||||||
{
|
|
||||||
var client = HttpClientFactory.CreateClient("ContainerHelper");
|
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, "api/rebuild");
|
|
||||||
|
|
||||||
request.Content = JsonContent.Create(
|
|
||||||
new RequestRebuildDto(noBuildCache),
|
|
||||||
null,
|
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
var response = await client.SendAsync(
|
|
||||||
request,
|
|
||||||
HttpCompletionOption.ResponseHeadersRead
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
yield return new RebuildEventDto()
|
|
||||||
{
|
|
||||||
Type = RebuildEventType.Failed,
|
|
||||||
Data = responseText
|
|
||||||
};
|
|
||||||
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var responseStream = await response.Content.ReadAsStreamAsync();
|
|
||||||
using var streamReader = new StreamReader(responseStream);
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var line = await streamReader.ReadLineAsync();
|
|
||||||
|
|
||||||
if (line == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var data = line.Trim("data: ");
|
|
||||||
var deserializedData = JsonSerializer.Deserialize<RebuildEventDto>(data, SerializationContext.Default.Options);
|
|
||||||
|
|
||||||
yield return deserializedData;
|
|
||||||
|
|
||||||
// Exit if service will go down for a clean exit
|
|
||||||
if (deserializedData is { Type: RebuildEventType.Step, Data: "ServiceDown" })
|
|
||||||
yield break;
|
|
||||||
} while (true);
|
|
||||||
|
|
||||||
yield return new RebuildEventDto()
|
|
||||||
{
|
|
||||||
Type = RebuildEventType.Succeeded,
|
|
||||||
Data = string.Empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetVersionAsync(string version)
|
|
||||||
{
|
|
||||||
var client = HttpClientFactory.CreateClient("ContainerHelper");
|
|
||||||
|
|
||||||
var response = await client.PostAsJsonAsync(
|
|
||||||
"api/configuration/version",
|
|
||||||
new SetVersionDto(version),
|
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var problemDetails =
|
|
||||||
await response.Content.ReadFromJsonAsync<ProblemDetails>(SerializationContext.Default.Options);
|
|
||||||
|
|
||||||
if (problemDetails == null)
|
|
||||||
throw new HttpRequestException($"Failed to set version: {response.ReasonPhrase}");
|
|
||||||
|
|
||||||
throw new HttpRequestException($"Failed to set version: {problemDetails.Detail ?? problemDetails.Title}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moonlight.Api.Configuration;
|
using Moonlight.Api.Configuration;
|
||||||
using Moonlight.Api.Constants;
|
|
||||||
using Moonlight.Api.Database;
|
using Moonlight.Api.Database;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
using Moonlight.Api.Models;
|
using Moonlight.Api.Models;
|
||||||
@@ -14,16 +13,14 @@ public class FrontendService
|
|||||||
private readonly IMemoryCache Cache;
|
private readonly IMemoryCache Cache;
|
||||||
private readonly DatabaseRepository<Theme> ThemeRepository;
|
private readonly DatabaseRepository<Theme> ThemeRepository;
|
||||||
private readonly IOptions<FrontendOptions> Options;
|
private readonly IOptions<FrontendOptions> Options;
|
||||||
private readonly SettingsService SettingsService;
|
|
||||||
|
|
||||||
private const string CacheKey = $"Moonlight.{nameof(FrontendService)}.{nameof(GetConfigurationAsync)}";
|
private const string CacheKey = $"Moonlight.{nameof(FrontendService)}.{nameof(GetConfigurationAsync)}";
|
||||||
|
|
||||||
public FrontendService(IMemoryCache cache, DatabaseRepository<Theme> themeRepository, IOptions<FrontendOptions> options, SettingsService settingsService)
|
public FrontendService(IMemoryCache cache, DatabaseRepository<Theme> themeRepository, IOptions<FrontendOptions> options)
|
||||||
{
|
{
|
||||||
Cache = cache;
|
Cache = cache;
|
||||||
ThemeRepository = themeRepository;
|
ThemeRepository = themeRepository;
|
||||||
Options = options;
|
Options = options;
|
||||||
SettingsService = settingsService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FrontendConfiguration> GetConfigurationAsync()
|
public async Task<FrontendConfiguration> GetConfigurationAsync()
|
||||||
@@ -38,9 +35,7 @@ public class FrontendService
|
|||||||
.Query()
|
.Query()
|
||||||
.FirstOrDefaultAsync(x => x.IsEnabled);
|
.FirstOrDefaultAsync(x => x.IsEnabled);
|
||||||
|
|
||||||
var name = await SettingsService.GetValueAsync<string>(FrontendSettingConstants.Name);
|
var config = new FrontendConfiguration("Moonlight", theme?.CssContent);
|
||||||
|
|
||||||
var config = new FrontendConfiguration(name ?? "Moonlight", theme?.CssContent);
|
|
||||||
|
|
||||||
Cache.Set(CacheKey, config, TimeSpan.FromMinutes(Options.Value.CacheMinutes));
|
Cache.Set(CacheKey, config, TimeSpan.FromMinutes(Options.Value.CacheMinutes));
|
||||||
|
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Caching.Hybrid;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Moonlight.Api.Configuration;
|
|
||||||
using Moonlight.Api.Database;
|
|
||||||
using Moonlight.Api.Database.Entities;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Services;
|
|
||||||
|
|
||||||
public class SettingsService
|
|
||||||
{
|
|
||||||
private readonly DatabaseRepository<SettingsOption> Repository;
|
|
||||||
private readonly IOptions<SettingsOptions> Options;
|
|
||||||
private readonly HybridCache HybridCache;
|
|
||||||
|
|
||||||
private const string CacheKey = "Moonlight.Api.SettingsService.{0}";
|
|
||||||
|
|
||||||
public SettingsService(
|
|
||||||
DatabaseRepository<SettingsOption> repository,
|
|
||||||
IOptions<SettingsOptions> options,
|
|
||||||
HybridCache hybridCache
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Repository = repository;
|
|
||||||
HybridCache = hybridCache;
|
|
||||||
Options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T?> GetValueAsync<T>(string key)
|
|
||||||
{
|
|
||||||
var cacheKey = string.Format(CacheKey, key);
|
|
||||||
|
|
||||||
var value = await HybridCache.GetOrCreateAsync<string?>(
|
|
||||||
cacheKey,
|
|
||||||
async ct =>
|
|
||||||
{
|
|
||||||
return await Repository
|
|
||||||
.Query()
|
|
||||||
.Where(x => x.Key == key)
|
|
||||||
.Select(o => o.ValueJson)
|
|
||||||
.FirstOrDefaultAsync(cancellationToken: ct);
|
|
||||||
},
|
|
||||||
new HybridCacheEntryOptions()
|
|
||||||
{
|
|
||||||
LocalCacheExpiration = Options.Value.LookupL1CacheTime,
|
|
||||||
Expiration = Options.Value.LookupL2CacheTime
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
return default;
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<T>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetValueAsync<T>(string key, T value)
|
|
||||||
{
|
|
||||||
var cacheKey = string.Format(CacheKey, key);
|
|
||||||
|
|
||||||
var option = await Repository
|
|
||||||
.Query()
|
|
||||||
.FirstOrDefaultAsync(x => x.Key == key);
|
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(value);
|
|
||||||
|
|
||||||
if (option != null)
|
|
||||||
{
|
|
||||||
option.ValueJson = json;
|
|
||||||
await Repository.UpdateAsync(option);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
option = new SettingsOption()
|
|
||||||
{
|
|
||||||
Key = key,
|
|
||||||
ValueJson = json
|
|
||||||
};
|
|
||||||
|
|
||||||
await Repository.AddAsync(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
await HybridCache.RemoveAsync(cacheKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Hybrid;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moonlight.Api.Configuration;
|
using Moonlight.Api.Configuration;
|
||||||
@@ -14,10 +14,10 @@ namespace Moonlight.Api.Services;
|
|||||||
public class UserAuthService
|
public class UserAuthService
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<User> UserRepository;
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
private readonly IMemoryCache Cache;
|
||||||
private readonly ILogger<UserAuthService> Logger;
|
private readonly ILogger<UserAuthService> Logger;
|
||||||
private readonly IOptions<UserOptions> Options;
|
private readonly IOptions<SessionOptions> Options;
|
||||||
private readonly IEnumerable<IUserAuthHook> Hooks;
|
private readonly IEnumerable<IUserAuthHook> Hooks;
|
||||||
private readonly HybridCache HybridCache;
|
|
||||||
|
|
||||||
private const string UserIdClaim = "UserId";
|
private const string UserIdClaim = "UserId";
|
||||||
private const string IssuedAtClaim = "IssuedAt";
|
private const string IssuedAtClaim = "IssuedAt";
|
||||||
@@ -27,16 +27,15 @@ public class UserAuthService
|
|||||||
public UserAuthService(
|
public UserAuthService(
|
||||||
DatabaseRepository<User> userRepository,
|
DatabaseRepository<User> userRepository,
|
||||||
ILogger<UserAuthService> logger,
|
ILogger<UserAuthService> logger,
|
||||||
IOptions<UserOptions> options,
|
IMemoryCache cache, IOptions<SessionOptions> options,
|
||||||
IEnumerable<IUserAuthHook> hooks,
|
IEnumerable<IUserAuthHook> hooks
|
||||||
HybridCache hybridCache
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
|
Cache = cache;
|
||||||
Options = options;
|
Options = options;
|
||||||
Hooks = hooks;
|
Hooks = hooks;
|
||||||
HybridCache = hybridCache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SyncAsync(ClaimsPrincipal? principal)
|
public async Task<bool> SyncAsync(ClaimsPrincipal? principal)
|
||||||
@@ -81,8 +80,8 @@ public class UserAuthService
|
|||||||
|
|
||||||
foreach (var hook in Hooks)
|
foreach (var hook in Hooks)
|
||||||
{
|
{
|
||||||
// Run every hook, and if any returns false, we return false as well
|
// Run every hook and if any returns false we return false as well
|
||||||
if (!await hook.SyncAsync(principal, user))
|
if(!await hook.SyncAsync(principal, user))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,29 +101,32 @@ public class UserAuthService
|
|||||||
|
|
||||||
var cacheKey = string.Format(CacheKeyPattern, userId);
|
var cacheKey = string.Format(CacheKeyPattern, userId);
|
||||||
|
|
||||||
var user = await HybridCache.GetOrCreateAsync<UserSession?>(
|
if (!Cache.TryGetValue<UserSession>(cacheKey, out var user))
|
||||||
cacheKey,
|
{
|
||||||
async ct =>
|
user = await UserRepository
|
||||||
{
|
.Query()
|
||||||
return await UserRepository
|
.AsNoTracking()
|
||||||
.Query()
|
.Where(u => u.Id == userId)
|
||||||
.AsNoTracking()
|
.Select(u => new UserSession(
|
||||||
.Where(u => u.Id == userId)
|
u.InvalidateTimestamp,
|
||||||
.Select(u => new UserSession(
|
u.RoleMemberships.SelectMany(x => x.Role.Permissions).ToArray())
|
||||||
u.InvalidateTimestamp,
|
)
|
||||||
u.RoleMemberships.SelectMany(x => x.Role.Permissions).ToArray())
|
.FirstOrDefaultAsync();
|
||||||
)
|
|
||||||
.FirstOrDefaultAsync(cancellationToken: ct);
|
|
||||||
},
|
|
||||||
new HybridCacheEntryOptions()
|
|
||||||
{
|
|
||||||
LocalCacheExpiration = Options.Value.ValidationCacheL1Expiry,
|
|
||||||
Expiration = Options.Value.ValidationCacheL2Expiry
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
Cache.Set(
|
||||||
|
cacheKey,
|
||||||
|
user,
|
||||||
|
TimeSpan.FromMinutes(Options.Value.ValidationCacheMinutes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var issuedAtString = principal.FindFirstValue(IssuedAtClaim);
|
var issuedAtString = principal.FindFirstValue(IssuedAtClaim);
|
||||||
|
|
||||||
@@ -147,8 +149,8 @@ public class UserAuthService
|
|||||||
|
|
||||||
foreach (var hook in Hooks)
|
foreach (var hook in Hooks)
|
||||||
{
|
{
|
||||||
// Run every hook, and if any returns false we return false as well
|
// Run every hook and if any returns false we return false as well
|
||||||
if (!await hook.ValidateAsync(principal, userId))
|
if(!await hook.ValidateAsync(principal, userId))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Hybrid;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Moonlight.Api.Database;
|
using Moonlight.Api.Database;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
using Moonlight.Api.Interfaces;
|
using Moonlight.Api.Interfaces;
|
||||||
@@ -10,17 +10,13 @@ public class UserDeletionService
|
|||||||
{
|
{
|
||||||
private readonly DatabaseRepository<User> Repository;
|
private readonly DatabaseRepository<User> Repository;
|
||||||
private readonly IEnumerable<IUserDeletionHook> Hooks;
|
private readonly IEnumerable<IUserDeletionHook> Hooks;
|
||||||
private readonly HybridCache HybridCache;
|
private readonly IMemoryCache Cache;
|
||||||
|
|
||||||
public UserDeletionService(
|
public UserDeletionService(DatabaseRepository<User> repository, IEnumerable<IUserDeletionHook> hooks, IMemoryCache cache)
|
||||||
DatabaseRepository<User> repository,
|
|
||||||
IEnumerable<IUserDeletionHook> hooks,
|
|
||||||
HybridCache hybridCache
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
Repository = repository;
|
Repository = repository;
|
||||||
Hooks = hooks;
|
Hooks = hooks;
|
||||||
HybridCache = hybridCache;
|
Cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserDeletionValidationResult> ValidateAsync(int userId)
|
public async Task<UserDeletionValidationResult> ValidateAsync(int userId)
|
||||||
@@ -58,8 +54,7 @@ public class UserDeletionService
|
|||||||
await hook.ExecuteAsync(user);
|
await hook.ExecuteAsync(user);
|
||||||
|
|
||||||
await Repository.RemoveAsync(user);
|
await Repository.RemoveAsync(user);
|
||||||
|
Cache.Remove(string.Format(UserAuthService.CacheKeyPattern, userId));
|
||||||
await HybridCache.RemoveAsync(string.Format(UserAuthService.CacheKeyPattern, user.Id));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Hybrid;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Moonlight.Api.Database;
|
using Moonlight.Api.Database;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
using Moonlight.Api.Interfaces;
|
using Moonlight.Api.Interfaces;
|
||||||
@@ -10,17 +10,17 @@ public class UserLogoutService
|
|||||||
{
|
{
|
||||||
private readonly DatabaseRepository<User> Repository;
|
private readonly DatabaseRepository<User> Repository;
|
||||||
private readonly IEnumerable<IUserLogoutHook> Hooks;
|
private readonly IEnumerable<IUserLogoutHook> Hooks;
|
||||||
private readonly HybridCache HybridCache;
|
private readonly IMemoryCache Cache;
|
||||||
|
|
||||||
public UserLogoutService(
|
public UserLogoutService(
|
||||||
DatabaseRepository<User> repository,
|
DatabaseRepository<User> repository,
|
||||||
IEnumerable<IUserLogoutHook> hooks,
|
IEnumerable<IUserLogoutHook> hooks,
|
||||||
HybridCache hybridCache
|
IMemoryCache cache
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Repository = repository;
|
Repository = repository;
|
||||||
Hooks = hooks;
|
Hooks = hooks;
|
||||||
HybridCache = hybridCache;
|
Cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogoutAsync(int userId)
|
public async Task LogoutAsync(int userId)
|
||||||
@@ -29,7 +29,7 @@ public class UserLogoutService
|
|||||||
.Query()
|
.Query()
|
||||||
.FirstOrDefaultAsync(x => x.Id == userId);
|
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||||
|
|
||||||
if (user == null)
|
if(user == null)
|
||||||
throw new AggregateException($"User with id {userId} not found");
|
throw new AggregateException($"User with id {userId} not found");
|
||||||
|
|
||||||
foreach (var hook in Hooks)
|
foreach (var hook in Hooks)
|
||||||
@@ -38,6 +38,6 @@ public class UserLogoutService
|
|||||||
user.InvalidateTimestamp = DateTimeOffset.UtcNow;
|
user.InvalidateTimestamp = DateTimeOffset.UtcNow;
|
||||||
await Repository.UpdateAsync(user);
|
await Repository.UpdateAsync(user);
|
||||||
|
|
||||||
await HybridCache.RemoveAsync(string.Format(UserAuthService.CacheKeyPattern, user.Id));
|
Cache.Remove(string.Format(UserAuthService.CacheKeyPattern, userId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
10
Moonlight.Api/Startup/IAppStartup.cs
Normal file
10
Moonlight.Api/Startup/IAppStartup.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Moonlight.Api.Startup;
|
||||||
|
|
||||||
|
public interface IAppStartup
|
||||||
|
{
|
||||||
|
public void PreBuild(WebApplicationBuilder builder);
|
||||||
|
public void PostBuild(WebApplication application);
|
||||||
|
public void PostMiddleware(WebApplication application);
|
||||||
|
}
|
||||||
@@ -17,26 +17,20 @@ public partial class Startup
|
|||||||
{
|
{
|
||||||
private static void AddAuth(WebApplicationBuilder builder)
|
private static void AddAuth(WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
// OIDC
|
|
||||||
var oidcOptions = new OidcOptions();
|
var oidcOptions = new OidcOptions();
|
||||||
builder.Configuration.GetSection("Moonlight:Oidc").Bind(oidcOptions);
|
builder.Configuration.GetSection("Moonlight:Oidc").Bind(oidcOptions);
|
||||||
|
|
||||||
// API Key
|
|
||||||
var apiKeyOptions = new ApiOptions();
|
var apiKeyOptions = new ApiOptions();
|
||||||
builder.Configuration.GetSection("Moonlight:Api").Bind(apiKeyOptions);
|
builder.Configuration.GetSection("Moonlight:Api").Bind(apiKeyOptions);
|
||||||
builder.Services.AddOptions<ApiOptions>().BindConfiguration("Moonlight:Api");
|
builder.Services.AddOptions<ApiOptions>().BindConfiguration("Moonlight:Api");
|
||||||
|
|
||||||
// Session
|
builder.Services.AddScoped<UserAuthService>();
|
||||||
builder.Services.AddOptions<UserOptions>().BindConfiguration("Moonlight:User");
|
|
||||||
|
|
||||||
// Authentication
|
|
||||||
builder.Services.AddAuthentication("Main")
|
builder.Services.AddAuthentication("Main")
|
||||||
.AddPolicyScheme("Main", null,
|
.AddPolicyScheme("Main", null, options =>
|
||||||
options =>
|
{
|
||||||
{
|
options.ForwardDefaultSelector += context => context.Request.Headers.Authorization.Count > 0 ? "ApiKey" : "Session";
|
||||||
options.ForwardDefaultSelector += context =>
|
})
|
||||||
context.Request.Headers.Authorization.Count > 0 ? "ApiKey" : "Session";
|
|
||||||
})
|
|
||||||
.AddCookie("Session", null, options =>
|
.AddCookie("Session", null, options =>
|
||||||
{
|
{
|
||||||
options.Events.OnSigningIn += async context =>
|
options.Events.OnSigningIn += async context =>
|
||||||
@@ -80,12 +74,6 @@ public partial class Startup
|
|||||||
options.Authority = oidcOptions.Authority;
|
options.Authority = oidcOptions.Authority;
|
||||||
options.RequireHttpsMetadata = oidcOptions.RequireHttpsMetadata;
|
options.RequireHttpsMetadata = oidcOptions.RequireHttpsMetadata;
|
||||||
|
|
||||||
if (oidcOptions.DisableHttpsOnlyCookies)
|
|
||||||
{
|
|
||||||
options.NonceCookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
|
||||||
options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scopes = oidcOptions.Scopes ?? ["openid", "email", "profile"];
|
var scopes = oidcOptions.Scopes ?? ["openid", "email", "profile"];
|
||||||
|
|
||||||
options.Scope.Clear();
|
options.Scope.Clear();
|
||||||
@@ -103,26 +91,15 @@ public partial class Startup
|
|||||||
|
|
||||||
options.GetClaimsFromUserInfoEndpoint = true;
|
options.GetClaimsFromUserInfoEndpoint = true;
|
||||||
})
|
})
|
||||||
.AddScheme<ApiKeySchemeOptions, ApiKeySchemeHandler>("ApiKey", null,
|
.AddScheme<ApiKeySchemeOptions, ApiKeySchemeHandler>("ApiKey", null, options =>
|
||||||
options =>
|
{
|
||||||
{
|
options.LookupCacheTime = TimeSpan.FromMinutes(apiKeyOptions.LookupCacheMinutes);
|
||||||
options.LookupL1CacheTime = apiKeyOptions.LookupCacheL1Expiry;
|
});
|
||||||
options.LookupL2CacheTime = apiKeyOptions.LookupCacheL2Expiry;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Authorization
|
|
||||||
builder.Services.AddAuthorization();
|
|
||||||
|
|
||||||
// Reduce log noise
|
|
||||||
builder.Logging.AddFilter("Moonlight.Api.Implementations.ApiKeyScheme.ApiKeySchemeHandler", LogLevel.Warning);
|
builder.Logging.AddFilter("Moonlight.Api.Implementations.ApiKeyScheme.ApiKeySchemeHandler", LogLevel.Warning);
|
||||||
|
|
||||||
// Custom permission handling using named policies
|
|
||||||
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
|
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
|
||||||
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
|
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
|
||||||
|
|
||||||
builder.Services.AddScoped<UserDeletionService>();
|
|
||||||
builder.Services.AddScoped<UserLogoutService>();
|
|
||||||
builder.Services.AddScoped<UserAuthService>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UseAuth(WebApplication application)
|
private static void UseAuth(WebApplication application)
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Console;
|
using Microsoft.Extensions.Logging.Console;
|
||||||
@@ -11,73 +9,43 @@ using Moonlight.Api.Helpers;
|
|||||||
using Moonlight.Api.Implementations;
|
using Moonlight.Api.Implementations;
|
||||||
using Moonlight.Api.Interfaces;
|
using Moonlight.Api.Interfaces;
|
||||||
using Moonlight.Api.Services;
|
using Moonlight.Api.Services;
|
||||||
|
using SessionOptions = Moonlight.Api.Configuration.SessionOptions;
|
||||||
|
|
||||||
namespace Moonlight.Api.Startup;
|
namespace Moonlight.Api.Startup;
|
||||||
|
|
||||||
public partial class Startup
|
public partial class Startup
|
||||||
{
|
{
|
||||||
private void AddBase(WebApplicationBuilder builder)
|
private static void AddBase(WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
// Create the base directory
|
builder.Services.AddControllers().AddJsonOptions(options =>
|
||||||
Directory.CreateDirectory("storage");
|
{
|
||||||
|
options.JsonSerializerOptions.TypeInfoResolverChain.Add(SerializationContext.Default);
|
||||||
|
});
|
||||||
|
|
||||||
// Hook up source-generated serialization and add controllers
|
|
||||||
builder.Services
|
|
||||||
.AddControllers()
|
|
||||||
.AddApplicationPart(typeof(Startup).Assembly)
|
|
||||||
.AddJsonOptions(options =>
|
|
||||||
{
|
|
||||||
options.JsonSerializerOptions.TypeInfoResolverChain.Add(SerializationContext.Default);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure logging
|
|
||||||
builder.Logging.ClearProviders();
|
builder.Logging.ClearProviders();
|
||||||
builder.Logging.AddConsole(options => { options.FormatterName = nameof(AppConsoleFormatter); });
|
builder.Logging.AddConsole(options => { options.FormatterName = nameof(AppConsoleFormatter); });
|
||||||
builder.Logging.AddConsoleFormatter<AppConsoleFormatter, ConsoleFormatterOptions>();
|
builder.Logging.AddConsoleFormatter<AppConsoleFormatter, ConsoleFormatterOptions>();
|
||||||
|
|
||||||
// Application service
|
|
||||||
builder.Services.AddSingleton<ApplicationService>();
|
builder.Services.AddSingleton<ApplicationService>();
|
||||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<ApplicationService>());
|
builder.Services.AddHostedService(sp => sp.GetRequiredService<ApplicationService>());
|
||||||
|
|
||||||
// Diagnose
|
|
||||||
builder.Services.AddSingleton<DiagnoseService>();
|
builder.Services.AddSingleton<DiagnoseService>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<IDiagnoseProvider, UpdateDiagnoseProvider>();
|
builder.Services.AddSingleton<IDiagnoseProvider, UpdateDiagnoseProvider>();
|
||||||
|
|
||||||
// Frontend
|
builder.Services.AddMemoryCache();
|
||||||
|
builder.Services.AddOptions<SessionOptions>().BindConfiguration("Moonlight:Session");
|
||||||
|
|
||||||
builder.Services.AddOptions<FrontendOptions>().BindConfiguration("Moonlight:Frontend");
|
builder.Services.AddOptions<FrontendOptions>().BindConfiguration("Moonlight:Frontend");
|
||||||
builder.Services.AddScoped<FrontendService>();
|
builder.Services.AddScoped<FrontendService>();
|
||||||
|
|
||||||
// HTTP Client
|
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
// Version fetching configuration
|
|
||||||
builder.Services.AddOptions<VersionOptions>().BindConfiguration("Moonlight:Version");
|
builder.Services.AddOptions<VersionOptions>().BindConfiguration("Moonlight:Version");
|
||||||
builder.Services.AddSingleton<VersionService>();
|
builder.Services.AddSingleton<VersionService>();
|
||||||
|
|
||||||
// Container Helper Options
|
|
||||||
builder.Configuration.GetSection("Moonlight:ContainerHelper").Bind(builder.Configuration);
|
|
||||||
|
|
||||||
builder.Services.AddOptions<ContainerHelperOptions>().BindConfiguration("Moonlight:ContainerHelper");
|
|
||||||
builder.Services.AddSingleton<ContainerHelperService>();
|
|
||||||
|
|
||||||
builder.Services.AddHttpClient("ContainerHelper", (provider, client) =>
|
|
||||||
{
|
|
||||||
var options = provider.GetRequiredService<IOptions<ContainerHelperOptions>>();
|
|
||||||
client.BaseAddress =
|
|
||||||
new Uri(options.Value.IsEnabled ? options.Value.Url : "http://you-should-fail.invalid");
|
|
||||||
});
|
|
||||||
|
|
||||||
// User management services
|
|
||||||
builder.Services.AddScoped<UserDeletionService>();
|
builder.Services.AddScoped<UserDeletionService>();
|
||||||
builder.Services.AddScoped<UserLogoutService>();
|
builder.Services.AddScoped<UserLogoutService>();
|
||||||
|
|
||||||
// Settings options
|
|
||||||
builder.Services.AddOptions<SettingsOptions>().BindConfiguration("Moonlight:Settings");
|
|
||||||
builder.Services.AddScoped<SettingsService>();
|
|
||||||
|
|
||||||
// Setup key loading
|
|
||||||
var keysDirectory = new DirectoryInfo(Path.Combine("storage", "keys"));
|
|
||||||
builder.Services.AddDataProtection().PersistKeysToFileSystem(keysDirectory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UseBase(WebApplication application)
|
private static void UseBase(WebApplication application)
|
||||||
@@ -91,7 +59,7 @@ public partial class Startup
|
|||||||
|
|
||||||
var options = application.Services.GetRequiredService<IOptions<FrontendOptions>>();
|
var options = application.Services.GetRequiredService<IOptions<FrontendOptions>>();
|
||||||
|
|
||||||
if (options.Value.Enabled)
|
if(options.Value.Enabled)
|
||||||
application.MapFallbackToFile("index.html");
|
application.MapFallbackToFile("index.html");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Moonlight.Api.Configuration;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Startup;
|
|
||||||
|
|
||||||
public partial class Startup
|
|
||||||
{
|
|
||||||
private static void AddCache(WebApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
// Load cache options
|
|
||||||
var cacheOptions = new CacheOptions();
|
|
||||||
builder.Configuration.GetSection("Moonlight:Cache").Bind(cacheOptions);
|
|
||||||
|
|
||||||
builder.Services.AddMemoryCache();
|
|
||||||
builder.Services.AddHybridCache();
|
|
||||||
|
|
||||||
if (!cacheOptions.EnableLayer2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var redisOptions = new RedisOptions();
|
|
||||||
builder.Configuration.GetSection("Moonlight:Redis").Bind(redisOptions);
|
|
||||||
|
|
||||||
if(!redisOptions.Enable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
builder.Services.AddStackExchangeRedisCache(options =>
|
|
||||||
{
|
|
||||||
options.Configuration = redisOptions.ConnectionString;
|
|
||||||
options.InstanceName = "Moonlight:";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,23 @@
|
|||||||
using System.Reflection;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Moonlight.Shared.Http;
|
|
||||||
using SimplePlugin.Abstractions;
|
|
||||||
|
|
||||||
namespace Moonlight.Api.Startup;
|
namespace Moonlight.Api.Startup;
|
||||||
|
|
||||||
[PluginModule]
|
public partial class Startup : IAppStartup
|
||||||
public partial class Startup : MoonlightPlugin
|
|
||||||
{
|
{
|
||||||
public override void PreBuild(WebApplicationBuilder builder)
|
public void PreBuild(WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
AddBase(builder);
|
AddBase(builder);
|
||||||
AddAuth(builder);
|
AddAuth(builder);
|
||||||
AddDatabase(builder);
|
AddDatabase(builder);
|
||||||
AddCache(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PostBuild(WebApplication application)
|
public void PostBuild(WebApplication application)
|
||||||
{
|
{
|
||||||
UseBase(application);
|
UseBase(application);
|
||||||
UseAuth(application);
|
UseAuth(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PostMiddleware(WebApplication application)
|
public void PostMiddleware(WebApplication application)
|
||||||
{
|
{
|
||||||
MapBase(application);
|
MapBase(application);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace Moonlight.Api;
|
|
||||||
|
|
||||||
public static class StartupHandler
|
|
||||||
{
|
|
||||||
public static async Task RunAsync(string[] args, MoonlightPlugin[] plugins)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Starting with: {string.Join(", ", plugins.Select(x => x.GetType().FullName))}");
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
|
|
||||||
// Setting up context
|
|
||||||
foreach (var plugin in plugins)
|
|
||||||
plugin.Initialize(plugins);
|
|
||||||
|
|
||||||
// Stage 1: Pre Build
|
|
||||||
foreach (var startup in plugins)
|
|
||||||
startup.PreBuild(builder);
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// Stage 2: Post Build
|
|
||||||
foreach (var startup in plugins)
|
|
||||||
startup.PostBuild(app);
|
|
||||||
|
|
||||||
// Stage 3: Post Middleware
|
|
||||||
foreach (var startup in plugins)
|
|
||||||
startup.PostMiddleware(app);
|
|
||||||
|
|
||||||
// Frontend debugging
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
app.UseWebAssemblyDebugging();
|
|
||||||
|
|
||||||
// Frontend hosting
|
|
||||||
app.UseBlazorFrameworkFiles();
|
|
||||||
app.UseStaticFiles();
|
|
||||||
|
|
||||||
await app.RunAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Moonlight.Frontend.Interfaces;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Configuration;
|
|
||||||
|
|
||||||
public class LayoutMiddlewareOptions
|
|
||||||
{
|
|
||||||
public IReadOnlyList<Type> Components => InnerComponents;
|
|
||||||
|
|
||||||
private readonly List<Type> InnerComponents = new();
|
|
||||||
|
|
||||||
public void Add<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : LayoutMiddlewareBase
|
|
||||||
{
|
|
||||||
InnerComponents.Add(typeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Insert<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(int index) where T : LayoutMiddlewareBase
|
|
||||||
{
|
|
||||||
InnerComponents.Insert(index, typeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Remove<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : LayoutMiddlewareBase
|
|
||||||
{
|
|
||||||
InnerComponents.Remove(typeof(T));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Configuration;
|
|
||||||
|
|
||||||
public class LayoutPageOptions
|
|
||||||
{
|
|
||||||
public IReadOnlyList<LayoutPageComponent> Components => InnerComponents;
|
|
||||||
|
|
||||||
private readonly List<LayoutPageComponent> InnerComponents = new();
|
|
||||||
|
|
||||||
public void Add<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(LayoutPageSlot slot, int order)
|
|
||||||
where T : ComponentBase
|
|
||||||
=> Add(typeof(T), slot, order);
|
|
||||||
|
|
||||||
public void Add(Type componentType, LayoutPageSlot slot, int order)
|
|
||||||
=> InnerComponents.Add(new LayoutPageComponent(componentType, order, slot));
|
|
||||||
|
|
||||||
public void Remove<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
|
|
||||||
where T : ComponentBase
|
|
||||||
=> Remove(typeof(T));
|
|
||||||
|
|
||||||
public void Remove(Type componentType)
|
|
||||||
=> InnerComponents.RemoveAll(x => x.ComponentType == componentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record LayoutPageComponent(Type ComponentType, int Order, LayoutPageSlot Slot);
|
|
||||||
|
|
||||||
public enum LayoutPageSlot
|
|
||||||
{
|
|
||||||
Header = 0,
|
|
||||||
Footer = 1
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Configuration;
|
|
||||||
|
|
||||||
public class NavigationAssemblyOptions
|
|
||||||
{
|
|
||||||
public List<Assembly> Assemblies { get; private set; } = new();
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Configuration;
|
|
||||||
|
|
||||||
public class SystemSettingsOptions
|
|
||||||
{
|
|
||||||
public IReadOnlyList<SystemSettingsPage> Components => InnerComponents;
|
|
||||||
|
|
||||||
private readonly List<SystemSettingsPage> InnerComponents = new();
|
|
||||||
|
|
||||||
public void Add<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TIcon,
|
|
||||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TComponent>(string name, string description,
|
|
||||||
int order)
|
|
||||||
where TIcon : ComponentBase where TComponent : ComponentBase
|
|
||||||
=> Add(name, description, order, typeof(TIcon), typeof(TComponent));
|
|
||||||
|
|
||||||
public void Add(string name, string description, int order, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type iconComponent, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type component)
|
|
||||||
=> InnerComponents.Add(new SystemSettingsPage(name, description, order, iconComponent, component));
|
|
||||||
|
|
||||||
public void Remove<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TComponent>()
|
|
||||||
where TComponent : ComponentBase
|
|
||||||
=> Remove(typeof(TComponent));
|
|
||||||
|
|
||||||
public void Remove([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type componentType)
|
|
||||||
=> InnerComponents.RemoveAll(x => x.ComponentType == componentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record SystemSettingsPage(
|
|
||||||
string Name,
|
|
||||||
string Description,
|
|
||||||
int Order,
|
|
||||||
Type IconComponentType,
|
|
||||||
Type ComponentType
|
|
||||||
);
|
|
||||||
28
Moonlight.Frontend/Constants.cs
Normal file
28
Moonlight.Frontend/Constants.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Moonlight.Shared.Http;
|
||||||
|
|
||||||
|
namespace Moonlight.Frontend;
|
||||||
|
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
public static JsonSerializerOptions SerializerOptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (InternalOptions != null)
|
||||||
|
return InternalOptions;
|
||||||
|
|
||||||
|
InternalOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add source generated options from shared project
|
||||||
|
InternalOptions.TypeInfoResolverChain.Add(SerializationContext.Default);
|
||||||
|
|
||||||
|
return InternalOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonSerializerOptions? InternalOptions;
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Net.Http.Json;
|
|
||||||
using Microsoft.AspNetCore.Components.Forms;
|
|
||||||
using Moonlight.Shared.Http.Responses;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Helpers;
|
|
||||||
|
|
||||||
public static class ProblemDetailsHelper
|
|
||||||
{
|
|
||||||
public static async Task HandleProblemDetailsAsync(HttpResponseMessage response, object model, ValidationMessageStore validationMessageStore)
|
|
||||||
{
|
|
||||||
var problemDetails = await response.Content.ReadFromJsonAsync<ProblemDetails>();
|
|
||||||
|
|
||||||
if (problemDetails == null)
|
|
||||||
response.EnsureSuccessStatusCode(); // Trigger exception when unable to parse
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(!string.IsNullOrEmpty(problemDetails.Detail))
|
|
||||||
validationMessageStore.Add(new FieldIdentifier(model, string.Empty), problemDetails.Detail);
|
|
||||||
|
|
||||||
if (problemDetails.Errors != null)
|
|
||||||
{
|
|
||||||
foreach (var error in problemDetails.Errors)
|
|
||||||
{
|
|
||||||
foreach (var message in error.Value)
|
|
||||||
validationMessageStore.Add(new FieldIdentifier(model, error.Key), message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,8 +28,6 @@ public sealed class PermissionProvider : IPermissionProvider
|
|||||||
new Permission(Permissions.System.Info, "Info", "View system info"),
|
new Permission(Permissions.System.Info, "Info", "View system info"),
|
||||||
new Permission(Permissions.System.Diagnose, "Diagnose", "Run diagnostics"),
|
new Permission(Permissions.System.Diagnose, "Diagnose", "Run diagnostics"),
|
||||||
new Permission(Permissions.System.Versions, "Versions", "Look at the available versions"),
|
new Permission(Permissions.System.Versions, "Versions", "Look at the available versions"),
|
||||||
new Permission(Permissions.System.Instance, "Instance", "Update the moonlight instance and add plugins"),
|
|
||||||
new Permission(Permissions.System.Settings, "Settings", "Change settings of the instance"),
|
|
||||||
]),
|
]),
|
||||||
new PermissionCategory("API Keys", typeof(KeyIcon), [
|
new PermissionCategory("API Keys", typeof(KeyIcon), [
|
||||||
new Permission(Permissions.ApiKeys.Create, "Create", "Create new API keys"),
|
new Permission(Permissions.ApiKeys.Create, "Create", "Create new API keys"),
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Interfaces;
|
|
||||||
|
|
||||||
public abstract class LayoutMiddlewareBase : ComponentBase
|
|
||||||
{
|
|
||||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
using Moonlight.Shared.Http.Requests.ApiKeys;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.ApiKeys;
|
using Moonlight.Shared.Http.Responses.ApiKeys;
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Mappers;
|
namespace Moonlight.Frontend.Mappers;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Roles;
|
using Moonlight.Shared.Http.Requests.Roles;
|
||||||
using Moonlight.Shared.Http.Responses.Admin;
|
using Moonlight.Shared.Http.Responses.Admin;
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Themes;
|
using Moonlight.Shared.Http.Requests.Themes;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Themes;
|
using Moonlight.Shared.Http.Responses.Themes;
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Mappers;
|
namespace Moonlight.Frontend.Mappers;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Users;
|
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Users;
|
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
using Moonlight.Shared.Http.Requests.Users;
|
||||||
|
using Moonlight.Shared.Http.Responses.Users;
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Mappers;
|
namespace Moonlight.Frontend.Mappers;
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,8 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1"/>
|
||||||
<PackageReference Include="Riok.Mapperly" Version="4.3.1-next.0"/>
|
<PackageReference Include="Riok.Mapperly" Version="4.3.1-next.0"/>
|
||||||
<PackageReference Include="ShadcnBlazor" Version="1.0.13" />
|
<PackageReference Include="ShadcnBlazor" Version="1.0.9" />
|
||||||
<PackageReference Include="ShadcnBlazor.Extras" Version="1.0.13" />
|
<PackageReference Include="ShadcnBlazor.Extras" Version="1.0.9" />
|
||||||
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -36,6 +35,5 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Styles/*" Pack="true" PackagePath="Styles/" />
|
<None Include="Styles/*" Pack="true" PackagePath="Styles/" />
|
||||||
<None Include="Moonlight.Frontend.targets" Pack="true" PackagePath="build\Moonlight.Frontend.targets" />
|
<None Include="Moonlight.Frontend.targets" Pack="true" PackagePath="build\Moonlight.Frontend.targets" />
|
||||||
<None Include="Moonlight.Frontend.targets" Pack="true" PackagePath="buildTransitive\Moonlight.Frontend.targets" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
</MoonlightCssClassDir>
|
</MoonlightCssClassDir>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Target Name="Moonlight_CopyContents" BeforeTargets="Build">
|
<Target Name="CopyContents" BeforeTargets="Build">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Files Include="$(MSBuildThisFileDirectory)..\Styles\**\*" />
|
<Files Include="$(MSBuildThisFileDirectory)..\Styles\**\*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|
||||||
using SimplePlugin.Abstractions;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend;
|
|
||||||
|
|
||||||
public abstract class MoonlightPlugin : IPluginModule
|
|
||||||
{
|
|
||||||
protected MoonlightPlugin[] Plugins { get; private set; }
|
|
||||||
|
|
||||||
public void Initialize(MoonlightPlugin[] plugins)
|
|
||||||
{
|
|
||||||
Plugins = plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void PreBuild(WebAssemblyHostBuilder builder){}
|
|
||||||
public virtual void PostBuild(WebAssemblyHost application){}
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,7 @@ using System.Net.Http.Json;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.VisualBasic;
|
using Moonlight.Shared.Http.Responses.Auth;
|
||||||
using Moonlight.Shared.Http;
|
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Auth;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Services;
|
namespace Moonlight.Frontend.Services;
|
||||||
|
|
||||||
@@ -25,7 +23,7 @@ public class RemoteAuthProvider : AuthenticationStateProvider
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var claimResponses = await HttpClient.GetFromJsonAsync<ClaimDto[]>(
|
var claimResponses = await HttpClient.GetFromJsonAsync<ClaimDto[]>(
|
||||||
"api/auth/claims", SerializationContext.Default.Options
|
"api/auth/claims", Constants.SerializerOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
var claims = claimResponses!.Select(claim => new Claim(claim.Type, claim.Value));
|
var claims = claimResponses!.Select(claim => new Claim(claim.Type, claim.Value));
|
||||||
|
|||||||
9
Moonlight.Frontend/Startup/IAppStartup.cs
Normal file
9
Moonlight.Frontend/Startup/IAppStartup.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
|
|
||||||
|
namespace Moonlight.Frontend.Startup;
|
||||||
|
|
||||||
|
public interface IAppStartup
|
||||||
|
{
|
||||||
|
public void PreBuild(WebAssemblyHostBuilder builder);
|
||||||
|
public void PostBuild(WebAssemblyHost application);
|
||||||
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
using LucideBlazor;
|
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Moonlight.Frontend.Configuration;
|
|
||||||
using Moonlight.Frontend.Implementations;
|
using Moonlight.Frontend.Implementations;
|
||||||
using Moonlight.Frontend.Interfaces;
|
using Moonlight.Frontend.Interfaces;
|
||||||
using Moonlight.Frontend.Services;
|
using Moonlight.Frontend.Services;
|
||||||
using Moonlight.Frontend.UI;
|
using Moonlight.Frontend.UI;
|
||||||
using Moonlight.Frontend.UI.Admin.Settings;
|
|
||||||
using ShadcnBlazor;
|
using ShadcnBlazor;
|
||||||
using ShadcnBlazor.Extras;
|
using ShadcnBlazor.Extras;
|
||||||
|
|
||||||
@@ -28,19 +25,5 @@ public partial class Startup
|
|||||||
builder.Services.AddSingleton<ISidebarProvider, SidebarProvider>();
|
builder.Services.AddSingleton<ISidebarProvider, SidebarProvider>();
|
||||||
|
|
||||||
builder.Services.AddScoped<FrontendService>();
|
builder.Services.AddScoped<FrontendService>();
|
||||||
|
|
||||||
builder.Services.Configure<NavigationAssemblyOptions>(options =>
|
|
||||||
{
|
|
||||||
options.Assemblies.Add(typeof(Startup).Assembly);
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.Configure<SystemSettingsOptions>(options =>
|
|
||||||
{
|
|
||||||
options.Add<TextCursorInputIcon, WhiteLabelingSetting>(
|
|
||||||
"White Labeling",
|
|
||||||
"Settings for white labeling your moonlight instance",
|
|
||||||
0
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using SimplePlugin.Abstractions;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Startup;
|
namespace Moonlight.Frontend.Startup;
|
||||||
|
|
||||||
[PluginModule]
|
public partial class Startup : IAppStartup
|
||||||
public partial class Startup : MoonlightPlugin
|
|
||||||
{
|
{
|
||||||
public override void PreBuild(WebAssemblyHostBuilder builder)
|
public void PreBuild(WebAssemblyHostBuilder builder)
|
||||||
{
|
{
|
||||||
AddBase(builder);
|
AddBase(builder);
|
||||||
AddAuth(builder);
|
AddAuth(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PostBuild(WebAssemblyHost application)
|
public void PostBuild(WebAssemblyHost application)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|
||||||
|
|
||||||
namespace Moonlight.Frontend;
|
|
||||||
|
|
||||||
public static class StartupHandler
|
|
||||||
{
|
|
||||||
public static async Task RunAsync(string[] args, MoonlightPlugin[] plugins)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Starting with: {string.Join(", ", plugins.Select(x => x.GetType().FullName))}");
|
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
|
||||||
|
|
||||||
// Setting up context
|
|
||||||
foreach (var plugin in plugins)
|
|
||||||
plugin.Initialize(plugins);
|
|
||||||
|
|
||||||
// Stage 1: Pre Build
|
|
||||||
foreach (var plugin in plugins)
|
|
||||||
plugin.PreBuild(builder);
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// Stage 2: Post Build
|
|
||||||
foreach(var plugin in plugins)
|
|
||||||
plugin.PostBuild(app);
|
|
||||||
|
|
||||||
await app.RunAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
@using Moonlight.Frontend.Helpers
|
@using Moonlight.Frontend.UI.Admin.Components
|
||||||
@using Moonlight.Frontend.UI.Admin.Components
|
@using Moonlight.Shared.Http.Requests.ApiKeys
|
||||||
@using Moonlight.Shared.Http
|
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.ApiKeys
|
|
||||||
@using Moonlight.Shared.Http.Responses
|
|
||||||
@using ShadcnBlazor.Dialogs
|
@using ShadcnBlazor.Dialogs
|
||||||
@using ShadcnBlazor.Extras.Forms
|
@using ShadcnBlazor.Extras.Common
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.FormHandlers
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
@using ShadcnBlazor.Inputs
|
||||||
|
@using ShadcnBlazor.Labels
|
||||||
|
|
||||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject ToastService ToastService
|
|
||||||
|
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create new API key</DialogTitle>
|
<DialogTitle>Create new API key</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -21,80 +15,56 @@
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnSubmitAsync">
|
<FormHandler @ref="FormHandler" Model="Request" OnValidSubmit="SubmitAsync">
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<FormValidationSummary />
|
||||||
|
|
||||||
<FieldGroup>
|
<div class="grid gap-2">
|
||||||
<DataAnnotationsValidator/>
|
<Label for="keyName">Name</Label>
|
||||||
<FormValidationSummary/>
|
<InputField @bind-Value="Request.Name" id="keyName" placeholder="My API key" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<FieldSet>
|
<div class="grid gap-2">
|
||||||
<Field>
|
<Label for="keyDescription">Description</Label>
|
||||||
<FieldLabel for="keyName">Name</FieldLabel>
|
<textarea
|
||||||
<TextInputField @bind-Value="Request.Name" id="keyName" placeholder="My API key"/>
|
@bind="Request.Description"
|
||||||
</Field>
|
id="keyDescription"
|
||||||
<Field>
|
maxlength="100"
|
||||||
<FieldLabel for="keyDescription">Description</FieldLabel>
|
class="border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
|
||||||
<TextareaInputField @bind-Value="Request.Description" id="keyDescription" placeholder="What this key is for"/>
|
placeholder="What this key is for">
|
||||||
</Field>
|
|
||||||
<Field>
|
</textarea>
|
||||||
<FieldLabel for="keyValidUntil">Valid until</FieldLabel>
|
</div>
|
||||||
<DateTimeInputField @bind-Value="Request.ValidUntil" id="keyValidUntil" />
|
|
||||||
</Field>
|
<div class="grid gap-2">
|
||||||
<Field>
|
<Label>Permissions</Label>
|
||||||
<FieldLabel>Permissions</FieldLabel>
|
<PermissionSelector Permissions="Permissions" />
|
||||||
<FieldContent>
|
</div>
|
||||||
<PermissionSelector Permissions="Permissions"/>
|
</div>
|
||||||
</FieldContent>
|
</FormHandler>
|
||||||
</Field>
|
|
||||||
</FieldSet>
|
<DialogFooter ClassName="justify-end gap-x-1">
|
||||||
<Field Orientation="FieldOrientation.Horizontal" ClassName="justify-end">
|
<WButtom OnClick="() => FormHandler.SubmitAsync()">Save changes</WButtom>
|
||||||
<SubmitButton>Save changes</SubmitButton>
|
</DialogFooter>
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</EnhancedEditForm>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter] public Func<Task> OnSubmit { get; set; }
|
[Parameter] public Func<CreateApiKeyDto, Task> OnSubmit { get; set; }
|
||||||
|
|
||||||
private CreateApiKeyDto Request;
|
private CreateApiKeyDto Request;
|
||||||
|
private FormHandler FormHandler;
|
||||||
|
|
||||||
private List<string> Permissions = new();
|
private List<string> Permissions = new();
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
Request = new()
|
Request = new();
|
||||||
{
|
|
||||||
Permissions = [],
|
|
||||||
ValidUntil = DateTimeOffset.UtcNow
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
Request.Permissions = Permissions.ToArray();
|
Request.Permissions = Permissions.ToArray();
|
||||||
Request.ValidUntil = Request.ValidUntil.ToUniversalTime();
|
await OnSubmit.Invoke(Request);
|
||||||
|
|
||||||
var response = await HttpClient.PostAsJsonAsync(
|
|
||||||
"/api/admin/apiKeys",
|
|
||||||
Request,
|
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync(
|
|
||||||
"API Key creation",
|
|
||||||
$"Successfully created API key {Request.Name}"
|
|
||||||
);
|
|
||||||
|
|
||||||
await OnSubmit.Invoke();
|
|
||||||
|
|
||||||
await CloseAsync();
|
await CloseAsync();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
@using Moonlight.Frontend.Helpers
|
|
||||||
@using Moonlight.Frontend.UI.Admin.Components
|
@using Moonlight.Frontend.UI.Admin.Components
|
||||||
@using Moonlight.Shared.Http
|
@using Moonlight.Shared.Http.Requests.Roles
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Roles
|
@using ShadcnBlazor.Buttons
|
||||||
@using ShadcnBlazor.Dialogs
|
@using ShadcnBlazor.Dialogs
|
||||||
@using ShadcnBlazor.Extras.Forms
|
@using ShadcnBlazor.Extras.Common
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.FormHandlers
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
@using ShadcnBlazor.Inputs
|
||||||
|
@using ShadcnBlazor.Labels
|
||||||
|
|
||||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject ToastService ToastService
|
|
||||||
|
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
Create new role
|
Create new role
|
||||||
@@ -22,43 +18,49 @@
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnSubmitAsync">
|
<FormHandler @ref="FormHandler" Model="Request" OnValidSubmit="OnSubmitAsync">
|
||||||
<FieldGroup>
|
<div class="flex flex-col gap-6">
|
||||||
<FormValidationSummary/>
|
|
||||||
<DataAnnotationsValidator/>
|
|
||||||
|
|
||||||
<FieldSet>
|
<FormValidationSummary/>
|
||||||
<Field>
|
|
||||||
<FieldLabel for="roleName">Name</FieldLabel>
|
<div class="grid gap-2">
|
||||||
<TextInputField
|
<Label for="roleName">Name</Label>
|
||||||
@bind-Value="Request.Name"
|
<InputField
|
||||||
id="roleName"
|
@bind-Value="Request.Name"
|
||||||
placeholder="My fancy role"/>
|
id="roleName"
|
||||||
</Field>
|
placeholder="My fancy role"/>
|
||||||
<Field>
|
</div>
|
||||||
<FieldLabel for="keyDescription">Description</FieldLabel>
|
|
||||||
<TextareaInputField @bind-Value="Request.Description" id="keyDescription"
|
<div class="grid gap-2">
|
||||||
placeholder="Describe what the role should be used for"/>
|
<Label for="roleDescription">Description</Label>
|
||||||
</Field>
|
<textarea
|
||||||
<Field>
|
@bind="Request.Description"
|
||||||
<FieldLabel>Permissions</FieldLabel>
|
id="roleDescription"
|
||||||
<FieldContent>
|
maxlength="100"
|
||||||
<PermissionSelector Permissions="Permissions"/>
|
class="border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
|
||||||
</FieldContent>
|
placeholder="Describe what the role should be used for">
|
||||||
</Field>
|
|
||||||
</FieldSet>
|
</textarea>
|
||||||
<Field Orientation="FieldOrientation.Horizontal" ClassName="justify-end">
|
</div>
|
||||||
<SubmitButton>Save changes</SubmitButton>
|
|
||||||
</Field>
|
<div class="grid gap-2">
|
||||||
</FieldGroup>
|
<Label>Permissions</Label>
|
||||||
</EnhancedEditForm>
|
<PermissionSelector Permissions="Permissions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormHandler>
|
||||||
|
|
||||||
|
<DialogFooter ClassName="justify-end gap-x-1">
|
||||||
|
<WButtom OnClick="SubmitAsync">Save changes</WButtom>
|
||||||
|
</DialogFooter>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter] public Func<Task> OnSubmit { get; set; }
|
[Parameter] public Func<CreateRoleDto, Task> OnSubmit { get; set; }
|
||||||
|
|
||||||
private CreateRoleDto Request;
|
private CreateRoleDto Request;
|
||||||
private List<string> Permissions;
|
private List<string> Permissions;
|
||||||
|
private FormHandler FormHandler;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
@@ -70,27 +72,15 @@
|
|||||||
Permissions = new();
|
Permissions = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
Request.Permissions = Permissions.ToArray();
|
Request.Permissions = Permissions.ToArray();
|
||||||
|
await FormHandler.SubmitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
var response = await HttpClient.PostAsJsonAsync(
|
private async Task OnSubmitAsync()
|
||||||
"api/admin/roles",
|
{
|
||||||
Request,
|
await OnSubmit.Invoke(Request);
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync("Role creation", $"Role {Request.Name} has been successfully created");
|
|
||||||
|
|
||||||
await OnSubmit.Invoke();
|
|
||||||
await CloseAsync();
|
await CloseAsync();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
@using Moonlight.Frontend.Helpers
|
@using Moonlight.Shared.Http.Requests.Users
|
||||||
@using Moonlight.Shared.Http
|
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Users
|
|
||||||
@using Moonlight.Shared.Http.Responses
|
|
||||||
@using ShadcnBlazor.Dialogs
|
@using ShadcnBlazor.Dialogs
|
||||||
@using ShadcnBlazor.Extras.Forms
|
@using ShadcnBlazor.Extras.Common
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.FormHandlers
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
@using ShadcnBlazor.Inputs
|
||||||
|
@using ShadcnBlazor.Labels
|
||||||
|
|
||||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject ToastService ToastService
|
|
||||||
|
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
Create new user
|
Create new user
|
||||||
@@ -22,67 +16,50 @@
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnSubmitAsync">
|
<FormHandler @ref="FormHandler" Model="Request" OnValidSubmit="SubmitAsync">
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
|
||||||
<FieldGroup>
|
|
||||||
<FormValidationSummary/>
|
<FormValidationSummary/>
|
||||||
<DataAnnotationsValidator />
|
|
||||||
|
|
||||||
<FieldSet>
|
<div class="grid gap-2">
|
||||||
<Field>
|
<Label for="username">Username</Label>
|
||||||
<FieldLabel for="username">Username</FieldLabel>
|
<InputField
|
||||||
<TextInputField
|
@bind-Value="Request.Username"
|
||||||
@bind-Value="Request.Username"
|
id="username"
|
||||||
id="username"
|
placeholder="Name of the user"/>
|
||||||
placeholder="Name of the user"/>
|
</div>
|
||||||
</Field>
|
|
||||||
<Field>
|
<div class="grid gap-2">
|
||||||
<FieldLabel for="emailAddress">Email Address</FieldLabel>
|
<Label for="emailAddress">Email Address</Label>
|
||||||
<TextInputField
|
<InputField
|
||||||
@bind-Value="Request.Email"
|
@bind-Value="Request.Email"
|
||||||
id="emailAddress"
|
id="emailAddress"
|
||||||
placeholder="email@of.user"/>
|
Type="email"
|
||||||
</Field>
|
placeholder="email@of.user"/>
|
||||||
</FieldSet>
|
</div>
|
||||||
<Field Orientation="FieldOrientation.Horizontal" ClassName="justify-end">
|
</div>
|
||||||
<SubmitButton>Save changes</SubmitButton>
|
</FormHandler>
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
<DialogFooter ClassName="justify-end gap-x-1">
|
||||||
</EnhancedEditForm>
|
<WButtom OnClick="_ => FormHandler.SubmitAsync()">Save changes</WButtom>
|
||||||
|
</DialogFooter>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter] public Func<Task> OnCompleted { get; set; }
|
[Parameter] public Func<CreateUserDto, Task> OnSubmit { get; set; }
|
||||||
|
|
||||||
private CreateUserDto Request;
|
private CreateUserDto Request;
|
||||||
|
private FormHandler FormHandler;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
Request = new();
|
Request = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
var response = await HttpClient.PostAsJsonAsync(
|
await OnSubmit.Invoke(Request);
|
||||||
"/api/admin/users",
|
|
||||||
Request,
|
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync(
|
|
||||||
"User creation",
|
|
||||||
$"Successfully created user {Request.Username}"
|
|
||||||
);
|
|
||||||
|
|
||||||
await OnCompleted.Invoke();
|
|
||||||
await CloseAsync();
|
await CloseAsync();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@using LucideBlazor
|
@using LucideBlazor
|
||||||
@using Moonlight.Shared.Http.Responses
|
@using Moonlight.Shared.Http.Responses
|
||||||
@using Moonlight.Shared.Http.Responses.Admin
|
@using Moonlight.Shared.Http.Responses.Admin
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.Users
|
@using Moonlight.Shared.Http.Responses.Users
|
||||||
@using ShadcnBlazor.Buttons
|
@using ShadcnBlazor.Buttons
|
||||||
@using ShadcnBlazor.DataGrids
|
@using ShadcnBlazor.DataGrids
|
||||||
@using ShadcnBlazor.Dialogs
|
@using ShadcnBlazor.Dialogs
|
||||||
@@ -32,9 +32,9 @@
|
|||||||
SearchPlaceholder="Search user"
|
SearchPlaceholder="Search user"
|
||||||
ValueSelector="dto => dto.Username"
|
ValueSelector="dto => dto.Username"
|
||||||
Source="LoadUsersAsync"/>
|
Source="LoadUsersAsync"/>
|
||||||
<WButton OnClick="AddAsync" Variant="ButtonVariant.Outline" Size="ButtonSize.Icon">
|
<WButtom OnClick="AddAsync" Variant="ButtonVariant.Outline" Size="ButtonSize.Icon">
|
||||||
<PlusIcon/>
|
<PlusIcon/>
|
||||||
</WButton>
|
</WButtom>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
@@ -50,9 +50,9 @@
|
|||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div class="flex justify-end me-1.5">
|
<div class="flex justify-end me-1.5">
|
||||||
<WButton OnClick="_ => RemoveAsync(context)" Variant="ButtonVariant.Destructive" Size="ButtonSize.Icon">
|
<WButtom OnClick="_ => RemoveAsync(context)" Variant="ButtonVariant.Destructive" Size="ButtonSize.Icon">
|
||||||
<TrashIcon/>
|
<TrashIcon/>
|
||||||
</WButton>
|
</WButtom>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
@using Moonlight.Frontend.Helpers
|
@using Moonlight.Frontend.Mappers
|
||||||
@using Moonlight.Frontend.Mappers
|
|
||||||
@using Moonlight.Frontend.UI.Admin.Components
|
@using Moonlight.Frontend.UI.Admin.Components
|
||||||
@using Moonlight.Shared.Http
|
@using Moonlight.Shared.Http.Requests.ApiKeys
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.ApiKeys
|
@using Moonlight.Shared.Http.Responses.ApiKeys
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.ApiKeys
|
|
||||||
@using ShadcnBlazor.Dialogs
|
@using ShadcnBlazor.Dialogs
|
||||||
@using ShadcnBlazor.Extras.Forms
|
@using ShadcnBlazor.Extras.Common
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.FormHandlers
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
@using ShadcnBlazor.Inputs
|
||||||
|
@using ShadcnBlazor.Labels
|
||||||
|
|
||||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject ToastService ToastService
|
|
||||||
|
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Update API key</DialogTitle>
|
<DialogTitle>Update API key</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -22,43 +17,45 @@
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnSubmitAsync">
|
<FormHandler @ref="FormHandler" Model="Request" OnValidSubmit="SubmitAsync">
|
||||||
<FieldGroup>
|
<div class="flex flex-col gap-6">
|
||||||
<FormValidationSummary/>
|
<FormValidationSummary />
|
||||||
<DataAnnotationsValidator/>
|
|
||||||
|
|
||||||
<FieldSet>
|
<div class="grid gap-2">
|
||||||
<Field>
|
<Label for="keyName">Name</Label>
|
||||||
<FieldLabel for="keyName">Name</FieldLabel>
|
<InputField @bind-Value="Request.Name" id="keyName" placeholder="My API key" />
|
||||||
<TextInputField @bind-Value="Request.Name" id="keyName" placeholder="My API key"/>
|
</div>
|
||||||
</Field>
|
|
||||||
<Field>
|
<div class="grid gap-2">
|
||||||
<FieldLabel for="keyDescription">Description</FieldLabel>
|
<Label for="keyDescription">Description</Label>
|
||||||
<TextareaInputField @bind-Value="Request.Description" id="keyDescription" placeholder="What this key is for"/>
|
<textarea
|
||||||
</Field>
|
@bind="Request.Description"
|
||||||
<Field>
|
id="keyDescription"
|
||||||
<FieldLabel for="keyValidUntil">Valid until</FieldLabel>
|
maxlength="100"
|
||||||
<DateTimeInputField @bind-Value="Request.ValidUntil" id="keyValidUntil" />
|
class="border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
|
||||||
</Field>
|
placeholder="What this key is for">
|
||||||
<Field>
|
|
||||||
<FieldLabel>Permissions</FieldLabel>
|
</textarea>
|
||||||
<FieldContent>
|
</div>
|
||||||
<PermissionSelector Permissions="Permissions"/>
|
|
||||||
</FieldContent>
|
<div class="grid gap-2">
|
||||||
</Field>
|
<Label>Permissions</Label>
|
||||||
</FieldSet>
|
<PermissionSelector Permissions="Permissions" />
|
||||||
<Field Orientation="FieldOrientation.Horizontal" ClassName="justify-end">
|
</div>
|
||||||
<SubmitButton>Save changes</SubmitButton>
|
</div>
|
||||||
</Field>
|
</FormHandler>
|
||||||
</FieldGroup>
|
|
||||||
</EnhancedEditForm>
|
<DialogFooter ClassName="justify-end gap-x-1">
|
||||||
|
<WButtom OnClick="_ => FormHandler.SubmitAsync()">Save changes</WButtom>
|
||||||
|
</DialogFooter>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter] public Func<Task> OnSubmit { get; set; }
|
[Parameter] public Func<UpdateApiKeyDto, Task> OnSubmit { get; set; }
|
||||||
[Parameter] public ApiKeyDto Key { get; set; }
|
[Parameter] public ApiKeyDto Key { get; set; }
|
||||||
|
|
||||||
private UpdateApiKeyDto Request;
|
private UpdateApiKeyDto Request;
|
||||||
|
private FormHandler FormHandler;
|
||||||
private List<string> Permissions = new();
|
private List<string> Permissions = new();
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
@@ -67,31 +64,10 @@
|
|||||||
Permissions = Key.Permissions.ToList();
|
Permissions = Key.Permissions.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
Request.Permissions = Permissions.ToArray();
|
Request.Permissions = Permissions.ToArray();
|
||||||
Request.ValidUntil = Request.ValidUntil.ToUniversalTime();
|
await OnSubmit.Invoke(Request);
|
||||||
|
|
||||||
var response = await HttpClient.PatchAsJsonAsync(
|
|
||||||
$"/api/admin/apiKeys/{Key.Id}",
|
|
||||||
Request,
|
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync(
|
|
||||||
"API Key update",
|
|
||||||
$"Successfully updated API key {Request.Name}"
|
|
||||||
);
|
|
||||||
|
|
||||||
await OnSubmit.Invoke();
|
|
||||||
await CloseAsync();
|
await CloseAsync();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,38 +1,37 @@
|
|||||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||||
|
|
||||||
@using System.Text.Json
|
|
||||||
@using LucideBlazor
|
@using LucideBlazor
|
||||||
@using Moonlight.Shared.Http
|
|
||||||
@using Moonlight.Shared.Http.Events
|
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.ContainerHelper
|
|
||||||
@using ShadcnBlazor.Buttons
|
|
||||||
@using ShadcnBlazor.Dialogs
|
@using ShadcnBlazor.Dialogs
|
||||||
|
@using ShadcnBlazor.Extras.AlertDialogs
|
||||||
@using ShadcnBlazor.Progresses
|
@using ShadcnBlazor.Progresses
|
||||||
@using ShadcnBlazor.Spinners
|
@using ShadcnBlazor.Spinners
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
@inject AlertDialogService AlertService
|
||||||
|
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
Updating instance to @Version...
|
Updating...
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 xl:grid-cols-2 w-full gap-5">
|
<div class="text-base flex flex-col p-2 gap-y-0.5">
|
||||||
<div class="text-base flex flex-col p-2 gap-y-1">
|
@for (var i = 0; i < Steps.Length; i++)
|
||||||
@for (var i = 0; i < Steps.Length; i++)
|
{
|
||||||
|
if (CurrentStep == i)
|
||||||
{
|
{
|
||||||
if (CurrentStep == i)
|
<div class="flex flex-row items-center gap-x-2">
|
||||||
|
<Spinner ClassName="size-4" />
|
||||||
|
<span>
|
||||||
|
@Steps[i]
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (i < CurrentStep)
|
||||||
{
|
{
|
||||||
<div class="flex flex-row items-center gap-x-1">
|
<div class="flex flex-row items-center gap-x-2">
|
||||||
@if (IsFailed)
|
<CheckIcon ClassName="text-green-500 size-4" />
|
||||||
{
|
|
||||||
<CircleXIcon ClassName="text-red-500 size-5"/>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Spinner ClassName="size-5"/>
|
|
||||||
}
|
|
||||||
<span>
|
<span>
|
||||||
@Steps[i]
|
@Steps[i]
|
||||||
</span>
|
</span>
|
||||||
@@ -40,205 +39,81 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (i < CurrentStep)
|
<div class="text-muted-foreground flex flex-row items-center gap-x-2">
|
||||||
{
|
<span class="size-4"></span>
|
||||||
<div class="flex flex-row items-center gap-x-1 text-muted-foreground">
|
@Steps[i]
|
||||||
<CircleCheckIcon ClassName="text-green-500 size-5"/>
|
</div>
|
||||||
<span>
|
|
||||||
@Steps[i]
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="text-muted-foreground flex flex-row items-center gap-x-1">
|
|
||||||
<span class="size-5"></span>
|
|
||||||
@Steps[i]
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
}
|
||||||
<div class="bg-black text-white rounded-lg font-mono h-96 flex flex-col-reverse overflow-auto p-3 scrollbar-thin">
|
|
||||||
@for (var i = LogLines.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
<div>
|
|
||||||
@LogLines[i]
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (CurrentStep == Steps.Length || IsFailed)
|
<DialogFooter>
|
||||||
{
|
<Progress Value="@Progress"></Progress>
|
||||||
<DialogFooter ClassName="justify-end">
|
</DialogFooter>
|
||||||
<Button Variant="ButtonVariant.Outline" @onclick="CloseAsync">Close</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<DialogFooter>
|
|
||||||
<Progress ClassName="my-1" Value="@Progress"></Progress>
|
|
||||||
</DialogFooter>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter] public string Version { get; set; }
|
private int Progress = 0;
|
||||||
[Parameter] public bool NoBuildCache { get; set; }
|
|
||||||
|
|
||||||
private bool IsFailed;
|
|
||||||
private int Progress;
|
|
||||||
private int CurrentStep;
|
private int CurrentStep;
|
||||||
|
|
||||||
private readonly string[] Steps =
|
private string[] Steps =
|
||||||
[
|
[
|
||||||
"Checking", // 0
|
"Preparing",
|
||||||
"Updating configuration files", // 1
|
"Updating configuration files",
|
||||||
"Starting rebuild task", // 2
|
"Building docker image",
|
||||||
"Building docker image", // 3
|
"Redeploying container instance",
|
||||||
"Redeploying container instance", // 4
|
"Waiting for container instance to start up",
|
||||||
"Waiting for container instance to start up", // 5
|
"Update complete"
|
||||||
"Update complete" // 6
|
|
||||||
];
|
];
|
||||||
|
|
||||||
private readonly List<string?> LogLines = new();
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (!firstRender)
|
if (!firstRender)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Checking
|
|
||||||
CurrentStep = 0;
|
CurrentStep = 0;
|
||||||
Progress = 0;
|
Progress = 0;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
|
|
||||||
// Update configuration
|
|
||||||
CurrentStep = 1;
|
CurrentStep = 1;
|
||||||
Progress = 20;
|
Progress = 20;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
await HttpClient.PostAsJsonAsync("api/admin/ch/version", new SetVersionDto()
|
await Task.Delay(6000);
|
||||||
{
|
|
||||||
Version = Version
|
|
||||||
}, SerializationContext.Default.Options);
|
|
||||||
|
|
||||||
// Starting rebuild task
|
|
||||||
CurrentStep = 2;
|
CurrentStep = 2;
|
||||||
Progress = 30;
|
Progress = 40;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, "api/admin/ch/rebuild");
|
await Task.Delay(2000);
|
||||||
|
|
||||||
request.Content = JsonContent.Create(
|
CurrentStep = 3;
|
||||||
new RequestRebuildDto(NoBuildCache),
|
Progress = 60;
|
||||||
null,
|
await InvokeAsync(StateHasChanged);
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
var response = await HttpClient.SendAsync(
|
await Task.Delay(4000);
|
||||||
request,
|
|
||||||
HttpCompletionOption.ResponseHeadersRead
|
|
||||||
);
|
|
||||||
|
|
||||||
await using var responseStream = await response.Content.ReadAsStreamAsync();
|
CurrentStep = 4;
|
||||||
using var streamReader = new StreamReader(responseStream);
|
Progress = 80;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
do
|
await Task.Delay(4000);
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var line = await streamReader.ReadLineAsync();
|
|
||||||
|
|
||||||
if (line == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var data = line.Trim("data: ");
|
|
||||||
var deserializedData = JsonSerializer.Deserialize<RebuildEventDto>(data, SerializationContext.Default.Options);
|
|
||||||
|
|
||||||
switch (deserializedData.Type)
|
|
||||||
{
|
|
||||||
case RebuildEventType.Log:
|
|
||||||
LogLines.Add(deserializedData.Data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RebuildEventType.Step:
|
|
||||||
|
|
||||||
switch (deserializedData.Data)
|
|
||||||
{
|
|
||||||
case "BuildImage":
|
|
||||||
|
|
||||||
// Building docker image
|
|
||||||
|
|
||||||
CurrentStep = 3;
|
|
||||||
Progress = 40;
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "ServiceDown":
|
|
||||||
|
|
||||||
// Redeploying container instance
|
|
||||||
|
|
||||||
CurrentStep = 4;
|
|
||||||
Progress = 60;
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RebuildEventType.Failed:
|
|
||||||
|
|
||||||
IsFailed = true;
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
|
|
||||||
// Waiting for container instance to start up
|
|
||||||
|
|
||||||
CurrentStep = 5;
|
CurrentStep = 5;
|
||||||
Progress = 90;
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
|
|
||||||
// Wait some time for instance to shut down
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
|
||||||
|
|
||||||
// Ping instance until its reachable again
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await HttpClient.GetStringAsync("api/ping");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update complete
|
|
||||||
CurrentStep = 7;
|
|
||||||
Progress = 100;
|
Progress = 100;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
await AlertService.SuccessAsync(
|
||||||
|
"Update completed",
|
||||||
|
"Update successfully completed. Please refresh the page to load new frontend changes"
|
||||||
|
);
|
||||||
|
|
||||||
|
await CloseAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
@using Moonlight.Frontend.Helpers
|
|
||||||
@using Moonlight.Frontend.Mappers
|
@using Moonlight.Frontend.Mappers
|
||||||
@using Moonlight.Frontend.UI.Admin.Components
|
@using Moonlight.Frontend.UI.Admin.Components
|
||||||
@using Moonlight.Shared.Http
|
@using Moonlight.Shared.Http.Requests.Roles
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Roles
|
|
||||||
@using Moonlight.Shared.Http.Responses.Admin
|
@using Moonlight.Shared.Http.Responses.Admin
|
||||||
|
@using ShadcnBlazor.Buttons
|
||||||
@using ShadcnBlazor.Dialogs
|
@using ShadcnBlazor.Dialogs
|
||||||
@using ShadcnBlazor.Extras.Forms
|
@using ShadcnBlazor.Extras.Common
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.FormHandlers
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
@using ShadcnBlazor.Inputs
|
||||||
|
@using ShadcnBlazor.Labels
|
||||||
|
|
||||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject ToastService ToastService
|
|
||||||
|
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
Update @Role.Name
|
Update @Role.Name
|
||||||
@@ -24,44 +20,50 @@
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnSubmitAsync">
|
<FormHandler @ref="FormHandler" Model="Request" OnValidSubmit="OnSubmitAsync">
|
||||||
<FieldGroup>
|
<div class="flex flex-col gap-6">
|
||||||
<FormValidationSummary/>
|
|
||||||
<DataAnnotationsValidator/>
|
|
||||||
|
|
||||||
<FieldSet>
|
<FormValidationSummary/>
|
||||||
<Field>
|
|
||||||
<FieldLabel for="roleName">Name</FieldLabel>
|
<div class="grid gap-2">
|
||||||
<TextInputField
|
<Label for="roleName">Name</Label>
|
||||||
@bind-Value="Request.Name"
|
<InputField
|
||||||
id="roleName"
|
@bind-Value="Request.Name"
|
||||||
placeholder="My fancy role"/>
|
id="roleName"
|
||||||
</Field>
|
placeholder="My fancy role"/>
|
||||||
<Field>
|
</div>
|
||||||
<FieldLabel for="keyDescription">Description</FieldLabel>
|
|
||||||
<TextareaInputField @bind-Value="Request.Description" id="keyDescription"
|
<div class="grid gap-2">
|
||||||
placeholder="Describe what the role should be used for"/>
|
<Label for="roleDescription">Description</Label>
|
||||||
</Field>
|
<textarea
|
||||||
<Field>
|
@bind="Request.Description"
|
||||||
<FieldLabel>Permissions</FieldLabel>
|
id="roleDescription"
|
||||||
<FieldContent>
|
maxlength="100"
|
||||||
<PermissionSelector Permissions="Permissions"/>
|
class="border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
|
||||||
</FieldContent>
|
placeholder="Describe what the role should be used for">
|
||||||
</Field>
|
|
||||||
</FieldSet>
|
</textarea>
|
||||||
<Field Orientation="FieldOrientation.Horizontal" ClassName="justify-end">
|
</div>
|
||||||
<SubmitButton>Save changes</SubmitButton>
|
|
||||||
</Field>
|
<div class="grid gap-2">
|
||||||
</FieldGroup>
|
<Label>Permissions</Label>
|
||||||
</EnhancedEditForm>
|
<PermissionSelector Permissions="Permissions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormHandler>
|
||||||
|
|
||||||
|
<DialogFooter ClassName="justify-end gap-x-1">
|
||||||
|
<WButtom OnClick="SubmitAsync">Save changes</WButtom>
|
||||||
|
</DialogFooter>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter] public Func<Task> OnSubmit { get; set; }
|
[Parameter] public Func<UpdateRoleDto, Task> OnSubmit { get; set; }
|
||||||
[Parameter] public RoleDto Role { get; set; }
|
[Parameter] public RoleDto Role { get; set; }
|
||||||
|
|
||||||
private UpdateRoleDto Request;
|
private UpdateRoleDto Request;
|
||||||
private List<string> Permissions;
|
private List<string> Permissions;
|
||||||
|
private FormHandler FormHandler;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
@@ -69,27 +71,15 @@
|
|||||||
Permissions = Role.Permissions.ToList();
|
Permissions = Role.Permissions.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
Request.Permissions = Permissions.ToArray();
|
Request.Permissions = Permissions.ToArray();
|
||||||
|
await FormHandler.SubmitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
var response = await HttpClient.PatchAsJsonAsync(
|
private async Task OnSubmitAsync()
|
||||||
$"api/admin/roles/{Role.Id}",
|
{
|
||||||
Request,
|
await OnSubmit.Invoke(Request);
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync("Role update", $"Role {Request.Name} has been successfully updated");
|
|
||||||
|
|
||||||
await OnSubmit.Invoke();
|
|
||||||
await CloseAsync();
|
await CloseAsync();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
@using Moonlight.Frontend.Helpers
|
@using Moonlight.Frontend.Mappers
|
||||||
@using Moonlight.Frontend.Mappers
|
@using Moonlight.Shared.Http.Requests.Users
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Users
|
@using Moonlight.Shared.Http.Responses.Users
|
||||||
@using Moonlight.Shared.Http.Responses
|
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.Users
|
|
||||||
@using ShadcnBlazor.Dialogs
|
@using ShadcnBlazor.Dialogs
|
||||||
@using ShadcnBlazor.Extras.Forms
|
@using ShadcnBlazor.Extras.Common
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.FormHandlers
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
@using ShadcnBlazor.Inputs
|
||||||
|
@using ShadcnBlazor.Labels
|
||||||
|
|
||||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject ToastService ToastService
|
|
||||||
|
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
Update @User.Username
|
Update @User.Username
|
||||||
@@ -23,66 +18,51 @@
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnSubmitAsync">
|
<FormHandler @ref="FormHandler" Model="Request" OnValidSubmit="SubmitAsync">
|
||||||
<FieldGroup>
|
<div class="flex flex-col gap-6">
|
||||||
<FormValidationSummary/>
|
|
||||||
<DataAnnotationsValidator/>
|
|
||||||
|
|
||||||
<FieldSet>
|
<FormValidationSummary/>
|
||||||
<Field>
|
|
||||||
<FieldLabel for="username">Username</FieldLabel>
|
<div class="grid gap-2">
|
||||||
<TextInputField
|
<Label for="username">Username</Label>
|
||||||
@bind-Value="Request.Username"
|
<InputField
|
||||||
id="username"
|
@bind-Value="Request.Username"
|
||||||
placeholder="Name of the user"/>
|
id="username"
|
||||||
</Field>
|
placeholder="Name of the user"/>
|
||||||
<Field>
|
</div>
|
||||||
<FieldLabel for="emailAddress">Email Address</FieldLabel>
|
|
||||||
<TextInputField
|
<div class="grid gap-2">
|
||||||
@bind-Value="Request.Email"
|
<Label for="emailAddress">Email Address</Label>
|
||||||
id="emailAddress"
|
<InputField
|
||||||
placeholder="email@of.user"/>
|
@bind-Value="Request.Email"
|
||||||
</Field>
|
id="emailAddress"
|
||||||
</FieldSet>
|
Type="email"
|
||||||
<Field Orientation="FieldOrientation.Horizontal" ClassName="justify-end">
|
placeholder="email@of.user"/>
|
||||||
<SubmitButton>Save changes</SubmitButton>
|
</div>
|
||||||
</Field>
|
</div>
|
||||||
</FieldGroup>
|
</FormHandler>
|
||||||
</EnhancedEditForm>
|
|
||||||
|
<DialogFooter ClassName="justify-end gap-x-1">
|
||||||
|
<WButtom OnClick="_ => FormHandler.SubmitAsync()">Save changes</WButtom>
|
||||||
|
</DialogFooter>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter] public Func<Task> OnCompleted { get; set; }
|
[Parameter] public Func<UpdateUserDto, Task> OnSubmit { get; set; }
|
||||||
[Parameter] public UserDto User { get; set; }
|
[Parameter] public UserDto User { get; set; }
|
||||||
|
|
||||||
private UpdateUserDto Request;
|
private UpdateUserDto Request;
|
||||||
|
private FormHandler FormHandler;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
Request = UserMapper.ToUpdate(User);
|
Request = UserMapper.ToUpdate(User);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
var response = await HttpClient.PatchAsJsonAsync(
|
await OnSubmit.Invoke(Request);
|
||||||
$"/api/admin/users/{User.Id}",
|
|
||||||
Request
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync(
|
|
||||||
"User update",
|
|
||||||
$"Successfully updated user {Request.Username}"
|
|
||||||
);
|
|
||||||
|
|
||||||
await OnCompleted.Invoke();
|
|
||||||
await CloseAsync();
|
await CloseAsync();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
@using LucideBlazor
|
|
||||||
@using Moonlight.Frontend.Helpers
|
|
||||||
@using Moonlight.Frontend.Services
|
|
||||||
@using Moonlight.Shared.Http
|
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Settings
|
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.Settings
|
|
||||||
@using ShadcnBlazor.Extras.Common
|
|
||||||
@using ShadcnBlazor.Extras.Forms
|
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject ToastService ToastService
|
|
||||||
@inject FrontendService FrontendService
|
|
||||||
|
|
||||||
<LazyLoader Load="LoadAsync">
|
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnValidSubmit">
|
|
||||||
<DataAnnotationsValidator />
|
|
||||||
|
|
||||||
<FieldSet>
|
|
||||||
<FormValidationSummary />
|
|
||||||
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel>Name</FieldLabel>
|
|
||||||
<TextInputField @bind-Value="Request.Name"/>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</FieldSet>
|
|
||||||
|
|
||||||
<SubmitButton ClassName="mt-3">
|
|
||||||
<SaveIcon/>
|
|
||||||
Save
|
|
||||||
</SubmitButton>
|
|
||||||
</EnhancedEditForm>
|
|
||||||
</LazyLoader>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
private SetWhiteLabelingDto Request;
|
|
||||||
|
|
||||||
private async Task LoadAsync(LazyLoader _)
|
|
||||||
{
|
|
||||||
var dto = await HttpClient.GetFromJsonAsync<WhiteLabelingDto>(
|
|
||||||
"api/admin/system/settings/whiteLabeling",
|
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
Request = new SetWhiteLabelingDto()
|
|
||||||
{
|
|
||||||
Name = dto!.Name
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> OnValidSubmit(EditContext editContext, ValidationMessageStore validationMessageStore)
|
|
||||||
{
|
|
||||||
var response = await HttpClient.PostAsJsonAsync(
|
|
||||||
"api/admin/system/settings/whiteLabeling",
|
|
||||||
Request,
|
|
||||||
SerializationContext.Default.Options
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await FrontendService.ReloadAsync();
|
|
||||||
await ToastService.SuccessAsync("Setting", "Successfully updated white labeling settings");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
@page "/admin"
|
@page "/admin"
|
||||||
@using LucideBlazor
|
@using LucideBlazor
|
||||||
@using Moonlight.Frontend.UI.Admin.Modals
|
@using Moonlight.Frontend.UI.Admin.Modals
|
||||||
@using Moonlight.Shared.Http
|
|
||||||
@using Moonlight.Shared.Http.Responses.Admin
|
@using Moonlight.Shared.Http.Responses.Admin
|
||||||
@using ShadcnBlazor.Buttons
|
@using ShadcnBlazor.Buttons
|
||||||
@using ShadcnBlazor.Cards
|
@using ShadcnBlazor.Cards
|
||||||
@@ -134,11 +133,7 @@
|
|||||||
{
|
{
|
||||||
<CardTitle ClassName="text-lg text-primary">Update available</CardTitle>
|
<CardTitle ClassName="text-lg text-primary">Update available</CardTitle>
|
||||||
<CardAction ClassName="self-center">
|
<CardAction ClassName="self-center">
|
||||||
<Button>
|
<Button @onclick="LaunchUpdateModalAsync">Update</Button>
|
||||||
<Slot>
|
|
||||||
<a href="/admin/system?tab=instance" @attributes="context">Update</a>
|
|
||||||
</Slot>
|
|
||||||
</Button>
|
|
||||||
</CardAction>
|
</CardAction>
|
||||||
}
|
}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -156,9 +151,14 @@
|
|||||||
if(!firstRender)
|
if(!firstRender)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
InfoResponse = await HttpClient.GetFromJsonAsync<SystemInfoDto>("api/admin/system/info", SerializationContext.Default.Options);
|
InfoResponse = await HttpClient.GetFromJsonAsync<SystemInfoDto>("api/admin/system/info", Constants.SerializerOptions);
|
||||||
IsInfoLoading = false;
|
IsInfoLoading = false;
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LaunchUpdateModalAsync() => await DialogService.LaunchAsync<UpdateInstanceModal>(onConfigure: model =>
|
||||||
|
{
|
||||||
|
model.ShowCloseButton = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
@using LucideBlazor
|
@using Moonlight.Shared.Http.Requests.ApiKeys
|
||||||
|
@using Moonlight.Shared.Http.Responses.ApiKeys
|
||||||
|
@using LucideBlazor
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using Microsoft.AspNetCore.Components.Authorization
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@using Moonlight.Frontend.UI.Admin.Modals
|
@using Moonlight.Frontend.UI.Admin.Modals
|
||||||
@using Moonlight.Shared
|
@using Moonlight.Shared
|
||||||
@using Moonlight.Shared.Http
|
|
||||||
@using Moonlight.Shared.Http.Requests
|
@using Moonlight.Shared.Http.Requests
|
||||||
@using Moonlight.Shared.Http.Responses
|
@using Moonlight.Shared.Http.Responses
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.ApiKeys
|
|
||||||
@using ShadcnBlazor.DataGrids
|
@using ShadcnBlazor.DataGrids
|
||||||
@using ShadcnBlazor.Dropdowns
|
@using ShadcnBlazor.Dropdowns
|
||||||
@using ShadcnBlazor.Extras.AlertDialogs
|
@using ShadcnBlazor.Extras.AlertDialogs
|
||||||
@@ -49,21 +49,7 @@
|
|||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<PropertyColumn IsFilterable="true"
|
<PropertyColumn IsFilterable="true"
|
||||||
Identifier="@nameof(ApiKeyDto.Description)" Field="k => k.Description" HeadClassName="hidden lg:table-cell" CellClassName="hidden lg:table-cell" />
|
Identifier="@nameof(ApiKeyDto.Description)" Field="k => k.Description"/>
|
||||||
<TemplateColumn Identifier="@nameof(ApiKeyDto.ValidUntil)" Title="Valid until" HeadClassName="hidden lg:table-cell">
|
|
||||||
<CellTemplate>
|
|
||||||
<TableCell ClassName="hidden lg:table-cell">
|
|
||||||
@{
|
|
||||||
var diff = context.ValidUntil - DateTimeOffset.UtcNow;
|
|
||||||
var text = string.Format(diff.TotalSeconds < 0 ? "Expired since {0}" : "Expires in {0}", Formatter.FormatDuration(diff));
|
|
||||||
}
|
|
||||||
|
|
||||||
<span>
|
|
||||||
@text
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
<TemplateColumn>
|
<TemplateColumn>
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -127,7 +113,7 @@
|
|||||||
|
|
||||||
var response = await HttpClient.GetFromJsonAsync<PagedData<ApiKeyDto>>(
|
var response = await HttpClient.GetFromJsonAsync<PagedData<ApiKeyDto>>(
|
||||||
$"api/admin/apiKeys{query}&filterOptions={filterOptions}",
|
$"api/admin/apiKeys{query}&filterOptions={filterOptions}",
|
||||||
SerializationContext.Default.Options
|
Constants.SerializerOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
return new DataGridResponse<ApiKeyDto>(response!.Data, response.TotalLength);
|
return new DataGridResponse<ApiKeyDto>(response!.Data, response.TotalLength);
|
||||||
@@ -137,8 +123,19 @@
|
|||||||
{
|
{
|
||||||
await DialogService.LaunchAsync<CreateApiKeyDialog>(parameters =>
|
await DialogService.LaunchAsync<CreateApiKeyDialog>(parameters =>
|
||||||
{
|
{
|
||||||
parameters[nameof(CreateApiKeyDialog.OnSubmit)] = async () =>
|
parameters[nameof(CreateApiKeyDialog.OnSubmit)] = async (CreateApiKeyDto dto) =>
|
||||||
{
|
{
|
||||||
|
await HttpClient.PostAsJsonAsync(
|
||||||
|
"/api/admin/apiKeys",
|
||||||
|
dto,
|
||||||
|
Constants.SerializerOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
await ToastService.SuccessAsync(
|
||||||
|
"API Key creation",
|
||||||
|
$"Successfully created API key {dto.Name}"
|
||||||
|
);
|
||||||
|
|
||||||
await Grid.RefreshAsync();
|
await Grid.RefreshAsync();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -149,8 +146,19 @@
|
|||||||
await DialogService.LaunchAsync<UpdateApiKeyDialog>(parameters =>
|
await DialogService.LaunchAsync<UpdateApiKeyDialog>(parameters =>
|
||||||
{
|
{
|
||||||
parameters[nameof(UpdateApiKeyDialog.Key)] = key;
|
parameters[nameof(UpdateApiKeyDialog.Key)] = key;
|
||||||
parameters[nameof(UpdateApiKeyDialog.OnSubmit)] = async () =>
|
parameters[nameof(UpdateApiKeyDialog.OnSubmit)] = async (UpdateApiKeyDto dto) =>
|
||||||
{
|
{
|
||||||
|
await HttpClient.PatchAsJsonAsync(
|
||||||
|
$"/api/admin/apiKeys/{key.Id}",
|
||||||
|
dto,
|
||||||
|
Constants.SerializerOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
await ToastService.SuccessAsync(
|
||||||
|
"API Key update",
|
||||||
|
$"Successfully updated API key {dto.Name}"
|
||||||
|
);
|
||||||
|
|
||||||
await Grid.RefreshAsync();
|
await Grid.RefreshAsync();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,10 +44,10 @@
|
|||||||
</Alert>
|
</Alert>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter ClassName="justify-end">
|
<CardFooter ClassName="justify-end">
|
||||||
<WButton OnClick="DiagnoseAsync" disabled="@(!AccessResult.Succeeded)">
|
<WButtom OnClick="DiagnoseAsync" disabled="@(!AccessResult.Succeeded)">
|
||||||
<StethoscopeIcon/>
|
<StethoscopeIcon/>
|
||||||
Start diagnostics
|
Start diagnostics
|
||||||
</WButton>
|
</WButtom>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user