Compare commits
2 Commits
6d854d82d3
...
826714962e
| Author | SHA1 | Date | |
|---|---|---|---|
| 826714962e | |||
| 58c882603c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -400,7 +400,6 @@ FodyWeavers.xsd
|
|||||||
# Style builds
|
# Style builds
|
||||||
**/style.min.css
|
**/style.min.css
|
||||||
**/package-lock.json
|
**/package-lock.json
|
||||||
**/bun.lock
|
|
||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
**/.env
|
**/.env
|
||||||
|
|||||||
@@ -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,6 +0,0 @@
|
|||||||
namespace Moonlight.Api.Configuration;
|
|
||||||
|
|
||||||
public class SettingsOptions
|
|
||||||
{
|
|
||||||
public int CacheMinutes { get; set; } = 3;
|
|
||||||
}
|
|
||||||
@@ -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: "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -136,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");
|
||||||
|
|
||||||
|
|||||||
@@ -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.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;
|
||||||
|
|
||||||
|
|||||||
@@ -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,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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,9 @@ 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;
|
namespace Moonlight.Api.Http.Controllers.Admin;
|
||||||
|
|
||||||
|
|||||||
@@ -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,29 +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))]
|
|
||||||
public partial class SerializationContext : JsonSerializerContext
|
|
||||||
{
|
|
||||||
private static JsonSerializerOptions? InternalTunedOptions;
|
|
||||||
|
|
||||||
public static JsonSerializerOptions TunedOptions
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (InternalTunedOptions != null)
|
|
||||||
return InternalTunedOptions;
|
|
||||||
|
|
||||||
InternalTunedOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
|
||||||
InternalTunedOptions.TypeInfoResolverChain.Add(Default);
|
|
||||||
|
|
||||||
return InternalTunedOptions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -35,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,117 +0,0 @@
|
|||||||
using System.Net.Http.Headers;
|
|
||||||
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.TunedOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
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.TunedOptions);
|
|
||||||
|
|
||||||
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.TunedOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var problemDetails =
|
|
||||||
await response.Content.ReadFromJsonAsync<ProblemDetails>(SerializationContext.TunedOptions);
|
|
||||||
|
|
||||||
if (problemDetails == null)
|
|
||||||
throw new HttpRequestException($"Failed to set version: {response.ReasonPhrase}");
|
|
||||||
|
|
||||||
throw new HttpRequestException($"Failed to set version: {problemDetails.Detail ?? problemDetails.Title}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
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 IMemoryCache Cache;
|
|
||||||
|
|
||||||
private const string CacheKey = "Moonlight.Api.SettingsService.{0}";
|
|
||||||
|
|
||||||
public SettingsService(
|
|
||||||
DatabaseRepository<SettingsOption> repository,
|
|
||||||
IOptions<SettingsOptions> options,
|
|
||||||
IMemoryCache cache
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Repository = repository;
|
|
||||||
Cache = cache;
|
|
||||||
Options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T?> GetValueAsync<T>(string key)
|
|
||||||
{
|
|
||||||
var cacheKey = string.Format(CacheKey, key);
|
|
||||||
|
|
||||||
if (Cache.TryGetValue<string>(cacheKey, out var value))
|
|
||||||
return JsonSerializer.Deserialize<T>(value!);
|
|
||||||
|
|
||||||
value = await Repository
|
|
||||||
.Query()
|
|
||||||
.Where(x => x.Key == key)
|
|
||||||
.Select(o => o.ValueJson)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
if(string.IsNullOrEmpty(value))
|
|
||||||
return default;
|
|
||||||
|
|
||||||
Cache.Set(
|
|
||||||
cacheKey,
|
|
||||||
value,
|
|
||||||
TimeSpan.FromMinutes(Options.Value.CacheMinutes)
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache.Remove(cacheKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -74,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();
|
||||||
@@ -106,9 +100,6 @@ public partial class Startup
|
|||||||
|
|
||||||
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
|
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
|
||||||
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
|
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
|
||||||
|
|
||||||
builder.Services.AddOptions<SettingsOptions>().BindConfiguration("Moonlight:Settings");
|
|
||||||
builder.Services.AddScoped<SettingsService>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UseAuth(WebApplication application)
|
private static void UseAuth(WebApplication application)
|
||||||
|
|||||||
@@ -43,16 +43,7 @@ public partial class Startup
|
|||||||
|
|
||||||
builder.Services.AddOptions<VersionOptions>().BindConfiguration("Moonlight:Version");
|
builder.Services.AddOptions<VersionOptions>().BindConfiguration("Moonlight:Version");
|
||||||
builder.Services.AddSingleton<VersionService>();
|
builder.Services.AddSingleton<VersionService>();
|
||||||
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddScoped<UserDeletionService>();
|
builder.Services.AddScoped<UserDeletionService>();
|
||||||
builder.Services.AddScoped<UserLogoutService>();
|
builder.Services.AddScoped<UserLogoutService>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +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 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,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,8 +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.11" />
|
<PackageReference Include="ShadcnBlazor" Version="1.0.9" />
|
||||||
<PackageReference Include="ShadcnBlazor.Extras" Version="1.0.11" />
|
<PackageReference Include="ShadcnBlazor.Extras" Version="1.0.9" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -3,7 +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 Moonlight.Shared.Http.Responses.Admin.Auth;
|
using Moonlight.Shared.Http.Responses.Auth;
|
||||||
|
|
||||||
namespace Moonlight.Frontend.Services;
|
namespace Moonlight.Frontend.Services;
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +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.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>
|
||||||
@@ -20,74 +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>
|
|
||||||
<Field>
|
<div class="grid gap-2">
|
||||||
<FieldLabel for="keyName">Name</FieldLabel>
|
<Label for="keyDescription">Description</Label>
|
||||||
<TextInputField @bind-Value="Request.Name" id="keyName" placeholder="My API key"/>
|
<textarea
|
||||||
</Field>
|
@bind="Request.Description"
|
||||||
<Field>
|
id="keyDescription"
|
||||||
<FieldLabel for="keyDescription">Description</FieldLabel>
|
maxlength="100"
|
||||||
<TextareaInputField @bind-Value="Request.Description" id="keyDescription" placeholder="What this key is for"/>
|
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<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 = []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
Request.Permissions = Permissions.ToArray();
|
Request.Permissions = Permissions.ToArray();
|
||||||
|
await OnSubmit.Invoke(Request);
|
||||||
var response = await HttpClient.PostAsJsonAsync(
|
|
||||||
"/api/admin/apiKeys",
|
|
||||||
Request,
|
|
||||||
Constants.SerializerOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
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,17 +1,14 @@
|
|||||||
@using Moonlight.Frontend.Helpers
|
|
||||||
@using Moonlight.Frontend.UI.Admin.Components
|
@using Moonlight.Frontend.UI.Admin.Components
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Roles
|
@using Moonlight.Shared.Http.Requests.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
|
||||||
@@ -21,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()
|
||||||
{
|
{
|
||||||
@@ -65,31 +68,19 @@
|
|||||||
{
|
{
|
||||||
Permissions = []
|
Permissions = []
|
||||||
};
|
};
|
||||||
|
|
||||||
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(
|
}
|
||||||
"api/admin/roles",
|
|
||||||
Request,
|
|
||||||
Constants.SerializerOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync("Role creation", $"Role {Request.Name} has been successfully created");
|
private async Task OnSubmitAsync()
|
||||||
|
{
|
||||||
await OnSubmit.Invoke();
|
await OnSubmit.Invoke(Request);
|
||||||
await CloseAsync();
|
await CloseAsync();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
@using Moonlight.Frontend.Helpers
|
@using Moonlight.Shared.Http.Requests.Users
|
||||||
@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
|
||||||
@@ -21,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,
|
|
||||||
Constants.SerializerOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
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,19 +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.Requests.Admin.ApiKeys
|
@using Moonlight.Shared.Http.Requests.ApiKeys
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.ApiKeys
|
@using Moonlight.Shared.Http.Responses.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>
|
||||||
@@ -21,39 +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>Permissions</FieldLabel>
|
maxlength="100"
|
||||||
<FieldContent>
|
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"
|
||||||
<PermissionSelector Permissions="Permissions"/>
|
placeholder="What this key is for">
|
||||||
</FieldContent>
|
|
||||||
</Field>
|
</textarea>
|
||||||
</FieldSet>
|
</div>
|
||||||
<Field Orientation="FieldOrientation.Horizontal" ClassName="justify-end">
|
|
||||||
<SubmitButton>Save changes</SubmitButton>
|
<div class="grid gap-2">
|
||||||
</Field>
|
<Label>Permissions</Label>
|
||||||
</FieldGroup>
|
<PermissionSelector Permissions="Permissions" />
|
||||||
</EnhancedEditForm>
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormHandler>
|
||||||
|
|
||||||
|
<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()
|
||||||
@@ -62,30 +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();
|
||||||
|
await OnSubmit.Invoke(Request);
|
||||||
var response = await HttpClient.PatchAsJsonAsync(
|
|
||||||
$"/api/admin/apiKeys/{Key.Id}",
|
|
||||||
Request,
|
|
||||||
Constants.SerializerOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
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.TunedOptions);
|
|
||||||
|
|
||||||
// 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.TunedOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
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, Constants.SerializerOptions);
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
await Task.Delay(4000);
|
||||||
|
|
||||||
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,19 +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.Requests.Admin.Roles
|
@using Moonlight.Shared.Http.Requests.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
|
||||||
@@ -23,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()
|
||||||
{
|
{
|
||||||
@@ -68,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(
|
}
|
||||||
$"api/admin/roles/{Role.Id}",
|
|
||||||
Request,
|
|
||||||
Constants.SerializerOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync("Role update", $"Role {Request.Name} has been successfully updated");
|
private async Task OnSubmitAsync()
|
||||||
|
{
|
||||||
await OnSubmit.Invoke();
|
await OnSubmit.Invoke(Request);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,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>
|
||||||
@@ -160,4 +156,9 @@
|
|||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LaunchUpdateModalAsync() => await DialogService.LaunchAsync<UpdateInstanceModal>(onConfigure: model =>
|
||||||
|
{
|
||||||
|
model.ShowCloseButton = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +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.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
|
||||||
@@ -122,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();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -134,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>
|
||||||
|
|||||||
@@ -31,13 +31,17 @@
|
|||||||
<HeartPulseIcon/>
|
<HeartPulseIcon/>
|
||||||
Diagnose
|
Diagnose
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger Value="instance" Disabled="@(!InstanceResult.Succeeded || !VersionsResult.Succeeded)">
|
|
||||||
<ContainerIcon/>
|
|
||||||
Instance
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent Value="settings">
|
<TabsContent Value="settings">
|
||||||
<Card ClassName="mt-5">
|
<Card ClassName="mt-5">
|
||||||
|
<CardContent>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||||
|
<div class="col-span-1 grid gap-3">
|
||||||
|
<Label for="instance-name">Instance Name</Label>
|
||||||
|
<InputField id="instance-name" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
<CardFooter ClassName="justify-end">
|
<CardFooter ClassName="justify-end">
|
||||||
<Button>
|
<Button>
|
||||||
<SaveIcon />
|
<SaveIcon />
|
||||||
@@ -61,12 +65,6 @@
|
|||||||
<Moonlight.Frontend.UI.Admin.Views.Sys.Themes.Index />
|
<Moonlight.Frontend.UI.Admin.Views.Sys.Themes.Index />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
}
|
}
|
||||||
@if (InstanceResult.Succeeded && VersionsResult.Succeeded)
|
|
||||||
{
|
|
||||||
<TabsContent Value="instance">
|
|
||||||
<Instance />
|
|
||||||
</TabsContent>
|
|
||||||
}
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
@@ -79,8 +77,6 @@
|
|||||||
|
|
||||||
private AuthorizationResult ApiKeyAccess;
|
private AuthorizationResult ApiKeyAccess;
|
||||||
private AuthorizationResult ThemesAccess;
|
private AuthorizationResult ThemesAccess;
|
||||||
private AuthorizationResult InstanceResult;
|
|
||||||
private AuthorizationResult VersionsResult;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
@@ -88,8 +84,6 @@
|
|||||||
|
|
||||||
ApiKeyAccess = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.ApiKeys.View);
|
ApiKeyAccess = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.ApiKeys.View);
|
||||||
ThemesAccess = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.Themes.View);
|
ThemesAccess = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.Themes.View);
|
||||||
InstanceResult = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.System.Versions);
|
|
||||||
VersionsResult = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.System.Instance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTabChanged(string name)
|
private void OnTabChanged(string name)
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
@using LucideBlazor
|
|
||||||
@using Moonlight.Frontend.UI.Admin.Modals
|
|
||||||
@using Moonlight.Shared.Http.Responses.Admin
|
|
||||||
@using ShadcnBlazor.Cards
|
|
||||||
@using ShadcnBlazor.Emptys
|
|
||||||
@using ShadcnBlazor.Buttons
|
|
||||||
@using ShadcnBlazor.Extras.AlertDialogs
|
|
||||||
@using ShadcnBlazor.Extras.Common
|
|
||||||
@using ShadcnBlazor.Extras.Dialogs
|
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Selects
|
|
||||||
@using ShadcnBlazor.Switches
|
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject DialogService DialogService
|
|
||||||
@inject AlertDialogService AlertDialogService
|
|
||||||
|
|
||||||
<div class="mt-5">
|
|
||||||
<LazyLoader Load="LoadAsync">
|
|
||||||
@if (StatusDto.IsEnabled)
|
|
||||||
{
|
|
||||||
if (StatusDto.IsReachable)
|
|
||||||
{
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
|
||||||
<Card ClassName="col-span-1">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Version</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<FieldGroup>
|
|
||||||
<FieldSet>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel>Version / Branch</FieldLabel>
|
|
||||||
<FieldContent>
|
|
||||||
<Select DefaultValue="@SelectedVersion" @bind-Value="SelectedVersion">
|
|
||||||
<SelectTrigger ClassName="w-64">
|
|
||||||
<SelectValue/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent ClassName="w-64">
|
|
||||||
@foreach (var version in Versions)
|
|
||||||
{
|
|
||||||
var displayName = version.Identifier;
|
|
||||||
|
|
||||||
if (version.IsDevelopment)
|
|
||||||
displayName += " (dev)";
|
|
||||||
|
|
||||||
if (version.IsPreRelease)
|
|
||||||
displayName += " (beta)";
|
|
||||||
|
|
||||||
<SelectItem Value="@version.Identifier">
|
|
||||||
@displayName
|
|
||||||
</SelectItem>
|
|
||||||
}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FieldContent>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel>Bypass Build Cache</FieldLabel>
|
|
||||||
<FieldContent>
|
|
||||||
<Switch @bind-Value="NoBuildCache"/>
|
|
||||||
</FieldContent>
|
|
||||||
</Field>
|
|
||||||
</FieldSet>
|
|
||||||
<Field Orientation="FieldOrientation.Horizontal">
|
|
||||||
<Button @onclick="AskApplyAsync">Apply</Button>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card ClassName="col-span-1">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Plugins</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Empty>
|
|
||||||
<EmptyHeader>
|
|
||||||
<EmptyMedia Variant="EmptyMediaVariant.Icon">
|
|
||||||
<SearchIcon/>
|
|
||||||
</EmptyMedia>
|
|
||||||
<EmptyTitle>No Plugins found</EmptyTitle>
|
|
||||||
<EmptyDescription>
|
|
||||||
No plugins found in instance configuration
|
|
||||||
</EmptyDescription>
|
|
||||||
</EmptyHeader>
|
|
||||||
</Empty>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Empty>
|
|
||||||
<EmptyHeader>
|
|
||||||
<EmptyMedia Variant="EmptyMediaVariant.Icon">
|
|
||||||
<CircleAlertIcon ClassName="text-red-500"/>
|
|
||||||
</EmptyMedia>
|
|
||||||
<EmptyTitle>Container Helper unreachable</EmptyTitle>
|
|
||||||
<EmptyDescription>
|
|
||||||
The container helper is unreachable. No management actions are available
|
|
||||||
</EmptyDescription>
|
|
||||||
</EmptyHeader>
|
|
||||||
</Empty>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Empty>
|
|
||||||
<EmptyHeader>
|
|
||||||
<EmptyMedia Variant="EmptyMediaVariant.Icon">
|
|
||||||
<ToggleLeftIcon/>
|
|
||||||
</EmptyMedia>
|
|
||||||
<EmptyTitle>Container Helper is disabled</EmptyTitle>
|
|
||||||
<EmptyDescription>
|
|
||||||
The container helper is disabled on this instance.
|
|
||||||
This might be due to running a multiple container moonlight setup
|
|
||||||
</EmptyDescription>
|
|
||||||
</EmptyHeader>
|
|
||||||
</Empty>
|
|
||||||
}
|
|
||||||
</LazyLoader>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
private ContainerHelperStatusDto StatusDto;
|
|
||||||
private string SelectedVersion = "v2.1";
|
|
||||||
private bool NoBuildCache;
|
|
||||||
|
|
||||||
private VersionDto[] Versions;
|
|
||||||
|
|
||||||
private async Task LoadAsync(LazyLoader _)
|
|
||||||
{
|
|
||||||
StatusDto = (await HttpClient.GetFromJsonAsync<ContainerHelperStatusDto>("api/admin/ch/status"))!;
|
|
||||||
|
|
||||||
var currentVersion = await HttpClient.GetFromJsonAsync<VersionDto>("api/admin/versions/instance");
|
|
||||||
|
|
||||||
if (currentVersion != null)
|
|
||||||
SelectedVersion = currentVersion.Identifier;
|
|
||||||
|
|
||||||
Versions = (await HttpClient.GetFromJsonAsync<VersionDto[]>("api/admin/versions"))!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ApplyAsync()
|
|
||||||
{
|
|
||||||
await DialogService.LaunchAsync<UpdateInstanceModal>(
|
|
||||||
parameters =>
|
|
||||||
{
|
|
||||||
parameters[nameof(UpdateInstanceModal.Version)] = SelectedVersion;
|
|
||||||
parameters[nameof(UpdateInstanceModal.NoBuildCache)] = NoBuildCache;
|
|
||||||
},
|
|
||||||
onConfigure: model =>
|
|
||||||
{
|
|
||||||
model.ShowCloseButton = false;
|
|
||||||
model.ClassName = "sm:max-w-4xl!";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task AskApplyAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(SelectedVersion))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var version = Versions.First(x => x.Identifier == SelectedVersion);
|
|
||||||
|
|
||||||
var shouldContinue = await ConfirmRiskyVersionAsync(
|
|
||||||
"Moonlight Rebuild",
|
|
||||||
"If you continue the moonlight instance will become unavailable during the rebuild process. This will impact users on this instance"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!shouldContinue)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (version.IsDevelopment)
|
|
||||||
{
|
|
||||||
shouldContinue = await ConfirmRiskyVersionAsync(
|
|
||||||
"Development Version",
|
|
||||||
"You are about to install development a version. This can break your instance. Continue at your own risk"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (version.IsPreRelease)
|
|
||||||
{
|
|
||||||
shouldContinue = await ConfirmRiskyVersionAsync(
|
|
||||||
"Beta / Pre-Release Version",
|
|
||||||
"You are about to install a version marked as pre-release / beta. This can break your instance. Continue at your own risk"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
shouldContinue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldContinue)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ApplyAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> ConfirmRiskyVersionAsync(string title, string message)
|
|
||||||
{
|
|
||||||
var tcs = new TaskCompletionSource();
|
|
||||||
var confirmed = false;
|
|
||||||
|
|
||||||
await AlertDialogService.ConfirmDangerAsync(
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
() =>
|
|
||||||
{
|
|
||||||
confirmed = true;
|
|
||||||
tcs.SetResult();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await tcs.Task;
|
|
||||||
|
|
||||||
return confirmed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,15 +3,14 @@
|
|||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using Moonlight.Shared
|
@using Moonlight.Shared
|
||||||
@using LucideBlazor
|
@using LucideBlazor
|
||||||
@using Moonlight.Frontend.Helpers
|
|
||||||
@using Moonlight.Frontend.Services
|
@using Moonlight.Frontend.Services
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Themes
|
@using Moonlight.Shared.Http.Requests.Themes
|
||||||
@using ShadcnBlazor.Buttons
|
@using ShadcnBlazor.Buttons
|
||||||
|
@using ShadcnBlazor.Labels
|
||||||
@using ShadcnBlazor.Cards
|
@using ShadcnBlazor.Cards
|
||||||
@using ShadcnBlazor.Extras.Editors
|
@using ShadcnBlazor.Extras.Editors
|
||||||
@using ShadcnBlazor.Extras.Forms
|
@using ShadcnBlazor.Extras.FormHandlers
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.Toasts
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
@using ShadcnBlazor.Inputs
|
||||||
@using ShadcnBlazor.Switches
|
@using ShadcnBlazor.Switches
|
||||||
|
|
||||||
@@ -22,89 +21,86 @@
|
|||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject FrontendService FrontendService
|
@inject FrontendService FrontendService
|
||||||
|
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnSubmitAsync" Context="editFormContext">
|
<div class="flex flex-row justify-between">
|
||||||
<div class="flex flex-row justify-between">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<h1 class="text-xl font-semibold">Create theme</h1>
|
||||||
<h1 class="text-xl font-semibold">Create theme</h1>
|
<div class="text-muted-foreground">
|
||||||
<div class="text-muted-foreground">
|
Create a new theme
|
||||||
Create a new theme
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-x-1.5">
|
|
||||||
<Button Variant="ButtonVariant.Secondary">
|
|
||||||
<Slot>
|
|
||||||
<a href="/admin/system?tab=themes" @attributes="context">
|
|
||||||
<ChevronLeftIcon/>
|
|
||||||
Back
|
|
||||||
</a>
|
|
||||||
</Slot>
|
|
||||||
</Button>
|
|
||||||
<SubmitButton>
|
|
||||||
<CheckIcon/>
|
|
||||||
Continue
|
|
||||||
</SubmitButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-row gap-x-1.5">
|
||||||
|
<Button Variant="ButtonVariant.Secondary">
|
||||||
|
<Slot>
|
||||||
|
<a href="/admin/system?tab=themes" @attributes="context">
|
||||||
|
<ChevronLeftIcon/>
|
||||||
|
Back
|
||||||
|
</a>
|
||||||
|
</Slot>
|
||||||
|
</Button>
|
||||||
|
<Button @onclick="SubmitAsync">
|
||||||
|
<CheckIcon/>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<FieldGroup>
|
<FormHandler @ref="Form" OnValidSubmit="OnSubmitAsync" Model="Request">
|
||||||
<FormValidationSummary/>
|
<div class="flex flex-col gap-6">
|
||||||
<DataAnnotationsValidator/>
|
|
||||||
|
<FormValidationSummary />
|
||||||
<FieldSet ClassName="grid grid-cols-1 lg:grid-cols-2">
|
|
||||||
<Field>
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
||||||
<FieldLabel for="themeName">Name</FieldLabel>
|
<div class="col-span-1 grid gap-2">
|
||||||
<TextInputField
|
<Label for="themeName">Name</Label>
|
||||||
|
<InputField
|
||||||
@bind-Value="Request.Name"
|
@bind-Value="Request.Name"
|
||||||
id="themeName"
|
id="themeName"
|
||||||
placeholder="My cool theme"/>
|
placeholder="My cool theme"/>
|
||||||
</Field>
|
</div>
|
||||||
|
|
||||||
<Field>
|
<div class="col-span-1 grid gap-2">
|
||||||
<FieldLabel for="themeVersion">Version</FieldLabel>
|
<Label for="themeVersion">Version</Label>
|
||||||
<TextInputField
|
<InputField
|
||||||
@bind-Value="Request.Version"
|
@bind-Value="Request.Version"
|
||||||
id="themeVersion"
|
id="themeVersion"
|
||||||
Type="text"
|
Type="text"
|
||||||
placeholder="1.0.0"/>
|
placeholder="1.0.0"/>
|
||||||
</Field>
|
</div>
|
||||||
|
|
||||||
<Field>
|
<div class="col-span-1 grid gap-2">
|
||||||
<FieldLabel for="themeAuthor">Author</FieldLabel>
|
<Label for="themeAuthor">Author</Label>
|
||||||
<TextInputField
|
<InputField
|
||||||
@bind-Value="Request.Author"
|
@bind-Value="Request.Author"
|
||||||
id="themeAuthor"
|
id="themeAuthor"
|
||||||
Type="text"
|
Type="text"
|
||||||
placeholder="Your name"/>
|
placeholder="Your name"/>
|
||||||
</Field>
|
</div>
|
||||||
|
|
||||||
<Field>
|
<div class="col-span-1 grid gap-2">
|
||||||
<FieldLabel for="themeAuthor">Is Enabled</FieldLabel>
|
<Label for="themeAuthor">Is Enabled</Label>
|
||||||
<FieldContent>
|
<Switch @bind-Value="Request.IsEnabled" />
|
||||||
<Switch @bind-Value="Request.IsEnabled"/>
|
</div>
|
||||||
</FieldContent>
|
</div>
|
||||||
</Field>
|
|
||||||
</FieldSet>
|
<style>
|
||||||
<Field>
|
.cm-editor {
|
||||||
<style>
|
max-height: 400px;
|
||||||
.cm-editor {
|
min-height: 400px;
|
||||||
max-height: 400px;
|
}
|
||||||
min-height: 400px;
|
</style>
|
||||||
}
|
|
||||||
</style>
|
<div class="grid gap-2">
|
||||||
|
<Label for="themeAuthor">CSS Content</Label>
|
||||||
<FieldLabel for="themeAuthor">CSS Content</FieldLabel>
|
<Editor @ref="Editor" Language="EditorLanguage.Css" InitialValue="@Request.CssContent"/>
|
||||||
<FieldContent>
|
</div>
|
||||||
<Editor @ref="Editor" Language="EditorLanguage.Css" InitialValue="@Request.CssContent"/>
|
</div>
|
||||||
</FieldContent>
|
</FormHandler>
|
||||||
</Field>
|
</CardContent>
|
||||||
</FieldGroup>
|
</Card>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</EnhancedEditForm>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
@@ -113,23 +109,22 @@
|
|||||||
CssContent = "/* Define your css here */"
|
CssContent = "/* Define your css here */"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private FormHandler Form;
|
||||||
private Editor Editor;
|
private Editor Editor;
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
Request.CssContent = await Editor.GetValueAsync();
|
Request.CssContent = await Editor.GetValueAsync();
|
||||||
|
await Form.SubmitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
var response = await HttpClient.PostAsJsonAsync(
|
private async Task OnSubmitAsync()
|
||||||
|
{
|
||||||
|
await HttpClient.PostAsJsonAsync(
|
||||||
"/api/admin/themes",
|
"/api/admin/themes",
|
||||||
Request,
|
Request,
|
||||||
Constants.SerializerOptions
|
Constants.SerializerOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync(
|
await ToastService.SuccessAsync(
|
||||||
"Theme creation",
|
"Theme creation",
|
||||||
@@ -139,7 +134,5 @@
|
|||||||
await FrontendService.ReloadAsync();
|
await FrontendService.ReloadAsync();
|
||||||
|
|
||||||
Navigation.NavigateTo("/admin/system?tab=themes");
|
Navigation.NavigateTo("/admin/system?tab=themes");
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
@using Moonlight.Shared
|
@using Moonlight.Shared
|
||||||
@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.Themes
|
@using Moonlight.Shared.Http.Responses.Themes
|
||||||
@using ShadcnBlazor.DataGrids
|
@using ShadcnBlazor.DataGrids
|
||||||
@using ShadcnBlazor.Dropdowns
|
@using ShadcnBlazor.Dropdowns
|
||||||
@using ShadcnBlazor.Extras.AlertDialogs
|
@using ShadcnBlazor.Extras.AlertDialogs
|
||||||
|
|||||||
@@ -3,18 +3,17 @@
|
|||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using Moonlight.Shared
|
@using Moonlight.Shared
|
||||||
@using LucideBlazor
|
@using LucideBlazor
|
||||||
@using Moonlight.Frontend.Helpers
|
|
||||||
@using Moonlight.Frontend.Mappers
|
@using Moonlight.Frontend.Mappers
|
||||||
@using Moonlight.Frontend.Services
|
@using Moonlight.Frontend.Services
|
||||||
@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 ShadcnBlazor.Buttons
|
@using ShadcnBlazor.Buttons
|
||||||
|
@using ShadcnBlazor.Labels
|
||||||
@using ShadcnBlazor.Cards
|
@using ShadcnBlazor.Cards
|
||||||
@using ShadcnBlazor.Extras.Common
|
@using ShadcnBlazor.Extras.Common
|
||||||
@using ShadcnBlazor.Extras.Editors
|
@using ShadcnBlazor.Extras.Editors
|
||||||
@using ShadcnBlazor.Extras.Forms
|
@using ShadcnBlazor.Extras.FormHandlers
|
||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.Toasts
|
||||||
@using ShadcnBlazor.Fields
|
|
||||||
@using ShadcnBlazor.Inputs
|
@using ShadcnBlazor.Inputs
|
||||||
@using ShadcnBlazor.Switches
|
@using ShadcnBlazor.Switches
|
||||||
|
|
||||||
@@ -25,102 +24,99 @@
|
|||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject FrontendService FrontendService
|
@inject FrontendService FrontendService
|
||||||
|
|
||||||
<LazyLoader Load="LoadAsync">
|
<div class="flex flex-row justify-between">
|
||||||
<EnhancedEditForm Model="Request" OnValidSubmit="OnSubmitAsync" Context="editFormContext">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-row justify-between">
|
<h1 class="text-xl font-semibold">Update theme</h1>
|
||||||
<div class="flex flex-col">
|
<div class="text-muted-foreground">
|
||||||
<h1 class="text-xl font-semibold">Update theme</h1>
|
Update the theme
|
||||||
<div class="text-muted-foreground">
|
|
||||||
Update the theme
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-x-1.5">
|
|
||||||
<Button Variant="ButtonVariant.Secondary">
|
|
||||||
<Slot>
|
|
||||||
<a href="/admin/system?tab=themes" @attributes="context">
|
|
||||||
<ChevronLeftIcon/>
|
|
||||||
Back
|
|
||||||
</a>
|
|
||||||
</Slot>
|
|
||||||
</Button>
|
|
||||||
<SubmitButton>
|
|
||||||
<CheckIcon/>
|
|
||||||
Continue
|
|
||||||
</SubmitButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-x-1.5">
|
||||||
|
<Button Variant="ButtonVariant.Secondary">
|
||||||
|
<Slot>
|
||||||
|
<a href="/admin/system?tab=themes" @attributes="context">
|
||||||
|
<ChevronLeftIcon/>
|
||||||
|
Back
|
||||||
|
</a>
|
||||||
|
</Slot>
|
||||||
|
</Button>
|
||||||
|
<Button @onclick="SubmitAsync">
|
||||||
|
<CheckIcon/>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<LazyLoader Load="LoadAsync">
|
||||||
|
<FormHandler @ref="Form" OnValidSubmit="OnSubmitAsync" Model="Request">
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
|
||||||
<div class="mt-8">
|
|
||||||
<Card>
|
|
||||||
<CardContent>
|
|
||||||
<FieldGroup>
|
|
||||||
<FormValidationSummary/>
|
<FormValidationSummary/>
|
||||||
<DataAnnotationsValidator/>
|
|
||||||
|
|
||||||
<FieldSet ClassName="grid grid-cols-1 lg:grid-cols-2">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
||||||
<Field>
|
<div class="col-span-1 grid gap-2">
|
||||||
<FieldLabel for="themeName">Name</FieldLabel>
|
<Label for="themeName">Name</Label>
|
||||||
<TextInputField
|
<InputField
|
||||||
@bind-Value="Request.Name"
|
@bind-Value="Request.Name"
|
||||||
id="themeName"
|
id="themeName"
|
||||||
placeholder="My cool theme"/>
|
placeholder="My cool theme"/>
|
||||||
</Field>
|
</div>
|
||||||
|
|
||||||
<Field>
|
<div class="col-span-1 grid gap-2">
|
||||||
<FieldLabel for="themeVersion">Version</FieldLabel>
|
<Label for="themeVersion">Version</Label>
|
||||||
<TextInputField
|
<InputField
|
||||||
@bind-Value="Request.Version"
|
@bind-Value="Request.Version"
|
||||||
id="themeVersion"
|
id="themeVersion"
|
||||||
Type="text"
|
Type="text"
|
||||||
placeholder="1.0.0"/>
|
placeholder="1.0.0"/>
|
||||||
</Field>
|
</div>
|
||||||
|
|
||||||
<Field>
|
<div class="col-span-1 grid gap-2">
|
||||||
<FieldLabel for="themeAuthor">Author</FieldLabel>
|
<Label for="themeAuthor">Author</Label>
|
||||||
<TextInputField
|
<InputField
|
||||||
@bind-Value="Request.Author"
|
@bind-Value="Request.Author"
|
||||||
id="themeAuthor"
|
id="themeAuthor"
|
||||||
Type="text"
|
Type="text"
|
||||||
placeholder="Your name"/>
|
placeholder="Your name"/>
|
||||||
</Field>
|
</div>
|
||||||
|
|
||||||
<Field>
|
<div class="col-span-1 grid gap-2">
|
||||||
<FieldLabel for="themeAuthor">Is Enabled</FieldLabel>
|
<Label for="themeAuthor">Is Enabled</Label>
|
||||||
<FieldContent>
|
<Switch @bind-Value="Request.IsEnabled" />
|
||||||
<Switch @bind-Value="Request.IsEnabled"/>
|
</div>
|
||||||
</FieldContent>
|
</div>
|
||||||
</Field>
|
|
||||||
</FieldSet>
|
|
||||||
<Field>
|
|
||||||
<style>
|
|
||||||
.cm-editor {
|
|
||||||
max-height: 400px;
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<FieldLabel for="themeAuthor">CSS Content</FieldLabel>
|
<style>
|
||||||
<FieldContent>
|
.cm-editor {
|
||||||
<Editor @ref="Editor" Language="EditorLanguage.Css"
|
max-height: 400px;
|
||||||
InitialValue="@Request.CssContent"/>
|
min-height: 400px;
|
||||||
</FieldContent>
|
}
|
||||||
</Field>
|
</style>
|
||||||
</FieldGroup>
|
|
||||||
</CardContent>
|
<div class="grid gap-2">
|
||||||
</Card>
|
<Label for="themeAuthor">CSS Content</Label>
|
||||||
</div>
|
<Editor @ref="Editor" Language="EditorLanguage.Css" InitialValue="@Request.CssContent"/>
|
||||||
</EnhancedEditForm>
|
</div>
|
||||||
</LazyLoader>
|
</div>
|
||||||
|
</FormHandler>
|
||||||
|
</LazyLoader>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter] public int Id { get; set; }
|
[Parameter] public int Id { get; set; }
|
||||||
|
|
||||||
private UpdateThemeDto Request;
|
private UpdateThemeDto Request;
|
||||||
private ThemeDto Theme;
|
private ThemeDto Theme;
|
||||||
|
|
||||||
|
private FormHandler Form;
|
||||||
private Editor Editor;
|
private Editor Editor;
|
||||||
|
|
||||||
private async Task LoadAsync(LazyLoader _)
|
private async Task LoadAsync(LazyLoader _)
|
||||||
{
|
{
|
||||||
var theme = await HttpClient.GetFromJsonAsync<ThemeDto>($"api/admin/themes/{Id}");
|
var theme = await HttpClient.GetFromJsonAsync<ThemeDto>($"api/admin/themes/{Id}");
|
||||||
@@ -129,21 +125,19 @@
|
|||||||
Request = ThemeMapper.ToUpdate(Theme);
|
Request = ThemeMapper.ToUpdate(Theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
private async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
Request.CssContent = await Editor.GetValueAsync();
|
Request.CssContent = await Editor.GetValueAsync();
|
||||||
|
await Form.SubmitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
var response = await HttpClient.PatchAsJsonAsync(
|
private async Task OnSubmitAsync()
|
||||||
|
{
|
||||||
|
await HttpClient.PatchAsJsonAsync(
|
||||||
$"/api/admin/themes/{Theme.Id}",
|
$"/api/admin/themes/{Theme.Id}",
|
||||||
Request,
|
Request,
|
||||||
Constants.SerializerOptions
|
Constants.SerializerOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ToastService.SuccessAsync(
|
await ToastService.SuccessAsync(
|
||||||
"Theme update",
|
"Theme update",
|
||||||
@@ -153,7 +147,5 @@
|
|||||||
await FrontendService.ReloadAsync();
|
await FrontendService.ReloadAsync();
|
||||||
|
|
||||||
Navigation.NavigateTo("/admin/system?tab=themes");
|
Navigation.NavigateTo("/admin/system?tab=themes");
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
@using Moonlight.Frontend.UI.Admin.Modals
|
@using Moonlight.Frontend.UI.Admin.Modals
|
||||||
@using Moonlight.Shared
|
@using Moonlight.Shared
|
||||||
@using Moonlight.Shared.Http.Requests
|
@using Moonlight.Shared.Http.Requests
|
||||||
|
@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
|
||||||
@using ShadcnBlazor.DataGrids
|
@using ShadcnBlazor.DataGrids
|
||||||
@@ -133,8 +134,15 @@
|
|||||||
{
|
{
|
||||||
await DialogService.LaunchAsync<CreateRoleDialog>(parameters =>
|
await DialogService.LaunchAsync<CreateRoleDialog>(parameters =>
|
||||||
{
|
{
|
||||||
parameters[nameof(CreateRoleDialog.OnSubmit)] = async Task () =>
|
parameters[nameof(CreateRoleDialog.OnSubmit)] = async Task (CreateRoleDto request) =>
|
||||||
{
|
{
|
||||||
|
await HttpClient.PostAsJsonAsync(
|
||||||
|
"api/admin/roles",
|
||||||
|
request,
|
||||||
|
Constants.SerializerOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
await ToastService.SuccessAsync("Role creation", $"Role {request.Name} has been successfully created");
|
||||||
await Grid.RefreshAsync();
|
await Grid.RefreshAsync();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -145,8 +153,15 @@
|
|||||||
await DialogService.LaunchAsync<UpdateRoleDialog>(parameters =>
|
await DialogService.LaunchAsync<UpdateRoleDialog>(parameters =>
|
||||||
{
|
{
|
||||||
parameters[nameof(UpdateRoleDialog.Role)] = role;
|
parameters[nameof(UpdateRoleDialog.Role)] = role;
|
||||||
parameters[nameof(UpdateRoleDialog.OnSubmit)] = async Task () =>
|
parameters[nameof(UpdateRoleDialog.OnSubmit)] = async Task (UpdateRoleDto request) =>
|
||||||
{
|
{
|
||||||
|
await HttpClient.PatchAsJsonAsync(
|
||||||
|
$"api/admin/roles/{role.Id}",
|
||||||
|
request,
|
||||||
|
Constants.SerializerOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
await ToastService.SuccessAsync("Role update", $"Role {request.Name} has been successfully updated");
|
||||||
await Grid.RefreshAsync();
|
await Grid.RefreshAsync();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,8 +10,9 @@
|
|||||||
@using ShadcnBlazor.Extras.Toasts
|
@using ShadcnBlazor.Extras.Toasts
|
||||||
@using ShadcnBlazor.Tabels
|
@using ShadcnBlazor.Tabels
|
||||||
@using Moonlight.Shared.Http.Requests
|
@using Moonlight.Shared.Http.Requests
|
||||||
|
@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
|
||||||
@using ShadcnBlazor.Extras.Dialogs
|
@using ShadcnBlazor.Extras.Dialogs
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
@inject HttpClient HttpClient
|
||||||
@@ -131,8 +132,19 @@
|
|||||||
{
|
{
|
||||||
await DialogService.LaunchAsync<CreateUserDialog>(parameters =>
|
await DialogService.LaunchAsync<CreateUserDialog>(parameters =>
|
||||||
{
|
{
|
||||||
parameters[nameof(CreateUserDialog.OnCompleted)] = async () =>
|
parameters[nameof(CreateUserDialog.OnSubmit)] = async (CreateUserDto dto) =>
|
||||||
{
|
{
|
||||||
|
await HttpClient.PostAsJsonAsync(
|
||||||
|
"/api/admin/users",
|
||||||
|
dto,
|
||||||
|
Constants.SerializerOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
await ToastService.SuccessAsync(
|
||||||
|
"User creation",
|
||||||
|
$"Successfully created user {dto.Username}"
|
||||||
|
);
|
||||||
|
|
||||||
await Grid.RefreshAsync();
|
await Grid.RefreshAsync();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -143,8 +155,18 @@
|
|||||||
await DialogService.LaunchAsync<UpdateUserDialog>(parameters =>
|
await DialogService.LaunchAsync<UpdateUserDialog>(parameters =>
|
||||||
{
|
{
|
||||||
parameters[nameof(UpdateUserDialog.User)] = user;
|
parameters[nameof(UpdateUserDialog.User)] = user;
|
||||||
parameters[nameof(UpdateUserDialog.OnCompleted)] = async () =>
|
parameters[nameof(CreateUserDialog.OnSubmit)] = async (UpdateUserDto dto) =>
|
||||||
{
|
{
|
||||||
|
await HttpClient.PatchAsJsonAsync(
|
||||||
|
$"/api/admin/users/{user.Id}",
|
||||||
|
dto
|
||||||
|
);
|
||||||
|
|
||||||
|
await ToastService.SuccessAsync(
|
||||||
|
"User update",
|
||||||
|
$"Successfully updated user {dto.Username}"
|
||||||
|
);
|
||||||
|
|
||||||
await Grid.RefreshAsync();
|
await Grid.RefreshAsync();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
@using LucideBlazor
|
@using LucideBlazor
|
||||||
@using Microsoft.AspNetCore.Components.Authorization
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@using Moonlight.Frontend.UI.Shared
|
@using Moonlight.Frontend.UI.Shared
|
||||||
@using Moonlight.Frontend.UI.Shared.Components
|
|
||||||
@using ShadcnBlazor.Emptys
|
@using ShadcnBlazor.Emptys
|
||||||
@using Moonlight.Frontend.UI.Shared.Components.Auth
|
@using Moonlight.Frontend.UI.Shared.Components.Auth
|
||||||
@using Moonlight.Frontend.UI.Shared.Partials
|
@using Moonlight.Frontend.UI.Shared.Partials
|
||||||
|
|
||||||
@inject NavigationManager Navigation
|
|
||||||
|
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<AuthorizeView>
|
<AuthorizeView>
|
||||||
@@ -33,42 +30,33 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var uri = new Uri(Navigation.Uri);
|
<Authentication/>
|
||||||
|
|
||||||
if (uri.LocalPath.StartsWith("/setup"))
|
|
||||||
{
|
|
||||||
<Setup />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Authentication/>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</NotAuthorized>
|
</NotAuthorized>
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
<ErrorContent>
|
<ErrorContent>
|
||||||
@if (context is HttpRequestException { StatusCode: HttpStatusCode.Unauthorized })
|
@if (context is HttpRequestException { StatusCode: HttpStatusCode.Unauthorized })
|
||||||
{
|
{
|
||||||
<Authentication/>
|
<Authentication/>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="m-10">
|
<div class="m-10">
|
||||||
<Empty>
|
<Empty>
|
||||||
<EmptyHeader>
|
<EmptyHeader>
|
||||||
<EmptyMedia Variant="EmptyMediaVariant.Icon">
|
<EmptyMedia Variant="EmptyMediaVariant.Icon">
|
||||||
<OctagonAlertIcon ClassName="text-red-500/80"/>
|
<OctagonAlertIcon ClassName="text-red-500/80"/>
|
||||||
</EmptyMedia>
|
</EmptyMedia>
|
||||||
<EmptyTitle>
|
<EmptyTitle>
|
||||||
Critical Application Error
|
Critical Application Error
|
||||||
</EmptyTitle>
|
</EmptyTitle>
|
||||||
<EmptyDescription>
|
<EmptyDescription>
|
||||||
@context.ToString()
|
@context.ToString()
|
||||||
</EmptyDescription>
|
</EmptyDescription>
|
||||||
</EmptyHeader>
|
</EmptyHeader>
|
||||||
</Empty>
|
</Empty>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</ErrorContent>
|
</ErrorContent>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@using Moonlight.Shared.Http.Responses.Admin.Auth
|
@using ShadcnBlazor.Cards
|
||||||
@using ShadcnBlazor.Cards
|
|
||||||
@using ShadcnBlazor.Spinners
|
@using ShadcnBlazor.Spinners
|
||||||
@using ShadcnBlazor.Buttons
|
@using ShadcnBlazor.Buttons
|
||||||
|
@using Moonlight.Shared.Http.Responses.Auth
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
@inject HttpClient HttpClient
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
@using LucideBlazor
|
|
||||||
@using Moonlight.Shared.Http.Requests.Seup
|
|
||||||
@using ShadcnBlazor.Cards
|
|
||||||
@using ShadcnBlazor.Spinners
|
|
||||||
@using ShadcnBlazor.Buttons
|
|
||||||
@using ShadcnBlazor.Inputs
|
|
||||||
@using ShadcnBlazor.Labels
|
|
||||||
|
|
||||||
@inject HttpClient HttpClient
|
|
||||||
@inject NavigationManager Navigation
|
|
||||||
|
|
||||||
<div class="h-screen w-full flex items-center justify-center">
|
|
||||||
<Card ClassName="w-full max-w-[calc(100%-2rem)] lg:max-w-xl grid gap-4 p-6">
|
|
||||||
@if (IsLoaded)
|
|
||||||
{
|
|
||||||
<div class="space-y-6">
|
|
||||||
@if (CurrentStep == 0)
|
|
||||||
{
|
|
||||||
<div
|
|
||||||
class="flex h-56 items-center justify-center rounded-lg bg-linear-to-br from-gray-100 to-gray-300 dark:from-gray-800 dark:to-gray-900">
|
|
||||||
<img alt="Moonlight Logo" class="size-34" src="/_content/Moonlight.Frontend/logo.svg" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2 text-center">
|
|
||||||
<h2 class="text-2xl font-bold">Welcome to Moonlight Panel</h2>
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
You successfully installed moonlight. Now you are ready to perform some initial steps to complete your installation
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else if (CurrentStep == 1)
|
|
||||||
{
|
|
||||||
<div
|
|
||||||
class="flex h-56 items-center justify-center rounded-lg bg-linear-to-br from-gray-100 to-gray-300 dark:from-gray-800 dark:to-gray-900">
|
|
||||||
<UserIcon ClassName="size-34 text-blue-500" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2 text-center">
|
|
||||||
<h2 class="text-2xl font-bold">Admin Account Creation</h2>
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
To continue please fill in the account details of the user you want to use as the initial administrator account.
|
|
||||||
If you use an external OIDC provider, these details need to match with your desired OIDC account
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-5">
|
|
||||||
<div class="grid gap-2">
|
|
||||||
<Label for="username">Username</Label>
|
|
||||||
<TextInputField
|
|
||||||
@bind-Value="SetupDto.AdminUsername"
|
|
||||||
id="username"
|
|
||||||
placeholder="someoneelse"/>
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2">
|
|
||||||
<Label for="email">Email</Label>
|
|
||||||
<TextInputField
|
|
||||||
@bind-Value="SetupDto.AdminEmail"
|
|
||||||
id="email"
|
|
||||||
Type="email"
|
|
||||||
placeholder="a@cool.email"/>
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2">
|
|
||||||
<Label for="password">Password</Label>
|
|
||||||
<TextInputField
|
|
||||||
@bind-Value="SetupDto.AdminPassword"
|
|
||||||
id="password"
|
|
||||||
Type="password"
|
|
||||||
placeholder="......."/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else if (CurrentStep == 2)
|
|
||||||
{
|
|
||||||
<div
|
|
||||||
class="flex h-56 items-center justify-center rounded-lg bg-linear-to-br from-gray-100 to-gray-300 dark:from-gray-800 dark:to-gray-900">
|
|
||||||
<RocketIcon ClassName="size-34 text-blue-500" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2 text-center">
|
|
||||||
<h2 class="text-2xl font-bold">You are all set!</h2>
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
You are now ready to finish the initial setup.
|
|
||||||
The configured options will be applied to the instance.
|
|
||||||
You will be redirected to the login after changes have been applied successfully
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
@for (var step = 0; step < StepCount; step++)
|
|
||||||
{
|
|
||||||
if (step == CurrentStep)
|
|
||||||
{
|
|
||||||
<div class="h-2 w-2 rounded-full transition-colors bg-foreground">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="h-2 w-2 rounded-full transition-colors bg-muted">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-1.5">
|
|
||||||
@if (CurrentStep > 0)
|
|
||||||
{
|
|
||||||
<Button @onclick="() => Navigate(-1)" Variant="ButtonVariant.Outline">
|
|
||||||
<ChevronLeftIcon />
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
@if (CurrentStep != StepCount - 1)
|
|
||||||
{
|
|
||||||
<Button @onclick="() => Navigate(1)">
|
|
||||||
Continue
|
|
||||||
<ArrowRightIcon/>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Button @onclick="ApplyAsync">
|
|
||||||
Finish
|
|
||||||
<RocketIcon />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="w-full flex justify-center items-center">
|
|
||||||
<Spinner ClassName="size-10"/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
private bool IsLoaded;
|
|
||||||
|
|
||||||
private int CurrentStep = 0;
|
|
||||||
private int StepCount = 3;
|
|
||||||
|
|
||||||
private ApplySetupDto SetupDto = new();
|
|
||||||
|
|
||||||
private void Navigate(int diff) => CurrentStep += diff;
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if(!firstRender)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var response = await HttpClient.GetAsync("api/admin/setup");
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
Navigation.NavigateTo("/", true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsLoaded = true;
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ApplyAsync()
|
|
||||||
{
|
|
||||||
await HttpClient.PostAsJsonAsync("api/admin/setup", SetupDto);
|
|
||||||
Navigation.NavigateTo("/", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.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,15 +0,0 @@
|
|||||||
namespace Moonlight.Shared.Http.Requests.Admin.ContainerHelper;
|
|
||||||
|
|
||||||
public class RequestRebuildDto
|
|
||||||
{
|
|
||||||
public bool NoBuildCache { get; set; }
|
|
||||||
|
|
||||||
public RequestRebuildDto()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestRebuildDto(bool noBuildCache)
|
|
||||||
{
|
|
||||||
NoBuildCache = noBuildCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.ContainerHelper;
|
|
||||||
|
|
||||||
public class SetVersionDto
|
|
||||||
{
|
|
||||||
[Required]
|
|
||||||
[RegularExpression(@"^(?!\/|.*\/\/|.*\.\.|.*\/$)[A-Za-z0-9._/-]+$", ErrorMessage = "Invalid version format")]
|
|
||||||
public string Version { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
namespace Moonlight.Shared.Http.Requests.ApiKeys;
|
||||||
|
|
||||||
public class CreateApiKeyDto
|
public class CreateApiKeyDto
|
||||||
{
|
{
|
||||||
[Required]
|
|
||||||
[MaxLength(30)]
|
[MaxLength(30)]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
[MaxLength(300)] public string Description { get; set; } = "";
|
[MaxLength(300)] public string Description { get; set; } = "";
|
||||||
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string[] Permissions { get; set; }
|
public string[] Permissions { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
namespace Moonlight.Shared.Http.Requests.ApiKeys;
|
||||||
|
|
||||||
public class UpdateApiKeyDto
|
public class UpdateApiKeyDto
|
||||||
{
|
{
|
||||||
[Required]
|
|
||||||
[MaxLength(30)]
|
[MaxLength(30)]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
[MaxLength(300)] public string Description { get; set; } = "";
|
[MaxLength(300)] public string Description { get; set; } = "";
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string[] Permissions { get; set; }
|
public string[] Permissions { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.Roles;
|
namespace Moonlight.Shared.Http.Requests.Roles;
|
||||||
|
|
||||||
public class CreateRoleDto
|
public class CreateRoleDto
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.Roles;
|
namespace Moonlight.Shared.Http.Requests.Roles;
|
||||||
|
|
||||||
public class UpdateRoleDto
|
public class UpdateRoleDto
|
||||||
{
|
{
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Seup;
|
|
||||||
|
|
||||||
public class ApplySetupDto
|
|
||||||
{
|
|
||||||
[Required]
|
|
||||||
[MinLength(3)]
|
|
||||||
[MaxLength(32)]
|
|
||||||
public string AdminUsername { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
public string AdminEmail { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
[MinLength(8)]
|
|
||||||
[MaxLength(64)]
|
|
||||||
public string AdminPassword { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.Themes;
|
namespace Moonlight.Shared.Http.Requests.Themes;
|
||||||
|
|
||||||
public class CreateThemeDto
|
public class CreateThemeDto
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.Themes;
|
namespace Moonlight.Shared.Http.Requests.Themes;
|
||||||
|
|
||||||
public class UpdateThemeDto
|
public class UpdateThemeDto
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.Users;
|
namespace Moonlight.Shared.Http.Requests.Users;
|
||||||
|
|
||||||
public class CreateUserDto
|
public class CreateUserDto
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http.Requests.Admin.Users;
|
namespace Moonlight.Shared.Http.Requests.Users;
|
||||||
|
|
||||||
public class UpdateUserDto
|
public class UpdateUserDto
|
||||||
{
|
{
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
namespace Moonlight.Shared.Http.Responses.Admin.Auth;
|
|
||||||
|
|
||||||
public record ClaimDto(string Type, string Value);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
namespace Moonlight.Shared.Http.Responses.Admin;
|
|
||||||
|
|
||||||
public record ContainerHelperStatusDto(bool IsEnabled, bool IsReachable);
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Moonlight.Shared.Http.Responses.Admin.ApiKeys;
|
namespace Moonlight.Shared.Http.Responses.ApiKeys;
|
||||||
|
|
||||||
public record ApiKeyDto(int Id, string Name, string Description, string[] Permissions, string Key, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt);
|
public record ApiKeyDto(int Id, string Name, string Description, string[] Permissions, string Key, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt);
|
||||||
3
Moonlight.Shared/Http/Responses/Auth/ClaimDto.cs
Normal file
3
Moonlight.Shared/Http/Responses/Auth/ClaimDto.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Moonlight.Shared.Http.Responses.Auth;
|
||||||
|
|
||||||
|
public record ClaimDto(string Type, string Value);
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Moonlight.Shared.Http.Responses.Admin.Auth;
|
namespace Moonlight.Shared.Http.Responses.Auth;
|
||||||
|
|
||||||
public record SchemeDto(string Name, string DisplayName);
|
public record SchemeDto(string Name, string DisplayName);
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Moonlight.Shared.Http.Responses.Admin.Frontend;
|
namespace Moonlight.Shared.Http.Responses.Frontend;
|
||||||
|
|
||||||
public record FrontendConfigDto(string Name, string? ThemeCss);
|
public record FrontendConfigDto(string Name, string? ThemeCss);
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Moonlight.Shared.Http.Responses;
|
|
||||||
|
|
||||||
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 +1,3 @@
|
|||||||
namespace Moonlight.Shared.Http.Responses.Admin.Themes;
|
namespace Moonlight.Shared.Http.Responses.Themes;
|
||||||
|
|
||||||
public record ThemeDto(int Id, string Name, string Author, string Version, string CssContent, bool IsEnabled);
|
public record ThemeDto(int Id, string Name, string Author, string Version, string CssContent, bool IsEnabled);
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Moonlight.Shared.Http.Responses.Admin.Users;
|
namespace Moonlight.Shared.Http.Responses.Users;
|
||||||
|
|
||||||
public record UserDto(int Id, string Username, string Email, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt);
|
public record UserDto(int Id, string Username, string Email, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt);
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.Json.Serialization;
|
using Moonlight.Shared.Http.Requests.ApiKeys;
|
||||||
using Moonlight.Shared.Http.Events;
|
using Moonlight.Shared.Http.Requests.Roles;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
using Moonlight.Shared.Http.Requests.Themes;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.ContainerHelper;
|
using Moonlight.Shared.Http.Requests.Users;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Roles;
|
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Themes;
|
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Users;
|
|
||||||
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.ApiKeys;
|
using Moonlight.Shared.Http.Responses.ApiKeys;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Auth;
|
using Moonlight.Shared.Http.Responses.Auth;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Themes;
|
using Moonlight.Shared.Http.Responses.Themes;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Users;
|
using Moonlight.Shared.Http.Responses.Users;
|
||||||
|
|
||||||
namespace Moonlight.Shared.Http;
|
namespace Moonlight.Shared.Http;
|
||||||
|
|
||||||
@@ -47,32 +44,8 @@ namespace Moonlight.Shared.Http;
|
|||||||
[JsonSerializable(typeof(PagedData<ThemeDto>))]
|
[JsonSerializable(typeof(PagedData<ThemeDto>))]
|
||||||
[JsonSerializable(typeof(ThemeDto))]
|
[JsonSerializable(typeof(ThemeDto))]
|
||||||
|
|
||||||
// Events
|
|
||||||
[JsonSerializable(typeof(RebuildEventDto))]
|
|
||||||
|
|
||||||
// Container Helper
|
|
||||||
[JsonSerializable(typeof(ContainerHelperStatusDto))]
|
|
||||||
[JsonSerializable(typeof(RequestRebuildDto))]
|
|
||||||
[JsonSerializable(typeof(SetVersionDto))]
|
|
||||||
|
|
||||||
//Misc
|
//Misc
|
||||||
[JsonSerializable(typeof(VersionDto))]
|
[JsonSerializable(typeof(VersionDto))]
|
||||||
[JsonSerializable(typeof(ProblemDetails))]
|
|
||||||
public partial class SerializationContext : JsonSerializerContext
|
public partial class SerializationContext : JsonSerializerContext
|
||||||
{
|
{
|
||||||
private static JsonSerializerOptions? InternalTunedOptions;
|
|
||||||
|
|
||||||
public static JsonSerializerOptions TunedOptions
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (InternalTunedOptions != null)
|
|
||||||
return InternalTunedOptions;
|
|
||||||
|
|
||||||
InternalTunedOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
|
||||||
InternalTunedOptions.TypeInfoResolverChain.Add(Default);
|
|
||||||
|
|
||||||
return InternalTunedOptions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,5 @@ public static class Permissions
|
|||||||
public const string Info = $"{Prefix}{Section}.{nameof(Info)}";
|
public const string Info = $"{Prefix}{Section}.{nameof(Info)}";
|
||||||
public const string Diagnose = $"{Prefix}{Section}.{nameof(Diagnose)}";
|
public const string Diagnose = $"{Prefix}{Section}.{nameof(Diagnose)}";
|
||||||
public const string Versions = $"{Prefix}{Section}.{nameof(Versions)}";
|
public const string Versions = $"{Prefix}{Section}.{nameof(Versions)}";
|
||||||
public const string Instance = $"{Prefix}{Section}.{nameof(Instance)}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
30
compose.yaml
30
compose.yaml
@@ -36,32 +36,4 @@
|
|||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
- "Logging__LogLevel__Default=Information"
|
- "Logging__LogLevel__Default=Information"
|
||||||
- "Logging__LogLevel__Microsoft.AspNetCore=Warning"
|
- "Logging__LogLevel__Microsoft.AspNetCore=Warning"
|
||||||
- "Logging__LogLevel__System.Net.Http.HttpClient=Warning"
|
|
||||||
|
|
||||||
- "Moonlight__ContainerHelper__IsEnabled=true"
|
|
||||||
- "Moonlight__ContainerHelper__Url=http://app:8080"
|
|
||||||
|
|
||||||
app:
|
|
||||||
image: git.battlestati.one/moonlight-panel/container_helper
|
|
||||||
|
|
||||||
group_add:
|
|
||||||
- "989"
|
|
||||||
|
|
||||||
environment:
|
|
||||||
# Logging
|
|
||||||
- "Logging__LogLevel__Default=Information"
|
|
||||||
- "Logging__LogLevel__Microsoft.AspNetCore=Warning"
|
|
||||||
|
|
||||||
# Compose
|
|
||||||
- "ContainerHelper__Compose__Directory=${PWD}"
|
|
||||||
- "ContainerHelper__Compose__Binary=docker-compose"
|
|
||||||
- "ContainerHelper__Service__Name=api"
|
|
||||||
|
|
||||||
# HTTP Proxy
|
|
||||||
- "HTTP_PROXY=${HTTP_PROXY}"
|
|
||||||
- "HTTPS_PROXY=${HTTPS_PROXY}"
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- "${PWD}:${PWD}"
|
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
|
||||||
Reference in New Issue
Block a user