Refactored css classes to match flyonui. Switched to postgres arrays for permissions. Migrated file manager. Adjusted everything to work with the latest mooncore version
This commit is contained in:
@@ -15,7 +15,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
|
||||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.7" />
|
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -8,12 +8,9 @@ public class ApiKey
|
|||||||
|
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
[Column(TypeName="jsonb")]
|
public string[] Permissions { get; set; } = [];
|
||||||
public string PermissionsJson { get; set; } = "[]";
|
|
||||||
|
|
||||||
[Column(TypeName = "timestamp with time zone")]
|
public DateTimeOffset ExpiresAt { get; set; }
|
||||||
public DateTime ExpiresAt { get; set; }
|
|
||||||
|
|
||||||
[Column(TypeName = "timestamp with time zone")]
|
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
||||||
}
|
}
|
||||||
@@ -9,10 +9,6 @@ public class User
|
|||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
public DateTimeOffset TokenValidTimestamp { get; set; } = DateTimeOffset.MinValue;
|
||||||
[Column(TypeName="timestamp with time zone")]
|
public string[] Permissions { get; set; } = [];
|
||||||
public DateTime TokenValidTimestamp { get; set; } = DateTime.MinValue;
|
|
||||||
|
|
||||||
[Column(TypeName="jsonb")]
|
|
||||||
public string PermissionsJson { get; set; } = "[]";
|
|
||||||
}
|
}
|
||||||
393
Moonlight.ApiServer/Database/Migrations/20250712202608_SwitchedToPgArraysForPermissions.Designer.cs
generated
Normal file
393
Moonlight.ApiServer/Database/Migrations/20250712202608_SwitchedToPgArraysForPermissions.Designer.cs
generated
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CoreDataContext))]
|
||||||
|
[Migration("20250712202608_SwitchedToPgArraysForPermissions")]
|
||||||
|
partial class SwitchedToPgArraysForPermissions
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireCounter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<long>("Value")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Value");
|
||||||
|
|
||||||
|
b.ToTable("HangfireCounter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Field")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Field");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireHash");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("InvocationData")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("StateId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("StateName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("StateId");
|
||||||
|
|
||||||
|
b.HasIndex("StateName");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("JobId", "Name");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJobParameter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Position");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireList");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AcquiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("HangfireLock");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FetchedAt")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Queue")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.HasIndex("Queue", "FetchedAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireQueuedJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireServer", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Heartbeat")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Queues")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("WorkerCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Heartbeat");
|
||||||
|
|
||||||
|
b.ToTable("HangfireServer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<double>("Score")
|
||||||
|
.HasColumnType("double precision");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Value");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Score");
|
||||||
|
|
||||||
|
b.ToTable("HangfireSet");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Data")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.ToTable("HangfireState");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.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()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireState", "State")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("StateId");
|
||||||
|
|
||||||
|
b.Navigation("State");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("Parameters")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("QueuedJobs")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("States")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Parameters");
|
||||||
|
|
||||||
|
b.Navigation("QueuedJobs");
|
||||||
|
|
||||||
|
b.Navigation("States");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class SwitchedToPgArraysForPermissions : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PermissionsJson",
|
||||||
|
table: "Core_Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PermissionsJson",
|
||||||
|
table: "Core_ApiKeys");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string[]>(
|
||||||
|
name: "Permissions",
|
||||||
|
table: "Core_Users",
|
||||||
|
type: "text[]",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new string[0]);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string[]>(
|
||||||
|
name: "Permissions",
|
||||||
|
table: "Core_ApiKeys",
|
||||||
|
type: "text[]",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new string[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Permissions",
|
||||||
|
table: "Core_Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Permissions",
|
||||||
|
table: "Core_ApiKeys");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "PermissionsJson",
|
||||||
|
table: "Core_Users",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "PermissionsJson",
|
||||||
|
table: "Core_ApiKeys",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ namespace Moonlight.ApiServer.Database.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "8.0.11")
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -283,19 +283,19 @@ namespace Moonlight.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<DateTime>("ExpiresAt")
|
b.Property<DateTimeOffset>("ExpiresAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("PermissionsJson")
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("jsonb");
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -318,11 +318,11 @@ namespace Moonlight.ApiServer.Database.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("PermissionsJson")
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("jsonb");
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
b.Property<DateTime>("TokenValidTimestamp")
|
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("Username")
|
b.Property<string>("Username")
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class ApiKeysController : Controller
|
|||||||
.Select(x => new ApiKeyResponse()
|
.Select(x => new ApiKeyResponse()
|
||||||
{
|
{
|
||||||
Id = x.Id,
|
Id = x.Id,
|
||||||
PermissionsJson = x.PermissionsJson,
|
Permissions = x.Permissions,
|
||||||
Description = x.Description,
|
Description = x.Description,
|
||||||
ExpiresAt = x.ExpiresAt
|
ExpiresAt = x.ExpiresAt
|
||||||
})
|
})
|
||||||
@@ -75,7 +75,7 @@ public class ApiKeysController : Controller
|
|||||||
return new ApiKeyResponse()
|
return new ApiKeyResponse()
|
||||||
{
|
{
|
||||||
Id = apiKey.Id,
|
Id = apiKey.Id,
|
||||||
PermissionsJson = apiKey.PermissionsJson,
|
Permissions = apiKey.Permissions,
|
||||||
Description = apiKey.Description,
|
Description = apiKey.Description,
|
||||||
ExpiresAt = apiKey.ExpiresAt
|
ExpiresAt = apiKey.ExpiresAt
|
||||||
};
|
};
|
||||||
@@ -88,7 +88,7 @@ public class ApiKeysController : Controller
|
|||||||
var apiKey = new ApiKey()
|
var apiKey = new ApiKey()
|
||||||
{
|
{
|
||||||
Description = request.Description,
|
Description = request.Description,
|
||||||
PermissionsJson = request.PermissionsJson,
|
Permissions = request.Permissions,
|
||||||
ExpiresAt = request.ExpiresAt
|
ExpiresAt = request.ExpiresAt
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ public class ApiKeysController : Controller
|
|||||||
var response = new CreateApiKeyResponse
|
var response = new CreateApiKeyResponse
|
||||||
{
|
{
|
||||||
Id = finalApiKey.Id,
|
Id = finalApiKey.Id,
|
||||||
PermissionsJson = finalApiKey.PermissionsJson,
|
Permissions = finalApiKey.Permissions,
|
||||||
Description = finalApiKey.Description,
|
Description = finalApiKey.Description,
|
||||||
ExpiresAt = finalApiKey.ExpiresAt,
|
ExpiresAt = finalApiKey.ExpiresAt,
|
||||||
Secret = ApiKeyService.GenerateJwt(finalApiKey)
|
Secret = ApiKeyService.GenerateJwt(finalApiKey)
|
||||||
@@ -125,7 +125,7 @@ public class ApiKeysController : Controller
|
|||||||
{
|
{
|
||||||
Id = apiKey.Id,
|
Id = apiKey.Id,
|
||||||
Description = apiKey.Description,
|
Description = apiKey.Description,
|
||||||
PermissionsJson = apiKey.PermissionsJson,
|
Permissions = apiKey.Permissions,
|
||||||
ExpiresAt = apiKey.ExpiresAt
|
ExpiresAt = apiKey.ExpiresAt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,39 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
|||||||
public class FilesController : Controller
|
public class FilesController : Controller
|
||||||
{
|
{
|
||||||
private readonly string BaseDirectory = "storage";
|
private readonly string BaseDirectory = "storage";
|
||||||
private readonly long ChunkSize = ByteConverter.FromMegaBytes(20).Bytes;
|
private readonly long MaxChunkSize = ByteConverter.FromMegaBytes(20).Bytes;
|
||||||
|
|
||||||
|
[HttpPost("touch")]
|
||||||
|
public async Task CreateFile([FromQuery] string path)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(physicalPath))
|
||||||
|
throw new HttpApiException("A file already exists at that path", 400);
|
||||||
|
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
throw new HttpApiException("A folder already exists at that path", 400);
|
||||||
|
|
||||||
|
await using var fs = System.IO.File.Create(physicalPath);
|
||||||
|
fs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("mkdir")]
|
||||||
|
public Task CreateFolder([FromQuery] string path)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
throw new HttpApiException("A folder already exists at that path", 400);
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(physicalPath))
|
||||||
|
throw new HttpApiException("A file already exists at that path", 400);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(physicalPath);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("list")]
|
[HttpGet("list")]
|
||||||
public Task<FileSystemEntryResponse[]> List([FromQuery] string path)
|
public Task<FileSystemEntryResponse[]> List([FromQuery] string path)
|
||||||
@@ -38,7 +70,7 @@ public class FilesController : Controller
|
|||||||
Name = fi.Name,
|
Name = fi.Name,
|
||||||
Size = fi.Length,
|
Size = fi.Length,
|
||||||
CreatedAt = fi.CreationTimeUtc,
|
CreatedAt = fi.CreationTimeUtc,
|
||||||
IsFile = true,
|
IsFolder = false,
|
||||||
UpdatedAt = fi.LastWriteTimeUtc
|
UpdatedAt = fi.LastWriteTimeUtc
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -55,7 +87,7 @@ public class FilesController : Controller
|
|||||||
Size = 0,
|
Size = 0,
|
||||||
CreatedAt = di.CreationTimeUtc,
|
CreatedAt = di.CreationTimeUtc,
|
||||||
UpdatedAt = di.LastWriteTimeUtc,
|
UpdatedAt = di.LastWriteTimeUtc,
|
||||||
IsFile = false
|
IsFolder = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,23 +97,23 @@ public class FilesController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("upload")]
|
[HttpPost("upload")]
|
||||||
public async Task Upload([FromQuery] string path, [FromQuery] long totalSize, [FromQuery] int chunkId)
|
public async Task Upload([FromQuery] string path, [FromQuery] long chunkSize, [FromQuery] long totalSize, [FromQuery] int chunkId)
|
||||||
{
|
{
|
||||||
if (Request.Form.Files.Count != 1)
|
if (Request.Form.Files.Count != 1)
|
||||||
throw new HttpApiException("You need to provide exactly one file", 400);
|
throw new HttpApiException("You need to provide exactly one file", 400);
|
||||||
|
|
||||||
var file = Request.Form.Files[0];
|
var file = Request.Form.Files[0];
|
||||||
|
|
||||||
if (file.Length > ChunkSize)
|
if (file.Length > chunkSize)
|
||||||
throw new HttpApiException("The provided data exceeds the chunk size limit", 400);
|
throw new HttpApiException("The provided data exceeds the chunk size limit", 400);
|
||||||
|
|
||||||
var chunks = totalSize / ChunkSize;
|
var chunks = totalSize / chunkSize;
|
||||||
chunks += totalSize % ChunkSize > 0 ? 1 : 0;
|
chunks += totalSize % chunkSize > 0 ? 1 : 0;
|
||||||
|
|
||||||
if (chunkId > chunks)
|
if (chunkId > chunks)
|
||||||
throw new HttpApiException("Invalid chunk id: Out of bounds", 400);
|
throw new HttpApiException("Invalid chunk id: Out of bounds", 400);
|
||||||
|
|
||||||
var positionToSkipTo = ChunkSize * chunkId;
|
var positionToSkipTo = chunkSize * chunkId;
|
||||||
|
|
||||||
var safePath = SanitizePath(path);
|
var safePath = SanitizePath(path);
|
||||||
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||||
@@ -156,16 +188,6 @@ public class FilesController : Controller
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("mkdir")]
|
|
||||||
public Task CreateDirectory([FromQuery] string path)
|
|
||||||
{
|
|
||||||
var safePath = SanitizePath(path);
|
|
||||||
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
|
||||||
|
|
||||||
Directory.CreateDirectory(physicalPath);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("download")]
|
[HttpGet("download")]
|
||||||
public async Task Download([FromQuery] string path)
|
public async Task Download([FromQuery] string path)
|
||||||
{
|
{
|
||||||
@@ -431,5 +453,23 @@ public class FilesController : Controller
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private string SanitizePath(string path)
|
private string SanitizePath(string path)
|
||||||
=> path.Replace("..", "");
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Normalize separators
|
||||||
|
path = path.Replace('\\', '/');
|
||||||
|
|
||||||
|
// Remove ".." and "."
|
||||||
|
var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Where(part => part != ".." && part != ".");
|
||||||
|
|
||||||
|
var sanitized = string.Join("/", parts);
|
||||||
|
|
||||||
|
// Ensure it does not start with a slash
|
||||||
|
if (sanitized.StartsWith('/'))
|
||||||
|
sanitized = sanitized.TrimStart('/');
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using MoonCore.Attributes;
|
|
||||||
using Moonlight.ApiServer.Interfaces;
|
|
||||||
using Moonlight.ApiServer.Services;
|
using Moonlight.ApiServer.Services;
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
||||||
|
|
||||||
@@ -12,13 +10,10 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
|||||||
public class SystemController : Controller
|
public class SystemController : Controller
|
||||||
{
|
{
|
||||||
private readonly ApplicationService ApplicationService;
|
private readonly ApplicationService ApplicationService;
|
||||||
private readonly IEnumerable<IDiagnoseProvider> DiagnoseProviders;
|
|
||||||
|
|
||||||
|
public SystemController(ApplicationService applicationService)
|
||||||
public SystemController(ApplicationService applicationService, IEnumerable<IDiagnoseProvider> diagnoseProviders)
|
|
||||||
{
|
{
|
||||||
ApplicationService = applicationService;
|
ApplicationService = applicationService;
|
||||||
DiagnoseProviders = diagnoseProviders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class UsersController : Controller
|
|||||||
Id = x.Id,
|
Id = x.Id,
|
||||||
Email = x.Email,
|
Email = x.Email,
|
||||||
Username = x.Username,
|
Username = x.Username,
|
||||||
PermissionsJson = x.PermissionsJson
|
Permissions = x.Permissions
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ public class UsersController : Controller
|
|||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
PermissionsJson = user.PermissionsJson
|
Permissions = user.Permissions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ public class UsersController : Controller
|
|||||||
Email = request.Email,
|
Email = request.Email,
|
||||||
Username = request.Username,
|
Username = request.Username,
|
||||||
Password = hashedPassword,
|
Password = hashedPassword,
|
||||||
PermissionsJson = request.PermissionsJson
|
Permissions = request.Permissions
|
||||||
};
|
};
|
||||||
|
|
||||||
var finalUser = await UserRepository.Add(user);
|
var finalUser = await UserRepository.Add(user);
|
||||||
@@ -111,7 +111,7 @@ public class UsersController : Controller
|
|||||||
Id = finalUser.Id,
|
Id = finalUser.Id,
|
||||||
Email = finalUser.Email,
|
Email = finalUser.Email,
|
||||||
Username = finalUser.Username,
|
Username = finalUser.Username,
|
||||||
PermissionsJson = finalUser.PermissionsJson
|
Permissions = finalUser.Permissions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,9 +144,9 @@ public class UsersController : Controller
|
|||||||
user.TokenValidTimestamp = DateTime.UtcNow; // Log out user after password change
|
user.TokenValidTimestamp = DateTime.UtcNow; // Log out user after password change
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.PermissionsJson != request.PermissionsJson)
|
if (request.Permissions.Any(x => !user.Permissions.Contains(x)))
|
||||||
{
|
{
|
||||||
user.PermissionsJson = request.PermissionsJson;
|
user.Permissions = request.Permissions;
|
||||||
user.TokenValidTimestamp = DateTime.UtcNow; // Log out user after permission change
|
user.TokenValidTimestamp = DateTime.UtcNow; // Log out user after permission change
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ public class UsersController : Controller
|
|||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
PermissionsJson = user.PermissionsJson
|
Permissions = user.Permissions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,6 @@ public class AuthController : Controller
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
throw new HttpApiException("Unable to load user data", 500);
|
throw new HttpApiException("Unable to load user data", 500);
|
||||||
|
|
||||||
//
|
|
||||||
var permissions = JsonSerializer.Deserialize<string[]>(user.PermissionsJson) ?? [];
|
|
||||||
|
|
||||||
// Generate token
|
// Generate token
|
||||||
var securityTokenDescriptor = new SecurityTokenDescriptor()
|
var securityTokenDescriptor = new SecurityTokenDescriptor()
|
||||||
{
|
{
|
||||||
@@ -90,7 +87,7 @@ public class AuthController : Controller
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"permissions",
|
"permissions",
|
||||||
string.Join(";", permissions)
|
string.Join(";", user.Permissions)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SigningCredentials = new SigningCredentials(
|
SigningCredentials = new SigningCredentials(
|
||||||
@@ -122,13 +119,11 @@ public class AuthController : Controller
|
|||||||
var userId = int.Parse(userIdClaim.Value);
|
var userId = int.Parse(userIdClaim.Value);
|
||||||
var user = await UserRepository.Get().FirstAsync(x => x.Id == userId);
|
var user = await UserRepository.Get().FirstAsync(x => x.Id == userId);
|
||||||
|
|
||||||
var permissions = JsonSerializer.Deserialize<string[]>(user.PermissionsJson) ?? [];
|
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
Permissions = string.Join(";", permissions)
|
Permissions = user.Permissions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
@using Moonlight.Shared.Misc
|
@using Moonlight.Shared.Misc
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="bg-background text-base-content font-inter">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
@@ -18,15 +18,15 @@
|
|||||||
<link rel="apple-touch-icon" sizes="192x192" href="/img/icon-192.png" />
|
<link rel="apple-touch-icon" sizes="192x192" href="/img/icon-192.png" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-gray-950 text-white font-inter h-full">
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
|
||||||
<div class="flex h-screen justify-center items-center">
|
<div class="flex h-screen justify-center items-center">
|
||||||
<div class="sm:max-w-lg">
|
<div class="sm:max-w-lg">
|
||||||
<div id="blazor-loader-label" class="text-center mb-2 text-lg font-semibold"></div>
|
<div id="blazor-loader-label" class="text-center mb-2 text-lg font-semibold"></div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<div class="progress min-w-sm md:min-w-md" role="progressbar">
|
<div class="progress h-3 min-w-sm md:min-w-md" role="progressbar" aria-valuemin="0" aria-valuemax="100">
|
||||||
<div id="blazor-loader-progress" class="progress-bar"></div>
|
<div id="blazor-loader-progress" class="progress-bar progress-primary"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ public partial class OAuth2Controller : Controller
|
|||||||
var userCount = await UserRepository.Get().CountAsync();
|
var userCount = await UserRepository.Get().CountAsync();
|
||||||
|
|
||||||
if (userCount == 0)
|
if (userCount == 0)
|
||||||
user.PermissionsJson = "[\"*\"]";
|
user.Permissions = ["*"];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public class CoreStartup : IPluginStartup
|
|||||||
{
|
{
|
||||||
Scripts =
|
Scripts =
|
||||||
[
|
[
|
||||||
"/_content/Moonlight.Client/js/moonlight.js", "/_content/Moonlight.Client/js/moonCore.js",
|
"/_content/Moonlight.Client/js/moonlight.js", "/_content/MoonCore.Blazor.FlyonUi/moonCore.js",
|
||||||
"/_content/Moonlight.Client/ace/ace.js"
|
"/_content/Moonlight.Client/ace/ace.js"
|
||||||
],
|
],
|
||||||
Styles = ["/css/style.min.css"]
|
Styles = ["/css/style.min.css"]
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageId>Moonlight.ApiServer</PackageId>
|
<PackageId>Moonlight.ApiServer</PackageId>
|
||||||
<Version>2.1.1</Version>
|
<Version>2.1.2</Version>
|
||||||
<Authors>Moonlight Panel</Authors>
|
<Authors>Moonlight Panel</Authors>
|
||||||
<Description>A build of the api server for moonlight development</Description>
|
<Description>A build of the api server for moonlight development</Description>
|
||||||
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
||||||
@@ -34,9 +34,9 @@
|
|||||||
<PackageReference Include="Hangfire.EntityFrameworkCore" Version="0.7.0" />
|
<PackageReference Include="Hangfire.EntityFrameworkCore" Version="0.7.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
|
||||||
<PackageReference Include="MoonCore" Version="1.9.1" />
|
<PackageReference Include="MoonCore" Version="1.9.2" />
|
||||||
<PackageReference Include="MoonCore.Extended" Version="1.3.5" />
|
<PackageReference Include="MoonCore.Extended" Version="1.3.5" />
|
||||||
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.1" />
|
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.2" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.12.0-beta.1" />
|
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.12.0-beta.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
||||||
@@ -58,5 +58,6 @@
|
|||||||
<Compile Remove="storage\**\*" />
|
<Compile Remove="storage\**\*" />
|
||||||
<Content Remove="storage\**\*" />
|
<Content Remove="storage\**\*" />
|
||||||
<None Remove="storage\**\*" />
|
<None Remove="storage\**\*" />
|
||||||
|
<None Remove="Properties\launchSettings.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"http": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": false,
|
|
||||||
"applicationUrl": "http://localhost:5165",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"HTTP_PROXY": "",
|
|
||||||
"HTTPS_PROXY": ""
|
|
||||||
},
|
|
||||||
"hotReloadEnabled": true
|
|
||||||
},
|
|
||||||
"WASM Debug": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": false,
|
|
||||||
"applicationUrl": "http://localhost:5165",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"HTTP_PROXY": "",
|
|
||||||
"HTTPS_PROXY": ""
|
|
||||||
},
|
|
||||||
"hotReloadEnabled": true,
|
|
||||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,13 +20,11 @@ public class ApiKeyService
|
|||||||
|
|
||||||
public string GenerateJwt(ApiKey apiKey)
|
public string GenerateJwt(ApiKey apiKey)
|
||||||
{
|
{
|
||||||
var permissions = JsonSerializer.Deserialize<string[]>(apiKey.PermissionsJson) ?? [];
|
|
||||||
|
|
||||||
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
|
||||||
var descriptor = new SecurityTokenDescriptor()
|
var descriptor = new SecurityTokenDescriptor()
|
||||||
{
|
{
|
||||||
Expires = apiKey.ExpiresAt,
|
Expires = apiKey.ExpiresAt.UtcDateTime,
|
||||||
IssuedAt = DateTime.Now,
|
IssuedAt = DateTime.Now,
|
||||||
NotBefore = DateTime.Now.AddMinutes(-1),
|
NotBefore = DateTime.Now.AddMinutes(-1),
|
||||||
Claims = new Dictionary<string, object>()
|
Claims = new Dictionary<string, object>()
|
||||||
@@ -37,7 +35,7 @@ public class ApiKeyService
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"permissions",
|
"permissions",
|
||||||
string.Join(";", permissions)
|
string.Join(";", apiKey.Permissions)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SigningCredentials = new SigningCredentials(
|
SigningCredentials = new SigningCredentials(
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.7"/>
|
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.8" />
|
||||||
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.1"/>
|
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.2" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.5" PrivateAssets="all"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.5" PrivateAssets="all"/>
|
||||||
@@ -50,6 +50,11 @@
|
|||||||
<PackagePath>styles</PackagePath>
|
<PackagePath>styles</PackagePath>
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Styles\mappings\mooncore.map">
|
||||||
|
<Pack>true</Pack>
|
||||||
|
<PackagePath>styles</PackagePath>
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
@keyframes shimmer {
|
|
||||||
0% {
|
|
||||||
background-position: 0 0
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
background-position: -200% 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
/* Buttons */
|
|
||||||
|
|
||||||
.btn,
|
|
||||||
.btn-lg,
|
|
||||||
.btn-sm,
|
|
||||||
.btn-xs {
|
|
||||||
@apply cursor-pointer font-medium text-sm inline-flex items-center justify-center border border-transparent rounded-lg leading-5 shadow-sm transition active:scale-95;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
@apply px-3 py-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-lg {
|
|
||||||
@apply px-4 py-3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sm {
|
|
||||||
@apply px-2.5 py-1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-xs {
|
|
||||||
@apply px-2 py-0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Colors */
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
@apply bg-primary hover:bg-primary/90 focus-visible:outline-primary text-diffcolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
@apply bg-secondary hover:bg-secondary/90 focus-visible:outline-secondary text-diffcolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-tertiary {
|
|
||||||
@apply bg-tertiary hover:bg-tertiary/90 focus-visible:outline-tertiary text-diffcolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger {
|
|
||||||
@apply bg-danger hover:bg-danger/90 focus-visible:outline-danger text-diffcolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-warning {
|
|
||||||
@apply bg-warning hover:bg-warning/90 focus-visible:outline-warning text-diffcolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-info {
|
|
||||||
@apply bg-info hover:bg-info/90 focus-visible:outline-info text-diffcolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-success {
|
|
||||||
@apply bg-success hover:bg-success/90 focus-visible:outline-success text-diffcolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disabled Buttons */
|
|
||||||
|
|
||||||
.btn:disabled,
|
|
||||||
.btn-lg:disabled,
|
|
||||||
.btn-sm:disabled,
|
|
||||||
.btn-xs:disabled {
|
|
||||||
@apply opacity-50 cursor-not-allowed pointer-events-none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Colors for Disabled States */
|
|
||||||
|
|
||||||
.btn-primary:disabled {
|
|
||||||
@apply bg-primary/80 text-gray-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:disabled {
|
|
||||||
@apply bg-secondary/80 text-gray-400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-tertiary:disabled {
|
|
||||||
@apply bg-tertiary/80 text-gray-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger:disabled {
|
|
||||||
@apply bg-danger/80 text-gray-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-warning:disabled {
|
|
||||||
@apply bg-warning/80 text-gray-400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-info:disabled {
|
|
||||||
@apply bg-info/80 text-gray-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-success:disabled {
|
|
||||||
@apply bg-success/80 text-gray-300;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
.card {
|
|
||||||
@apply flex flex-col bg-gray-800 shadow-sm rounded-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
@apply p-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header:has(+ .card-body) {
|
|
||||||
@apply pb-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
@apply text-2xl font-semibold text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
@apply p-5 text-gray-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-footer {
|
|
||||||
@apply p-5;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback') layer(base);
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap') layer(base);
|
|
||||||
@import url("https://cdn.jsdelivr.net/npm/lucide-static/font/lucide.css") layer(base);
|
|
||||||
|
|
||||||
@theme {
|
|
||||||
--font-inter: "Inter", var(--font-sans);
|
|
||||||
--font-scp: "Source Code Pro", var(--font-mono);
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
/* Forms */
|
|
||||||
input[type="search"]::-webkit-search-decoration,
|
|
||||||
input[type="search"]::-webkit-search-cancel-button,
|
|
||||||
input[type="search"]::-webkit-search-results-button,
|
|
||||||
input[type="search"]::-webkit-search-results-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input,
|
|
||||||
.form-textarea,
|
|
||||||
.form-multiselect,
|
|
||||||
.form-select,
|
|
||||||
.form-checkbox,
|
|
||||||
.form-radio {
|
|
||||||
@apply bg-gray-700/60 border-2 focus:ring-0 focus:ring-offset-0 disabled:bg-gray-700/30 disabled:border-gray-700 disabled:hover:border-gray-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-checkbox {
|
|
||||||
@apply rounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input,
|
|
||||||
.form-textarea,
|
|
||||||
.form-multiselect,
|
|
||||||
.form-select {
|
|
||||||
@apply text-sm text-gray-100 leading-5 py-2 px-3 border-gray-700 focus:border-primary shadow-sm rounded-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input,
|
|
||||||
.form-textarea {
|
|
||||||
@apply placeholder-gray-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-select {
|
|
||||||
@apply pr-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-checkbox,
|
|
||||||
.form-radio {
|
|
||||||
@apply text-primary checked:bg-primary checked:border-transparent border border-gray-700/60 focus:border-primary/50;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Switch element */
|
|
||||||
.form-switch {
|
|
||||||
@apply relative select-none;
|
|
||||||
width: 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-switch label {
|
|
||||||
@apply block overflow-hidden cursor-pointer h-6 rounded-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-switch label > span:first-child {
|
|
||||||
@apply absolute block rounded-full;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
right: 50%;
|
|
||||||
transition: all .15s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-switch input[type="checkbox"]:checked + label {
|
|
||||||
@apply bg-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-switch input[type="checkbox"]:checked + label > span:first-child {
|
|
||||||
left: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-switch input[type="checkbox"]:disabled + label {
|
|
||||||
@apply cursor-not-allowed bg-gray-700/20 border border-gray-700/60;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-switch input[type="checkbox"]:disabled + label > span:first-child {
|
|
||||||
@apply bg-gray-600;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
.loader-spinner {
|
|
||||||
width: 2.5rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
position: relative;
|
|
||||||
animation: loader-spinner-rotate 1s linear infinite
|
|
||||||
}
|
|
||||||
.loader-spinner::before {
|
|
||||||
content: "";
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: absolute;
|
|
||||||
inset: 0px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 3px solid #FFF;
|
|
||||||
animation: loader-spinner-prixClipFix 2s linear infinite ;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-spinner-rotate {
|
|
||||||
100% {transform: rotate(360deg)}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-spinner-prixClipFix {
|
|
||||||
0% {clip-path:polygon(50% 50%,0 0,0 0,0 0,0 0,0 0)}
|
|
||||||
25% {clip-path:polygon(50% 50%,0 0,100% 0,100% 0,100% 0,100% 0)}
|
|
||||||
50% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,100% 100%,100% 100%)}
|
|
||||||
75% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 100%)}
|
|
||||||
100% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 0)}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
.progress {
|
|
||||||
@apply bg-gray-800 rounded-full overflow-hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
@apply bg-primary rounded-full h-3;
|
|
||||||
transition: width 0.6s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar.progress-intermediate {
|
|
||||||
animation: progress-animation 1s infinite linear;
|
|
||||||
transform-origin: 0 50%
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes progress-animation {
|
|
||||||
0% {
|
|
||||||
transform: translateX(0) scaleX(0);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
transform: translateX(0) scaleX(0.4);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(100%) scaleX(0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
* {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: #64748b transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-scrollbar {
|
|
||||||
scrollbar-width: none;
|
|
||||||
scrollbar-color: transparent transparent;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
.tabs {
|
|
||||||
@apply flex gap-x-1 bg-gray-800 rounded-lg transition p-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs .tabs-segment {
|
|
||||||
@apply cursor-pointer font-medium text-sm inline-flex items-center justify-center border border-transparent rounded-lg leading-5 text-gray-300 hover:text-primary py-1.5 px-3.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs .tabs-segment-active {
|
|
||||||
@apply bg-primary hover:bg-primary/90 focus-visible:outline-primary text-diffcolor hover:text-diffcolor;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
@theme {
|
|
||||||
/* Color Variables */
|
|
||||||
|
|
||||||
--color-primary: oklch(.511 .262 276.966);
|
|
||||||
--color-secondary: rgb(31, 41, 55);
|
|
||||||
--color-tertiary: oklch(.627 .265 303.9);
|
|
||||||
--color-warning: oklch(.828 .189 84.429);
|
|
||||||
--color-danger: oklch(.586 .253 17.585);
|
|
||||||
--color-success: oklch(.627 .194 149.214);
|
|
||||||
--color-info: oklch(.546 .245 262.881);
|
|
||||||
|
|
||||||
/* Gray */
|
|
||||||
|
|
||||||
--color-gray-50: #e8eefc;
|
|
||||||
--color-gray-100: rgb(249 249 249);
|
|
||||||
--color-gray-200: rgb(241 241 242);
|
|
||||||
--color-gray-300: rgb(219 223 233);
|
|
||||||
--color-gray-400: rgb(181 181 195);
|
|
||||||
--color-gray-500: rgb(153 161 183);
|
|
||||||
--color-gray-600: rgb(112 121 147);
|
|
||||||
--color-gray-700: rgb(68 78 107);
|
|
||||||
--color-gray-750: rgb(41 50 73);
|
|
||||||
--color-gray-800: rgb(28 36 56);
|
|
||||||
--color-gray-900: rgb(17 23 33);
|
|
||||||
--color-gray-950: rgb(14 18 28);
|
|
||||||
|
|
||||||
/*
|
|
||||||
--color-gray-50: #e8eefc;
|
|
||||||
--color-gray-100: #ccd6ee;
|
|
||||||
--color-gray-200: #bec9e1;
|
|
||||||
--color-gray-300: #a3b2d5;
|
|
||||||
--color-gray-400: #7d91bb;
|
|
||||||
--color-gray-500: #5f719d;
|
|
||||||
--color-gray-600: #1a2640;
|
|
||||||
--color-gray-700: #101a2e;
|
|
||||||
--color-gray-750: #0f1729;
|
|
||||||
--color-gray-800: #0c1221;
|
|
||||||
--color-gray-900: #050a16;
|
|
||||||
--color-gray-950: #03060e;
|
|
||||||
*/
|
|
||||||
/* Full Colors */
|
|
||||||
|
|
||||||
--color-white: rgb(255 255 255);
|
|
||||||
--color-black: rgb(0 0 0);
|
|
||||||
--color-diffcolor: rgb(var(--color-white));
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
@theme {
|
|
||||||
/* Gray (Inverted for White Mode) */
|
|
||||||
|
|
||||||
--color-gray-100: rgb(14 18 28); /* Formerly gray-950 */
|
|
||||||
--color-gray-200: rgb(17 23 33); /* Formerly gray-900 */
|
|
||||||
--color-gray-300: rgb(28 36 56); /* Formerly gray-800 */
|
|
||||||
--color-gray-400: rgb(41 50 73); /* Formerly gray-750 */
|
|
||||||
--color-gray-500: rgb(68 78 107); /* Formerly gray-700 */
|
|
||||||
--color-gray-600: rgb(112 121 147); /* Formerly gray-600 */
|
|
||||||
--color-gray-700: rgb(153 161 183); /* Formerly gray-500 */
|
|
||||||
--color-gray-750: rgb(181 181 195); /* Formerly gray-400 */
|
|
||||||
--color-gray-800: rgb(219 223 233); /* Formerly gray-300 */
|
|
||||||
--color-gray-900: rgb(241 241 242); /* Formerly gray-200 */
|
|
||||||
--color-gray-950: rgb(249 249 249); /* Formerly gray-100 */
|
|
||||||
|
|
||||||
/* Full Colors (Inverted) */
|
|
||||||
|
|
||||||
--color-white: rgb(0 0 0); /* Inverted to black */
|
|
||||||
--color-black: rgb(255 255 255); /* Inverted to white */
|
|
||||||
|
|
||||||
/* Special light mode stuff */
|
|
||||||
--color-diffcolor: rgb(255 255 255);
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
@import "./additions/fonts.css";
|
|
||||||
@import "./additions/theme.css" layer(theme);
|
|
||||||
|
|
||||||
/* @import "./additions/theme.white.css"; */
|
|
||||||
|
|
||||||
@import "./additions/buttons.css" layer(components);
|
|
||||||
@import "./additions/cards.css" layer(components);
|
|
||||||
@import "./additions/forms.css" layer(components);
|
|
||||||
@import "./additions/progress.css" layer(components);
|
|
||||||
@import "./additions/scrollbar.css" layer(components);
|
|
||||||
@import "./additions/loaders.css" layer(components);
|
|
||||||
@import "./additions/tabs.css" layer(components);
|
|
||||||
|
|
||||||
@source "./mappings/*.map";
|
|
||||||
|
|
||||||
#blazor-error-ui {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#blazor-loader-label:after {
|
|
||||||
content: var(--blazor-load-percentage-text, "Loading");
|
|
||||||
}
|
|
||||||
|
|
||||||
#blazor-loader-progress {
|
|
||||||
width: var(--blazor-load-percentage, 0%);
|
|
||||||
}
|
|
||||||
30
Moonlight.Client.Runtime/Styles/extract-classes.js
Normal file
30
Moonlight.Client.Runtime/Styles/extract-classes.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// extract-classes.js
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
module.exports = (opts = {}) => {
|
||||||
|
const classSet = new Set();
|
||||||
|
|
||||||
|
return {
|
||||||
|
postcssPlugin: 'extract-tailwind-classes',
|
||||||
|
Rule(rule) {
|
||||||
|
const selectorParser = require('postcss-selector-parser');
|
||||||
|
selectorParser(selectors => {
|
||||||
|
selectors.walkClasses(node => {
|
||||||
|
classSet.add(node.value);
|
||||||
|
});
|
||||||
|
}).processSync(rule.selector);
|
||||||
|
},
|
||||||
|
OnceExit() {
|
||||||
|
const classArray = Array.from(classSet).sort();
|
||||||
|
|
||||||
|
if (!fs.existsSync("./mappings")){
|
||||||
|
fs.mkdirSync("./mappings");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync('./mappings/mooncore.map', classArray.join('\n'));
|
||||||
|
console.log(`✅ Extracted ${classArray.length} Tailwind classes to tailwind-classes.txt`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.postcss = true;
|
||||||
1093
Moonlight.Client.Runtime/Styles/mappings/mooncore.map
Normal file → Executable file
1093
Moonlight.Client.Runtime/Styles/mappings/mooncore.map
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
1319
Moonlight.Client.Runtime/Styles/package-lock.json
generated
1319
Moonlight.Client.Runtime/Styles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,23 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"name": "styles",
|
||||||
"@tailwindcss/cli": "^4.1.4",
|
"version": "1.0.0",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"description": "",
|
||||||
"tailwindcss": "^4.1.4",
|
"main": "index.js",
|
||||||
"xml2js": "^0.6.2"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pretailwind-build": "node resolveNuget.js ../Moonlight.Client.Runtime.csproj",
|
"tailwind": "npx postcss styles.css -o ../wwwroot/css/style.min.css --watch",
|
||||||
"tailwind-build": "npx tailwindcss -i style.css -o ../wwwroot/css/style.min.css",
|
"tailwind-build": "npx postcss styles.css -o ../wwwroot/css/style.min.css",
|
||||||
"pretailwind": "node resolveNuget.js ../Moonlight.Client.Runtime.csproj",
|
"mappings": "EXTRACT_CLASSES=true npx postcss styles.css -o ../wwwroot/css/style.min.css "
|
||||||
"tailwind": "npx tailwindcss -i style.css -o ../wwwroot/css/style.min.css --watch"
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
|
"flyonui": "^2.2.0",
|
||||||
|
"tailwindcss": "^4.1.11",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"postcss-cli": "^11.0.1",
|
||||||
|
"postcss-selector-parser": "^7.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
Moonlight.Client.Runtime/Styles/postcss.config.js
Normal file
11
Moonlight.Client.Runtime/Styles/postcss.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const tailwindcss = require('@tailwindcss/postcss');
|
||||||
|
const extractClasses = require('./extract-classes');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
tailwindcss
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if(process.env.EXTRACT_CLASSES === "true")
|
||||||
|
module.exports.plugins.push(extractClasses);
|
||||||
@@ -1 +0,0 @@
|
|||||||
@import "./additions/fonts.css";
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const os = require('os');
|
|
||||||
const xml2js = require('xml2js');
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
function getPackageRefs(csprojPath) {
|
|
||||||
const xml = fs.readFileSync(csprojPath, 'utf8');
|
|
||||||
const parser = new xml2js.Parser();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
parser.parseString(xml, (err, result) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
|
|
||||||
const itemGroups = result.Project.ItemGroup || [];
|
|
||||||
const refs = [];
|
|
||||||
|
|
||||||
for (const group of itemGroups) {
|
|
||||||
const packages = group.PackageReference || [];
|
|
||||||
for (const pkg of packages) {
|
|
||||||
const name = pkg.$.Include;
|
|
||||||
const version = pkg.$.Version || (pkg.Version && pkg.Version[0]);
|
|
||||||
if (name && version) {
|
|
||||||
refs.push({ name: name.toLowerCase(), version });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve(refs);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const csprojPath = process.argv[2];
|
|
||||||
if (!csprojPath || !fs.existsSync(csprojPath)) {
|
|
||||||
console.error('Usage: Missing csproj path');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nugetPath = path.join(os.homedir(), '.nuget', 'packages');
|
|
||||||
const moonlightDir = path.join(__dirname, 'node_modules', 'moonlight');
|
|
||||||
fs.mkdirSync(moonlightDir, { recursive: true });
|
|
||||||
|
|
||||||
const refs = await getPackageRefs(csprojPath);
|
|
||||||
|
|
||||||
var outputCss = "";
|
|
||||||
var preOutputCss = "";
|
|
||||||
|
|
||||||
for (const { name, version } of refs) {
|
|
||||||
const packagePath = path.join(nugetPath, name, version);
|
|
||||||
const exportsFile = path.join(packagePath, 'styles', 'exports.css');
|
|
||||||
const preTailwindFile = path.join(packagePath, 'styles', 'preTailwind.css');
|
|
||||||
const sourceFolder = path.join(packagePath, 'src');
|
|
||||||
|
|
||||||
const rel = (p) => p.replace(/\\/g, '/');
|
|
||||||
|
|
||||||
if (fs.existsSync(exportsFile)) {
|
|
||||||
outputCss += `@import "${rel(exportsFile)}";\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(preTailwindFile)) {
|
|
||||||
preOutputCss += `@import "${rel(preTailwindFile)}";\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(sourceFolder)) {
|
|
||||||
outputCss += `@source "${rel(path.join(sourceFolder, "**", "*.razor"))}";\n`;
|
|
||||||
outputCss += `@source "${rel(path.join(sourceFolder, "**", "*.cs"))}";\n`;
|
|
||||||
outputCss += `@source "${rel(path.join(sourceFolder, "**", "*.html"))}";\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(moonlightDir, 'nuget.css'), outputCss);
|
|
||||||
fs.writeFileSync(path.join(moonlightDir, 'preTailwind.nuget.css'), preOutputCss);
|
|
||||||
console.log(`Generated nuget.css in ${moonlightDir}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
@import "./preTailwind.css";
|
|
||||||
@import "moonlight/preTailwind.nuget.css";
|
|
||||||
|
|
||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
@import "./exports.css";
|
|
||||||
@import "moonlight/nuget.css";
|
|
||||||
|
|
||||||
@plugin "@tailwindcss/forms" {
|
|
||||||
strategy: "base";
|
|
||||||
}
|
|
||||||
|
|
||||||
@source "../**/*.razor";
|
|
||||||
@source "../**/*.cs";
|
|
||||||
@source "../**/*.html";
|
|
||||||
|
|
||||||
@source "../../Moonlight.Client/**/*.razor";
|
|
||||||
@source "../../Moonlight.Client/**/*.cs";
|
|
||||||
@source "../../Moonlight.Client/**/*.html";
|
|
||||||
|
|
||||||
@source "./mappings/*.map";
|
|
||||||
112
Moonlight.Client.Runtime/Styles/styles.css
Normal file
112
Moonlight.Client.Runtime/Styles/styles.css
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback') layer(base);
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap') layer()base;
|
||||||
|
@import url("https://cdn.jsdelivr.net/npm/lucide-static/font/lucide.css") layer(base);
|
||||||
|
|
||||||
|
@import "tailwindcss";
|
||||||
|
@import "./node_modules/flyonui/variants.css";
|
||||||
|
@import "./theme.css";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--font-inter: "Inter", var(--font-sans);
|
||||||
|
--font-scp: "Source Code Pro", var(--font-mono);
|
||||||
|
|
||||||
|
--color-background: var(--mooncore-color-background);
|
||||||
|
--color-base-150: var(--mooncore-color-base-150);
|
||||||
|
--color-base-250: var(--mooncore-color-base-250);
|
||||||
|
}
|
||||||
|
|
||||||
|
@plugin "flyonui" {
|
||||||
|
themes: mooncore --default;
|
||||||
|
}
|
||||||
|
|
||||||
|
@source "./node_modules/flyonui/dist/index.js";
|
||||||
|
|
||||||
|
@source "../**/*.razor";
|
||||||
|
@source "../**/*.cs";
|
||||||
|
@source "../**/*.html";
|
||||||
|
|
||||||
|
@source "../../Moonlight.Client/**/*.cs";
|
||||||
|
@source "../../Moonlight.Client/**/*.html";
|
||||||
|
@source "../../Moonlight.Client/**/*.razor";
|
||||||
|
@source "../../Moonlight.Client/Styles/mappings/*.map";
|
||||||
|
|
||||||
|
@source "../../Moonlight.ApiServer/**/*.razor";
|
||||||
|
|
||||||
|
#blazor-error-ui {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blazor-loader-label:after {
|
||||||
|
content: var(--blazor-load-percentage-text, "Loading");
|
||||||
|
}
|
||||||
|
|
||||||
|
#blazor-loader-progress {
|
||||||
|
width: var(--blazor-load-percentage, 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@plugin "flyonui/theme" {
|
||||||
|
name: "mooncore";
|
||||||
|
default: true;
|
||||||
|
prefersdark: true;
|
||||||
|
color-scheme: "dark";
|
||||||
|
--color-base-100: var(--mooncore-color-base-100);
|
||||||
|
--color-base-200: var(--mooncore-color-base-200);
|
||||||
|
--color-base-300: var(--mooncore-color-base-300);
|
||||||
|
--color-base-content: var(--mooncore-color-base-content);
|
||||||
|
--color-primary: var(--mooncore-color-primary);
|
||||||
|
--color-primary-content: var(--mooncore-color-primary-content);
|
||||||
|
--color-secondary: var(--mooncore-color-secondary);
|
||||||
|
--color-secondary-content: var(--mooncore-color-secondary-content);
|
||||||
|
--color-accent: var(--mooncore-color-accent);
|
||||||
|
--color-accent-content: var(--mooncore-color-accent-content);
|
||||||
|
--color-neutral: var(--mooncore-color-neutral);
|
||||||
|
--color-neutral-content: var(--mooncore-color-neutral-content);
|
||||||
|
--color-info: var(--mooncore-color-info);
|
||||||
|
--color-info-content: var(--mooncore-color-info-content);
|
||||||
|
--color-success: var(--mooncore-color-success);
|
||||||
|
--color-success-content: var(--mooncore-color-success-content);
|
||||||
|
--color-warning: var(--mooncore-color-warning);
|
||||||
|
--color-warning-content: var(--mooncore-color-warning-content);
|
||||||
|
--color-error: var(--mooncore-color-error);
|
||||||
|
--color-error-content: var(--mooncore-color-error-content);
|
||||||
|
--radius-selector: var(--mooncore-radius-selector);
|
||||||
|
--radius-field: var(--mooncore-radius-field);
|
||||||
|
--radius-box: var(--mooncore-radius-box);
|
||||||
|
--size-selector: var(--mooncore-size-selector);
|
||||||
|
--size-field: var(--mooncore-size-field);
|
||||||
|
--border: var(--mooncore-border);
|
||||||
|
--depth: var(--mooncore-depth);
|
||||||
|
--noise: var(--mooncore-noise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.btn {
|
||||||
|
@apply text-sm font-medium inline-flex items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
@apply border-base-content/30 bg-base-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
@apply !border-base-content/20 border-2 ring-0! outline-0! focus:border-primary! focus-within:border-primary! bg-base-200/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advance-select-toggle {
|
||||||
|
@apply !border-base-content/20 border-2 ring-0! outline-0! focus:border-primary! focus-within:border-primary! bg-base-200/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
:where(th, td) {
|
||||||
|
@apply py-1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
@apply px-2.5 py-1.5 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
@apply bg-base-150;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Moonlight.Client.Runtime/Styles/theme.css
Normal file
33
Moonlight.Client.Runtime/Styles/theme.css
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@theme {
|
||||||
|
--mooncore-color-background: #0c0f18;
|
||||||
|
--mooncore-color-base-100: #1e2b47;
|
||||||
|
--mooncore-color-base-150: #1a2640;
|
||||||
|
--mooncore-color-base-200: #101a2e;
|
||||||
|
--mooncore-color-base-250: #0f1729;
|
||||||
|
--mooncore-color-base-300: #0c1221;
|
||||||
|
--mooncore-color-base-content: #dde5f5;
|
||||||
|
--mooncore-color-primary: oklch(.511 .262 276.966);
|
||||||
|
--mooncore-color-primary-content: #dde5f5;
|
||||||
|
--mooncore-color-secondary: oklch(37% 0.034 259.733);
|
||||||
|
--mooncore-color-secondary-content: #dde5f5;
|
||||||
|
--mooncore-color-accent: oklch(.627 .265 303.9);
|
||||||
|
--mooncore-color-accent-content: #dde5f5;
|
||||||
|
--mooncore-color-neutral: #dde5f5;
|
||||||
|
--mooncore-color-neutral-content: oklch(14% 0.005 285.823);
|
||||||
|
--mooncore-color-info: oklch(.546 .245 262.881);
|
||||||
|
--mooncore-color-info-content: #dde5f5;
|
||||||
|
--mooncore-color-success: oklch(.627 .194 149.214);
|
||||||
|
--mooncore-color-success-content: #dde5f5;
|
||||||
|
--mooncore-color-warning: oklch(.828 .189 84.429);
|
||||||
|
--mooncore-color-warning-content: #dde5f5;
|
||||||
|
--mooncore-color-error: oklch(.586 .253 17.585);
|
||||||
|
--mooncore-color-error-content: #dde5f5;
|
||||||
|
--mooncore-radius-selector: 0.25rem;
|
||||||
|
--mooncore-radius-field: 0.5rem;
|
||||||
|
--mooncore-radius-box: 0.5rem;
|
||||||
|
--mooncore-size-selector: 0.25rem;
|
||||||
|
--mooncore-size-field: 0.25rem;
|
||||||
|
--mooncore-border: 1px;
|
||||||
|
--mooncore-depth: 0;
|
||||||
|
--mooncore-noise: 0;
|
||||||
|
}
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
using MoonCore.Blazor.Services;
|
|
||||||
using MoonCore.Blazor.Tailwind.Fm;
|
|
||||||
using MoonCore.Blazor.Tailwind.Fm.Models;
|
|
||||||
using MoonCore.Helpers;
|
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Sys.Files;
|
|
||||||
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
|
||||||
|
|
||||||
namespace Moonlight.Client.Implementations;
|
|
||||||
|
|
||||||
public class SysFileSystemProvider : IFileSystemProvider, ICompressFileSystemProvider
|
|
||||||
{
|
|
||||||
private readonly DownloadService DownloadService;
|
|
||||||
private readonly HttpApiClient HttpApiClient;
|
|
||||||
private readonly LocalStorageService LocalStorageService;
|
|
||||||
private readonly string BaseApiUrl = "api/admin/system/files";
|
|
||||||
|
|
||||||
public CompressType[] CompressTypes { get; } =
|
|
||||||
[
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Extension = "zip",
|
|
||||||
DisplayName = "ZIP Archive"
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Extension = "tar.gz",
|
|
||||||
DisplayName = "GZ Compressed Tar Archive"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
public SysFileSystemProvider(
|
|
||||||
HttpApiClient httpApiClient,
|
|
||||||
DownloadService downloadService,
|
|
||||||
LocalStorageService localStorageService
|
|
||||||
)
|
|
||||||
{
|
|
||||||
HttpApiClient = httpApiClient;
|
|
||||||
DownloadService = downloadService;
|
|
||||||
LocalStorageService = localStorageService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<FileSystemEntry[]> List(string path)
|
|
||||||
{
|
|
||||||
var entries = await HttpApiClient.GetJson<FileSystemEntryResponse[]>(
|
|
||||||
$"{BaseApiUrl}/list?path={path}"
|
|
||||||
);
|
|
||||||
|
|
||||||
return entries.Select(x => new FileSystemEntry()
|
|
||||||
{
|
|
||||||
Name = x.Name,
|
|
||||||
Size = x.Size,
|
|
||||||
CreatedAt = x.CreatedAt,
|
|
||||||
IsFile = x.IsFile,
|
|
||||||
UpdatedAt = x.UpdatedAt
|
|
||||||
}).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Create(string path, Stream stream)
|
|
||||||
{
|
|
||||||
await Upload(_ => Task.CompletedTask, path, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Move(string oldPath, string newPath)
|
|
||||||
=> await HttpApiClient.Post($"{BaseApiUrl}/move?oldPath={oldPath}&newPath={newPath}");
|
|
||||||
|
|
||||||
public async Task Delete(string path)
|
|
||||||
=> await HttpApiClient.Delete($"{BaseApiUrl}/delete?path={path}");
|
|
||||||
|
|
||||||
public async Task CreateDirectory(string path)
|
|
||||||
=> await HttpApiClient.Post($"{BaseApiUrl}/mkdir?path={path}");
|
|
||||||
|
|
||||||
public async Task<Stream> Read(string path)
|
|
||||||
=> await HttpApiClient.GetStream($"{BaseApiUrl}/download?path={path}");
|
|
||||||
|
|
||||||
public async Task Download(Func<int, Task> updateProgress, string path, string fileName)
|
|
||||||
{
|
|
||||||
var accessToken = await LocalStorageService.GetString("AccessToken");
|
|
||||||
|
|
||||||
await DownloadService.DownloadUrl(fileName, $"{BaseApiUrl}/download?path={path}",
|
|
||||||
async (loaded, total) =>
|
|
||||||
{
|
|
||||||
var percent = total == 0 ? 0 : (int)Math.Round((float)loaded / total * 100);
|
|
||||||
await updateProgress.Invoke(percent);
|
|
||||||
},
|
|
||||||
onConfigureHeaders: headers => { headers.Add("Authorization", $"Bearer {accessToken}"); }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Upload(Func<int, Task> updateProgress, string path, Stream stream)
|
|
||||||
{
|
|
||||||
var size = stream.Length;
|
|
||||||
var chunkSize = ByteConverter.FromMegaBytes(20).Bytes;
|
|
||||||
|
|
||||||
var chunks = size / chunkSize;
|
|
||||||
chunks += size % chunkSize > 0 ? 1 : 0;
|
|
||||||
|
|
||||||
for (var chunkId = 0; chunkId < chunks; chunkId++)
|
|
||||||
{
|
|
||||||
var percent = (int)Math.Round((chunkId + 1f) / chunks * 100);
|
|
||||||
await updateProgress.Invoke(percent);
|
|
||||||
|
|
||||||
var buffer = new byte[chunkSize];
|
|
||||||
var bytesRead = await stream.ReadAsync(buffer);
|
|
||||||
|
|
||||||
var uploadForm = new MultipartFormDataContent();
|
|
||||||
uploadForm.Add(new ByteArrayContent(buffer, 0, bytesRead), "file", path);
|
|
||||||
|
|
||||||
await HttpApiClient.Post(
|
|
||||||
$"{BaseApiUrl}/upload?path={path}&totalSize={size}&chunkId={chunkId}",
|
|
||||||
uploadForm
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Compress(CompressType type, string path, string[] itemsToCompress)
|
|
||||||
{
|
|
||||||
await HttpApiClient.Post($"{BaseApiUrl}/compress", new CompressRequest()
|
|
||||||
{
|
|
||||||
Type = type.Extension,
|
|
||||||
Path = path,
|
|
||||||
ItemsToCompress = itemsToCompress
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Decompress(CompressType type, string path, string destination)
|
|
||||||
{
|
|
||||||
await HttpApiClient.Post($"{BaseApiUrl}/decompress", new DecompressRequest()
|
|
||||||
{
|
|
||||||
Type = type.Extension,
|
|
||||||
Path = path,
|
|
||||||
Destination = destination
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
88
Moonlight.Client/Implementations/SystemFsAccess.cs
Normal file
88
Moonlight.Client/Implementations/SystemFsAccess.cs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
using MoonCore.Blazor.FlyonUi.Files;
|
||||||
|
using MoonCore.Blazor.FlyonUi.Files.Manager;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
||||||
|
|
||||||
|
namespace Moonlight.Client.Implementations;
|
||||||
|
|
||||||
|
public class SystemFsAccess : IFsAccess
|
||||||
|
{
|
||||||
|
private readonly HttpApiClient ApiClient;
|
||||||
|
|
||||||
|
private const string BaseApiUrl = "api/admin/system/files";
|
||||||
|
|
||||||
|
public SystemFsAccess(HttpApiClient apiClient)
|
||||||
|
{
|
||||||
|
ApiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateFile(string path)
|
||||||
|
{
|
||||||
|
await ApiClient.Post(
|
||||||
|
$"{BaseApiUrl}/touch?path={path}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateDirectory(string path)
|
||||||
|
{
|
||||||
|
await ApiClient.Post(
|
||||||
|
$"{BaseApiUrl}/mkdir?path={path}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FsEntry[]> List(string path)
|
||||||
|
{
|
||||||
|
var entries = await ApiClient.GetJson<FileSystemEntryResponse[]>(
|
||||||
|
$"{BaseApiUrl}/list?path={path}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return entries.Select(x => new FsEntry()
|
||||||
|
{
|
||||||
|
Name = x.Name,
|
||||||
|
CreatedAt = x.CreatedAt,
|
||||||
|
IsFolder = x.IsFolder,
|
||||||
|
Size = x.Size,
|
||||||
|
UpdatedAt = x.UpdatedAt
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Move(string oldPath, string newPath)
|
||||||
|
{
|
||||||
|
await ApiClient.Post(
|
||||||
|
$"{BaseApiUrl}/move?oldPath={oldPath}&newPath={newPath}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Read(string path, Func<Stream, Task> onHandleData)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Write(string path, Stream dataStream)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Delete(string path)
|
||||||
|
{
|
||||||
|
await ApiClient.Delete(
|
||||||
|
$"{BaseApiUrl}/delete?path={path}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UploadChunk(string path, int chunkId, long chunkSize, long totalSize, byte[] data)
|
||||||
|
{
|
||||||
|
using var formContent = new MultipartFormDataContent();
|
||||||
|
formContent.Add(new ByteArrayContent(data), "file", "file");
|
||||||
|
|
||||||
|
await ApiClient.Post(
|
||||||
|
$"{BaseApiUrl}/upload?path={path}&chunkId={chunkId}&chunkSize={chunkSize}&totalSize={totalSize}",
|
||||||
|
formContent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<byte[]> DownloadChunk(string path, int chunkId, long chunkSize)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageTags>frontend</PackageTags>
|
<PackageTags>frontend</PackageTags>
|
||||||
<PackageId>Moonlight.Client</PackageId>
|
<PackageId>Moonlight.Client</PackageId>
|
||||||
<Version>2.1.1</Version>
|
<Version>2.1.2</Version>
|
||||||
<Authors>Moonlight Panel</Authors>
|
<Authors>Moonlight Panel</Authors>
|
||||||
<Description>A build of the client for moonlight development</Description>
|
<Description>A build of the client for moonlight development</Description>
|
||||||
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
||||||
@@ -22,9 +22,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Blazor-ApexCharts" Version="6.0.0" />
|
<PackageReference Include="Blazor-ApexCharts" Version="6.0.0" />
|
||||||
<PackageReference Include="MoonCore" Version="1.9.1" />
|
<PackageReference Include="MoonCore" Version="1.9.2" />
|
||||||
<PackageReference Include="MoonCore.Blazor" Version="1.3.1" />
|
<PackageReference Include="MoonCore.Blazor" Version="1.3.1" />
|
||||||
<PackageReference Include="MoonCore.Blazor.FlyonUi" Version="1.0.4" />
|
<PackageReference Include="MoonCore.Blazor.FlyonUi" Version="1.0.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
||||||
@@ -58,6 +58,11 @@
|
|||||||
<Folder Include="Styles\" />
|
<Folder Include="Styles\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<_ContentIncludedByDefault Remove="wwwroot\css\style.min.css" />
|
<_ContentIncludedByDefault Remove="Properties\launchSettings.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Styles/mappings/*" Pack="true" PackagePath="Styles/mappings/" />
|
||||||
|
<None Include="Moonlight.Client.targets" Pack="true" PackagePath="build\Moonlight.Client.targets" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
8
Moonlight.Client/Moonlight.Client.targets
Normal file
8
Moonlight.Client/Moonlight.Client.targets
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<Project>
|
||||||
|
<ItemGroup>
|
||||||
|
<StylesFilesToCopy Include="$(MSBuildThisFileDirectory)../Styles/**/*.*"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<Target Name="CopyContent" BeforeTargets="Build">
|
||||||
|
<Copy SourceFiles="@(StylesFilesToCopy)" DestinationFolder="$(ProjectDir)Styles/%(RecursiveDir)" SkipUnchangedFiles="true" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"http": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": false,
|
|
||||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
|
||||||
"applicationUrl": "http://localhost:5165",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -104,7 +104,7 @@ public class RemoteAuthStateManager : AuthenticationStateManager
|
|||||||
[
|
[
|
||||||
new Claim("username", checkData.Username),
|
new Claim("username", checkData.Username),
|
||||||
new Claim("email", checkData.Email),
|
new Claim("email", checkData.Email),
|
||||||
new Claim("permissions", checkData.Permissions)
|
new Claim("permissions", string.Join(";", checkData.Permissions))
|
||||||
],
|
],
|
||||||
"RemoteAuthStateManager"
|
"RemoteAuthStateManager"
|
||||||
)
|
)
|
||||||
|
|||||||
513
Moonlight.Client/Styles/mappings/mooncore.map
Executable file
513
Moonlight.Client/Styles/mappings/mooncore.map
Executable file
@@ -0,0 +1,513 @@
|
|||||||
|
!bg-base-100
|
||||||
|
!border-base-content/40
|
||||||
|
!border-none
|
||||||
|
!flex
|
||||||
|
!font-medium
|
||||||
|
!font-semibold
|
||||||
|
!h-2.5
|
||||||
|
!justify-between
|
||||||
|
!me-1.5
|
||||||
|
!ms-auto
|
||||||
|
!px-2.5
|
||||||
|
!rounded-full
|
||||||
|
!text-sm
|
||||||
|
!w-2.5
|
||||||
|
*:[grid-area:1/1]
|
||||||
|
*:first:rounded-tl-lg
|
||||||
|
*:last:rounded-tr-lg
|
||||||
|
-left-4
|
||||||
|
-ml-4
|
||||||
|
-translate-x-full
|
||||||
|
-translate-y-1/2
|
||||||
|
absolute
|
||||||
|
accordion
|
||||||
|
accordion-bordered
|
||||||
|
accordion-toggle
|
||||||
|
active
|
||||||
|
active-tab:bg-primary
|
||||||
|
active-tab:text-base-content
|
||||||
|
advance-select-menu
|
||||||
|
advance-select-option
|
||||||
|
advance-select-tag
|
||||||
|
advance-select-toggle
|
||||||
|
alert
|
||||||
|
alert-error
|
||||||
|
alert-outline
|
||||||
|
alert-soft
|
||||||
|
align-bottom
|
||||||
|
align-middle
|
||||||
|
animate-bounce
|
||||||
|
animate-ping
|
||||||
|
aria-[current='page']:text-bg-soft-primary
|
||||||
|
avatar
|
||||||
|
avatar-away-bottom
|
||||||
|
avatar-away-top
|
||||||
|
avatar-busy-bottom
|
||||||
|
avatar-busy-top
|
||||||
|
avatar-offline-bottom
|
||||||
|
avatar-offline-top
|
||||||
|
avatar-online-bottom
|
||||||
|
avatar-online-top
|
||||||
|
avatar-placeholder
|
||||||
|
badge
|
||||||
|
badge-error
|
||||||
|
badge-info
|
||||||
|
badge-outline
|
||||||
|
badge-primary
|
||||||
|
badge-soft
|
||||||
|
badge-success
|
||||||
|
bg-background
|
||||||
|
bg-background/60
|
||||||
|
bg-base-100
|
||||||
|
bg-base-150
|
||||||
|
bg-base-200
|
||||||
|
bg-base-200/50
|
||||||
|
bg-base-300
|
||||||
|
bg-base-300/45
|
||||||
|
bg-base-300/50
|
||||||
|
bg-base-300/60
|
||||||
|
bg-error
|
||||||
|
bg-info
|
||||||
|
bg-primary
|
||||||
|
bg-primary/5
|
||||||
|
bg-success
|
||||||
|
bg-transparent
|
||||||
|
bg-warning
|
||||||
|
block
|
||||||
|
blur
|
||||||
|
border
|
||||||
|
border-0
|
||||||
|
border-2
|
||||||
|
border-b
|
||||||
|
border-base-content
|
||||||
|
border-base-content/20
|
||||||
|
border-base-content/40
|
||||||
|
border-base-content/5
|
||||||
|
border-dashed
|
||||||
|
border-t
|
||||||
|
border-transparent
|
||||||
|
bottom-0
|
||||||
|
bottom-full
|
||||||
|
break-words
|
||||||
|
btn
|
||||||
|
btn-accent
|
||||||
|
btn-active
|
||||||
|
btn-circle
|
||||||
|
btn-disabled
|
||||||
|
btn-error
|
||||||
|
btn-info
|
||||||
|
btn-outline
|
||||||
|
btn-primary
|
||||||
|
btn-secondary
|
||||||
|
btn-sm
|
||||||
|
btn-soft
|
||||||
|
btn-square
|
||||||
|
btn-success
|
||||||
|
btn-text
|
||||||
|
btn-warning
|
||||||
|
card
|
||||||
|
card-alert
|
||||||
|
card-body
|
||||||
|
card-border
|
||||||
|
card-footer
|
||||||
|
card-header
|
||||||
|
card-title
|
||||||
|
carousel
|
||||||
|
carousel-body
|
||||||
|
carousel-next
|
||||||
|
carousel-prev
|
||||||
|
carousel-slide
|
||||||
|
chat
|
||||||
|
chat-avatar
|
||||||
|
chat-bubble
|
||||||
|
chat-footer
|
||||||
|
chat-header
|
||||||
|
chat-receiver
|
||||||
|
chat-sender
|
||||||
|
checkbox
|
||||||
|
checkbox-primary
|
||||||
|
checkbox-xs
|
||||||
|
col-span-1
|
||||||
|
collapse
|
||||||
|
combo-box-selected:block
|
||||||
|
combo-box-selected:dropdown-active
|
||||||
|
complete
|
||||||
|
container
|
||||||
|
contents
|
||||||
|
cursor-default
|
||||||
|
cursor-not-allowed
|
||||||
|
cursor-pointer
|
||||||
|
diff
|
||||||
|
disabled
|
||||||
|
divide-base-150/60
|
||||||
|
divide-y
|
||||||
|
drop-shadow
|
||||||
|
dropdown
|
||||||
|
dropdown-disabled
|
||||||
|
dropdown-item
|
||||||
|
dropdown-menu
|
||||||
|
dropdown-open:opacity-100
|
||||||
|
dropdown-open:rotate-180
|
||||||
|
dropdown-toggle
|
||||||
|
duration-300
|
||||||
|
duration-500
|
||||||
|
ease-in-out
|
||||||
|
ease-linear
|
||||||
|
end-3
|
||||||
|
file-upload-complete:progress-success
|
||||||
|
fill-black
|
||||||
|
filter
|
||||||
|
filter-reset
|
||||||
|
fixed
|
||||||
|
flex
|
||||||
|
flex-1
|
||||||
|
flex-col
|
||||||
|
flex-grow
|
||||||
|
flex-nowrap
|
||||||
|
flex-row
|
||||||
|
flex-shrink-0
|
||||||
|
flex-wrap
|
||||||
|
focus-visible:outline-none
|
||||||
|
focus-within:border-primary
|
||||||
|
focus:border-primary
|
||||||
|
focus:outline-1
|
||||||
|
focus:outline-none
|
||||||
|
focus:outline-primary
|
||||||
|
focus:ring-0
|
||||||
|
font-bold
|
||||||
|
font-inter
|
||||||
|
font-medium
|
||||||
|
font-normal
|
||||||
|
font-semibold
|
||||||
|
gap-0.5
|
||||||
|
gap-1
|
||||||
|
gap-1.5
|
||||||
|
gap-2
|
||||||
|
gap-3
|
||||||
|
gap-4
|
||||||
|
gap-5
|
||||||
|
gap-6
|
||||||
|
gap-x-1
|
||||||
|
gap-x-2
|
||||||
|
gap-x-3
|
||||||
|
gap-y-1
|
||||||
|
gap-y-3
|
||||||
|
grid
|
||||||
|
grid-cols-1
|
||||||
|
grid-flow-col
|
||||||
|
grow
|
||||||
|
grow-0
|
||||||
|
h-12
|
||||||
|
h-2
|
||||||
|
h-32
|
||||||
|
h-64
|
||||||
|
h-8
|
||||||
|
h-auto
|
||||||
|
h-full
|
||||||
|
h-screen
|
||||||
|
helper-text
|
||||||
|
hidden
|
||||||
|
hover:bg-primary/5
|
||||||
|
hover:bg-transparent
|
||||||
|
hover:text-base-content
|
||||||
|
hover:text-base-content/60
|
||||||
|
hover:text-primary
|
||||||
|
image-full
|
||||||
|
inline
|
||||||
|
inline-block
|
||||||
|
inline-flex
|
||||||
|
inline-grid
|
||||||
|
input
|
||||||
|
input-floating
|
||||||
|
input-floating-label
|
||||||
|
input-lg
|
||||||
|
input-md
|
||||||
|
input-sm
|
||||||
|
input-xl
|
||||||
|
inset-0
|
||||||
|
inset-y-0
|
||||||
|
inset-y-2
|
||||||
|
invisible
|
||||||
|
is-invalid
|
||||||
|
is-valid
|
||||||
|
isolate
|
||||||
|
italic
|
||||||
|
items-center
|
||||||
|
items-end
|
||||||
|
items-start
|
||||||
|
join
|
||||||
|
join-item
|
||||||
|
justify-between
|
||||||
|
justify-center
|
||||||
|
justify-end
|
||||||
|
justify-start
|
||||||
|
justify-stretch
|
||||||
|
label-text
|
||||||
|
leading-3
|
||||||
|
leading-3.5
|
||||||
|
leading-6
|
||||||
|
left-0
|
||||||
|
lg:bg-base-100/20
|
||||||
|
lg:flex
|
||||||
|
lg:gap-y-0
|
||||||
|
lg:grid-cols-2
|
||||||
|
lg:hidden
|
||||||
|
lg:justify-end
|
||||||
|
lg:justify-start
|
||||||
|
lg:min-w-0
|
||||||
|
lg:p-10
|
||||||
|
lg:pb-5
|
||||||
|
lg:pl-64
|
||||||
|
lg:pr-3.5
|
||||||
|
lg:pt-5
|
||||||
|
lg:ring-1
|
||||||
|
lg:ring-base-content/10
|
||||||
|
lg:rounded-lg
|
||||||
|
lg:shadow-xs
|
||||||
|
list-disc
|
||||||
|
list-inside
|
||||||
|
loading
|
||||||
|
loading-lg
|
||||||
|
loading-sm
|
||||||
|
loading-spinner
|
||||||
|
loading-xl
|
||||||
|
loading-xs
|
||||||
|
lowercase
|
||||||
|
m-10
|
||||||
|
mask
|
||||||
|
max-lg:flex-col
|
||||||
|
max-lg:hidden
|
||||||
|
max-w-7xl
|
||||||
|
max-w-80
|
||||||
|
max-w-full
|
||||||
|
max-w-lg
|
||||||
|
max-w-sm
|
||||||
|
max-w-xl
|
||||||
|
mb-0.5
|
||||||
|
mb-1
|
||||||
|
mb-2
|
||||||
|
mb-3
|
||||||
|
mb-4
|
||||||
|
mb-5
|
||||||
|
md:table-cell
|
||||||
|
md:text-3xl
|
||||||
|
me-1
|
||||||
|
me-1.5
|
||||||
|
me-2
|
||||||
|
me-5
|
||||||
|
menu
|
||||||
|
menu-active
|
||||||
|
menu-disabled
|
||||||
|
menu-dropdown
|
||||||
|
menu-dropdown-show
|
||||||
|
menu-focus
|
||||||
|
menu-horizontal
|
||||||
|
menu-title
|
||||||
|
min-h-0
|
||||||
|
min-h-svh
|
||||||
|
min-w-0
|
||||||
|
min-w-28
|
||||||
|
min-w-48
|
||||||
|
min-w-60
|
||||||
|
min-w-[100px]
|
||||||
|
ml-3
|
||||||
|
ml-4
|
||||||
|
modal
|
||||||
|
modal-content
|
||||||
|
modal-dialog
|
||||||
|
modal-middle
|
||||||
|
modal-title
|
||||||
|
mr-4
|
||||||
|
ms-1
|
||||||
|
ms-2
|
||||||
|
ms-3
|
||||||
|
mt-1
|
||||||
|
mt-1.5
|
||||||
|
mt-10
|
||||||
|
mt-12
|
||||||
|
mt-2
|
||||||
|
mt-2.5
|
||||||
|
mt-3
|
||||||
|
mt-4
|
||||||
|
mt-5
|
||||||
|
mt-8
|
||||||
|
mx-1
|
||||||
|
mx-auto
|
||||||
|
my-3
|
||||||
|
my-auto
|
||||||
|
opacity-0
|
||||||
|
opacity-100
|
||||||
|
open
|
||||||
|
origin-top-left
|
||||||
|
outline
|
||||||
|
outline-0
|
||||||
|
overflow-hidden
|
||||||
|
overflow-x-auto
|
||||||
|
overflow-y-auto
|
||||||
|
overlay-open:duration-50
|
||||||
|
overlay-open:opacity-100
|
||||||
|
p-0.5
|
||||||
|
p-1
|
||||||
|
p-2
|
||||||
|
p-3
|
||||||
|
p-4
|
||||||
|
p-5
|
||||||
|
p-6
|
||||||
|
p-8
|
||||||
|
pin-input
|
||||||
|
pin-input-underline
|
||||||
|
placeholder-base-content/60
|
||||||
|
pointer-events-auto
|
||||||
|
pointer-events-none
|
||||||
|
progress
|
||||||
|
progress-bar
|
||||||
|
progress-indeterminate
|
||||||
|
progress-primary
|
||||||
|
pt-0.5
|
||||||
|
pt-3
|
||||||
|
px-1.5
|
||||||
|
px-2
|
||||||
|
px-3
|
||||||
|
px-4
|
||||||
|
px-5
|
||||||
|
py-0.5
|
||||||
|
py-1.5
|
||||||
|
py-2
|
||||||
|
py-2.5
|
||||||
|
py-6
|
||||||
|
radio
|
||||||
|
range
|
||||||
|
relative
|
||||||
|
resize
|
||||||
|
ring-0
|
||||||
|
ring-1
|
||||||
|
ring-white/10
|
||||||
|
rounded-box
|
||||||
|
rounded-field
|
||||||
|
rounded-full
|
||||||
|
rounded-lg
|
||||||
|
rounded-md
|
||||||
|
rounded-t-lg
|
||||||
|
row-active
|
||||||
|
row-hover
|
||||||
|
rtl:!mr-0
|
||||||
|
select
|
||||||
|
select-disabled:opacity-40
|
||||||
|
select-disabled:pointer-events-none
|
||||||
|
select-floating
|
||||||
|
select-floating-label
|
||||||
|
selected
|
||||||
|
selected:select-active
|
||||||
|
shadow-base-300/20
|
||||||
|
shadow-lg
|
||||||
|
shadow-xs
|
||||||
|
shrink-0
|
||||||
|
size-10
|
||||||
|
size-4
|
||||||
|
size-5
|
||||||
|
size-8
|
||||||
|
skeleton
|
||||||
|
skeleton-animated
|
||||||
|
sm:auto-cols-max
|
||||||
|
sm:flex
|
||||||
|
sm:items-center
|
||||||
|
sm:items-end
|
||||||
|
sm:justify-between
|
||||||
|
sm:justify-end
|
||||||
|
sm:max-w-2xl
|
||||||
|
sm:max-w-3xl
|
||||||
|
sm:max-w-4xl
|
||||||
|
sm:max-w-5xl
|
||||||
|
sm:max-w-6xl
|
||||||
|
sm:max-w-7xl
|
||||||
|
sm:max-w-lg
|
||||||
|
sm:max-w-md
|
||||||
|
sm:max-w-xl
|
||||||
|
sm:mb-0
|
||||||
|
sm:mt-5
|
||||||
|
sm:mt-6
|
||||||
|
sm:p-6
|
||||||
|
sm:py-2
|
||||||
|
sm:text-sm/5
|
||||||
|
space-x-1
|
||||||
|
space-y-1
|
||||||
|
space-y-4
|
||||||
|
sr-only
|
||||||
|
static
|
||||||
|
status
|
||||||
|
status-error
|
||||||
|
sticky
|
||||||
|
switch
|
||||||
|
tab
|
||||||
|
tab-active
|
||||||
|
table
|
||||||
|
table-pin-cols
|
||||||
|
table-pin-rows
|
||||||
|
tabs
|
||||||
|
tabs-bordered
|
||||||
|
tabs-lg
|
||||||
|
tabs-lifted
|
||||||
|
tabs-md
|
||||||
|
tabs-sm
|
||||||
|
tabs-xl
|
||||||
|
tabs-xs
|
||||||
|
text-2xl
|
||||||
|
text-4xl
|
||||||
|
text-accent
|
||||||
|
text-base
|
||||||
|
text-base-content
|
||||||
|
text-base-content/40
|
||||||
|
text-base-content/50
|
||||||
|
text-base-content/60
|
||||||
|
text-base-content/70
|
||||||
|
text-base-content/80
|
||||||
|
text-base/6
|
||||||
|
text-center
|
||||||
|
text-error
|
||||||
|
text-error-content
|
||||||
|
text-gray-400
|
||||||
|
text-info
|
||||||
|
text-info-content
|
||||||
|
text-left
|
||||||
|
text-lg
|
||||||
|
text-primary
|
||||||
|
text-primary-content
|
||||||
|
text-sm
|
||||||
|
text-sm/5
|
||||||
|
text-success
|
||||||
|
text-success-content
|
||||||
|
text-warning
|
||||||
|
text-warning-content
|
||||||
|
text-xl
|
||||||
|
text-xs
|
||||||
|
text-xs/5
|
||||||
|
textarea
|
||||||
|
textarea-floating
|
||||||
|
textarea-floating-label
|
||||||
|
theme-controller
|
||||||
|
tooltip
|
||||||
|
tooltip-content
|
||||||
|
top-0
|
||||||
|
top-1/2
|
||||||
|
top-full
|
||||||
|
transform
|
||||||
|
transition
|
||||||
|
transition-all
|
||||||
|
transition-opacity
|
||||||
|
translate-x-0
|
||||||
|
truncate
|
||||||
|
underline
|
||||||
|
uppercase
|
||||||
|
validate
|
||||||
|
w-0
|
||||||
|
w-0.5
|
||||||
|
w-12
|
||||||
|
w-4
|
||||||
|
w-56
|
||||||
|
w-64
|
||||||
|
w-fit
|
||||||
|
w-full
|
||||||
|
whitespace-nowrap
|
||||||
|
z-10
|
||||||
|
z-40
|
||||||
|
z-50
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<p class="text-xl font-semibold text-slate-200">
|
<p class="text-xl font-semibold text-base-content">
|
||||||
@Text
|
@Text
|
||||||
</p>
|
</p>
|
||||||
<i class="@Icon text-4xl text-primary"></i>
|
<i class="@Icon text-4xl text-primary"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-base text-slate-300">@Title</p>
|
<p class="text-base-content/80">@Title</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<i class="icon-palette mix-blend-exclusion"></i>
|
<i class="icon-palette mix-blend-exclusion"></i>
|
||||||
<span class="ms-2 mix-blend-exclusion">@(currentHex)</span>
|
<span class="ms-2 mix-blend-exclusion">@(currentHex)</span>
|
||||||
</label>
|
</label>
|
||||||
<button @onclick="Reset" class="btn btn-danger">
|
<button @onclick="Reset" class="btn btn-error">
|
||||||
<i class="icon-rotate-ccw"></i>
|
<i class="icon-rotate-ccw"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="animate-shimmer bg-gradient-to-r from-violet-400 via-sky-400 to-purple-400 bg-clip-text font-semibold text-transparent text-3xl" style="animation-duration: 5s; background-size: 200% 100%">
|
<div class="animate-shimmer bg-gradient-to-r from-violet-400 via-sky-400 to-purple-400 bg-clip-text font-semibold text-transparent text-3xl" style="animation-duration: 5s; background-size: 200% 100%">
|
||||||
Welcome, @(Username)
|
Welcome, @(Username)
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-200 text-2xl">What do you want to do today?</div>
|
<div class="text-base-content/90 text-2xl">What do you want to do today?</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
@using Moonlight.Client.UI.Partials
|
@using Moonlight.Client.UI.Partials
|
||||||
|
@using MoonCore.Blazor.FlyonUi.Files.Drop
|
||||||
|
|
||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<div class="relative isolate flex min-h-svh w-full h-screen max-lg:flex-col bg-gray-900 lg:bg-gray-950/80">
|
<div class="relative isolate flex min-h-svh w-full h-screen max-lg:flex-col bg-background">
|
||||||
<AppSidebar Layout="this"/>
|
<AppSidebar Layout="this"/>
|
||||||
<AppHeader Layout="this" />
|
<AppHeader Layout="this" />
|
||||||
|
|
||||||
<main class="h-full flex flex-1 flex-col lg:pb-5 lg:min-w-0 lg:pt-5 lg:pr-3.5 lg:pl-64">
|
<main class="h-full flex flex-1 flex-col lg:pb-5 lg:min-w-0 lg:pt-5 lg:pr-3.5 lg:pl-64">
|
||||||
<div class="h-full grow p-6 lg:rounded-lg lg:p-10 lg:ring-1 lg:shadow-xs lg:bg-gray-900/80 lg:ring-white/10">
|
<div class="grow p-6 lg:rounded-lg lg:p-10 lg:ring-1 lg:shadow-xs lg:bg-base-100/20 lg:ring-base-content/10">
|
||||||
<div class="h-full mx-auto max-w-7xl">
|
<div class="h-full mx-auto max-w-7xl">
|
||||||
<CascadingValue Value="this" IsFixed="true">
|
<CascadingValue Value="this" IsFixed="true">
|
||||||
@Body
|
@Body
|
||||||
@@ -15,20 +16,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ToastLauncher/>
|
<ToastLauncher/>
|
||||||
<ModalLauncher/>
|
<ModalLauncher/>
|
||||||
|
|
||||||
|
<DropHandler />
|
||||||
|
|
||||||
<div id="blazor-error-ui" class="fixed bottom-0 left-0 w-full z-50">
|
<div id="blazor-error-ui" class="fixed bottom-0 left-0 w-full z-50">
|
||||||
<div class="bg-error text-white p-4 flex flex-row justify-between items-center">
|
<div class="bg-error text-base-content p-4 flex flex-row justify-between items-center">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<i class="icon-bomb text-lg text-white me-2"></i>
|
<i class="icon-bomb text-lg text-base-content me-2"></i>
|
||||||
<span>An unhandled error has occurred.</span>
|
<span>An unhandled error has occurred.</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href="#" class="reload text-white underline mr-4">Reload</a>
|
<a href="#" class="reload text-base-content underline mr-4">Reload</a>
|
||||||
<a href="#" class="dismiss hidden">🗙</a>
|
<a href="#" class="dismiss hidden">🗙</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
<header class="flex items-center px-4 lg:hidden border-b border-white/5">
|
<header class="flex items-center px-4 lg:hidden border-b border-base-content/5">
|
||||||
<div class="py-2.5">
|
<div class="py-2.5">
|
||||||
<span class="relative">
|
<span class="relative">
|
||||||
<button @onclick="Layout.ToggleMobileNavigation" aria-label="Open navigation"
|
<button @onclick="Layout.ToggleMobileNavigation" aria-label="Open navigation"
|
||||||
class="relative flex min-w-0 items-center gap-3 rounded-lg p-2 text-left text-base/6 sm:text-sm/5 text-white"
|
class="relative flex min-w-0 items-center gap-3 rounded-lg p-2 text-left text-base/6 sm:text-sm/5 text-base-content"
|
||||||
type="button">
|
type="button">
|
||||||
<i class="icon-menu text-xl"></i>
|
<i class="icon-menu text-xl"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -18,12 +18,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="relative">
|
<span class="relative">
|
||||||
<div class="relative flex min-w-0 cursor-default items-center gap-3 rounded-lg p-2 text-left text-base/6 font-medium sm:text-sm/5 text-white">
|
<div class="relative flex min-w-0 cursor-default items-center gap-3 rounded-lg p-2 text-left text-base/6 font-medium sm:text-sm/5 text-base-content">
|
||||||
<div data-slot="avatar"
|
<div data-slot="avatar"
|
||||||
class="inline-grid shrink-0 align-middle">
|
class="inline-grid shrink-0 align-middle">
|
||||||
<img
|
<img
|
||||||
class="h-8 rounded-full"
|
class="h-8 rounded-full"
|
||||||
src="/img/pfp_placeholder.png"
|
src="/svg/logo.svg"
|
||||||
alt=""/>
|
alt=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,17 +17,17 @@
|
|||||||
|
|
||||||
<div class="fixed inset-y-0 left-0 w-64 max-lg:hidden">
|
<div class="fixed inset-y-0 left-0 w-64 max-lg:hidden">
|
||||||
<nav class="flex h-full min-h-0 flex-col">
|
<nav class="flex h-full min-h-0 flex-col">
|
||||||
<div class="flex flex-col border-b p-4 border-white/5">
|
<div class="flex flex-col border-b p-4 border-base-content/5">
|
||||||
<span class="relative">
|
<span class="relative">
|
||||||
<div type="button"
|
<div type="button"
|
||||||
class="flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-lg font-medium text-gray-100">
|
class="flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-lg font-medium text-base-content">
|
||||||
<span
|
<span
|
||||||
class="inline-grid shrink-0 align-middle">
|
class="inline-grid shrink-0 align-middle">
|
||||||
<img class="h-8 rounded-full"
|
<img class="h-8 rounded-full"
|
||||||
src="/svg/logo.svg"
|
src="/svg/logo.svg"
|
||||||
alt=""/>
|
alt=""/>
|
||||||
</span>
|
</span>
|
||||||
<span class="truncate">Moonlight</span>
|
<span class="truncate">Moonlight v2.1</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(item.Key))
|
if (!string.IsNullOrEmpty(item.Key))
|
||||||
{
|
{
|
||||||
<h3 class="mt-4 px-2 text-sm/5 font-medium text-gray-400">
|
<h3 class="mt-4 px-2 text-sm/5 font-medium text-base-content/40">
|
||||||
@item.Key
|
@item.Key
|
||||||
</h3>
|
</h3>
|
||||||
}
|
}
|
||||||
@@ -51,10 +51,10 @@
|
|||||||
@if (isMatch)
|
@if (isMatch)
|
||||||
{
|
{
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-white"
|
<span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-primary"
|
||||||
style="opacity: 1;">
|
style="opacity: 1;">
|
||||||
</span>
|
</span>
|
||||||
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal bg-white/5 sm:py-2 sm:text-sm/5 text-white"
|
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal bg-primary/5 sm:py-2 sm:text-sm/5 text-base-content"
|
||||||
href="@sidebarItem.Path">
|
href="@sidebarItem.Path">
|
||||||
<i class="@sidebarItem.Icon text-lg"></i>
|
<i class="@sidebarItem.Icon text-lg"></i>
|
||||||
<span class="truncate">
|
<span class="truncate">
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal sm:py-2 sm:text-sm/5 text-white hover:bg-white/5"
|
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal sm:py-2 sm:text-sm/5 text-base-content hover:bg-primary/5"
|
||||||
href="@sidebarItem.Path">
|
href="@sidebarItem.Path">
|
||||||
<i class="@sidebarItem.Icon text-lg"></i>
|
<i class="@sidebarItem.Icon text-lg"></i>
|
||||||
<span class="truncate">
|
<span class="truncate">
|
||||||
@@ -79,9 +79,9 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col border-t p-4 max-lg:hidden border-white/5 mt-2.5">
|
<div class="flex flex-col border-t p-4 max-lg:hidden border-base-content/5 mt-2.5">
|
||||||
<div
|
<div
|
||||||
class="flex w-full items-center px-2 py-2.5 gap-6 rounded-lg text-left text-base/6 font-medium sm:py-2 sm:text-sm/5 text-white">
|
class="flex w-full items-center px-2 py-2.5 gap-6 rounded-lg text-left text-base/6 font-medium sm:py-2 sm:text-sm/5 text-base-content">
|
||||||
<div class="flex min-w-0 items-center gap-3">
|
<div class="flex min-w-0 items-center gap-3">
|
||||||
<span class="inline-grid shrink-0 align-middle">
|
<span class="inline-grid shrink-0 align-middle">
|
||||||
<img class="h-8 rounded-full"
|
<img class="h-8 rounded-full"
|
||||||
@@ -89,10 +89,10 @@
|
|||||||
alt=""/>
|
alt=""/>
|
||||||
</span>
|
</span>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="block truncate text-sm/5 font-medium text-white">
|
<div class="block truncate text-sm/5 font-medium text-base-content">
|
||||||
@Username
|
@Username
|
||||||
</div>
|
</div>
|
||||||
<div class="block truncate text-xs/5 font-normal text-gray-400">
|
<div class="block truncate text-xs/5 font-normal text-base-content/40">
|
||||||
@Email
|
@Email
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,22 +108,23 @@
|
|||||||
<div
|
<div
|
||||||
class="lg:hidden z-50 transition-opacity ease-linear duration-300 @(Layout.ShowMobileNavigation ? "opacity-100" : "opacity-0 pointer-events-none")"
|
class="lg:hidden z-50 transition-opacity ease-linear duration-300 @(Layout.ShowMobileNavigation ? "opacity-100" : "opacity-0 pointer-events-none")"
|
||||||
role="dialog" tabindex="-1">
|
role="dialog" tabindex="-1">
|
||||||
<div class="fixed inset-0 bg-black/30"></div>
|
<div class="fixed inset-0 bg-background/60"></div>
|
||||||
<div class="fixed inset-y-0 w-full max-w-80 p-2">
|
<div class="fixed inset-y-0 w-full max-w-80 p-2">
|
||||||
<div
|
<div
|
||||||
class="relative flex h-full flex-col rounded-lg shadow-xs ring-1 bg-gray-900 ring-white/10 transition ease-in-out duration-300 transform @(Layout.ShowMobileNavigation ? "translate-x-0" : "-translate-x-full")">
|
class="relative flex h-full flex-col rounded-lg shadow-xs ring-1 bg-base-300 ring-white/10 transition ease-in-out duration-300 transform @(Layout.ShowMobileNavigation ? "translate-x-0" : "-translate-x-full")">
|
||||||
<div class="border-b p-4 border-white/5 flex justify-between px-5 pt-3">
|
<div class="border-b p-4 border-base-content/5 flex justify-between px-5 pt-3">
|
||||||
<div class="flex items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 font-medium sm:py-2 sm:text-sm/5 text-white">
|
<div
|
||||||
|
class="flex items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 font-medium sm:py-2 sm:text-sm/5 text-base-content">
|
||||||
<div data-slot="avatar"
|
<div data-slot="avatar"
|
||||||
class="inline-grid shrink-0 align-middle">
|
class="inline-grid shrink-0 align-middle">
|
||||||
<img
|
<img
|
||||||
class="h-8 rounded-full" src="/svg/logo.svg" alt=""/>
|
class="h-8 rounded-full" src="/placeholder.jpg" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
<div class="truncate">Moonlight</div>
|
<div class="truncate">Moonlight v2.1</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button @onclick="Layout.ToggleMobileNavigation" aria-label="Close navigation" type="button"
|
<button @onclick="Layout.ToggleMobileNavigation" aria-label="Close navigation" type="button"
|
||||||
class="relative flex min-w-0 items-center gap-3 rounded-lg p-2 text-left text-base/6 text-white">
|
class="relative flex min-w-0 items-center gap-3 rounded-lg p-2 text-left text-base/6 text-base-content">
|
||||||
<i class="icon-x text-lg"></i>
|
<i class="icon-x text-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,10 +150,10 @@
|
|||||||
@if (isMatch)
|
@if (isMatch)
|
||||||
{
|
{
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-white"
|
<span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-primary"
|
||||||
style="opacity: 1;">
|
style="opacity: 1;">
|
||||||
</span>
|
</span>
|
||||||
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal bg-white/5 sm:py-2 sm:text-sm/5 text-white"
|
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal bg-primary/5 sm:py-2 sm:text-sm/5 text-base-content"
|
||||||
href="@sidebarItem.Path">
|
href="@sidebarItem.Path">
|
||||||
<i class="@sidebarItem.Icon text-lg"></i>
|
<i class="@sidebarItem.Icon text-lg"></i>
|
||||||
<span class="truncate">
|
<span class="truncate">
|
||||||
@@ -164,7 +165,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal sm:py-2 sm:text-sm/5 text-white hover:bg-white/5"
|
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal sm:py-2 sm:text-sm/5 text-base-content hover:bg-primary/5"
|
||||||
href="@sidebarItem.Path">
|
href="@sidebarItem.Path">
|
||||||
<i class="@sidebarItem.Icon text-lg"></i>
|
<i class="@sidebarItem.Icon text-lg"></i>
|
||||||
<span class="truncate">
|
<span class="truncate">
|
||||||
@@ -180,8 +181,7 @@
|
|||||||
<div class="mt-8 flex-1"></div>
|
<div class="mt-8 flex-1"></div>
|
||||||
<div class="flex flex-col gap-0.5">
|
<div class="flex flex-col gap-0.5">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
|
<a class="flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 sm:py-2 sm:text-sm/5 text-base-content"
|
||||||
<a class="flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 sm:py-2 sm:text-sm/5 text-white"
|
|
||||||
href="#" @onclick:preventDefault @onclick="Logout">
|
href="#" @onclick:preventDefault @onclick="Logout">
|
||||||
<i class="icon-log-out"></i>
|
<i class="icon-log-out"></i>
|
||||||
<span class="truncate">Logout</span>
|
<span class="truncate">Logout</span>
|
||||||
|
|||||||
@@ -4,22 +4,22 @@
|
|||||||
@using MoonCore.Helpers
|
@using MoonCore.Helpers
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.ApiKeys
|
@using Moonlight.Shared.Http.Requests.Admin.ApiKeys
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.ApiKeys
|
@using Moonlight.Shared.Http.Responses.Admin.ApiKeys
|
||||||
@using MoonCore.Blazor.Tailwind.Input2
|
@using MoonCore.Blazor.FlyonUi.Forms
|
||||||
|
@using MoonCore.Blazor.FlyonUi.Helpers
|
||||||
|
|
||||||
@inject HttpApiClient ApiClient
|
@inject HttpApiClient ApiClient
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
|
@inject DownloadService DownloadService
|
||||||
@* @inject DownloadService DownloadService *@
|
|
||||||
|
|
||||||
<PageHeader Title="Create API Key">
|
<PageHeader Title="Create API Key">
|
||||||
<a href="/admin/api" class="btn btn-secondary">
|
<a href="/admin/api" class="btn btn-secondary">
|
||||||
<i class="icon-chevron-left mr-1"></i>
|
<i class="icon-chevron-left"></i>
|
||||||
Back
|
Back
|
||||||
</a>
|
</a>
|
||||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||||
<i class="icon-check mr-1"></i>
|
<i class="icon-check"></i>
|
||||||
Create
|
Create
|
||||||
</WButton>
|
</WButton>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
@@ -28,21 +28,21 @@
|
|||||||
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
||||||
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Description</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Description</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.Description" type="text" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.Description" type="text" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Permissions</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Permissions</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<InputTags @bind-Value="Permissions" />
|
<InputTags Value="Permissions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Expires at</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Expires at</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.ExpiresAt" type="date" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.ExpiresAt" type="date" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
private HandleForm Form;
|
private HandleForm Form;
|
||||||
private CreateApiKeyRequest Request;
|
private CreateApiKeyRequest Request;
|
||||||
|
|
||||||
private string[] Permissions = [];
|
private List<string> Permissions = [];
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
@@ -63,12 +63,12 @@
|
|||||||
|
|
||||||
private async Task OnSubmit()
|
private async Task OnSubmit()
|
||||||
{
|
{
|
||||||
Request.PermissionsJson = JsonSerializer.Serialize(Permissions);
|
Request.Permissions = Permissions.ToArray();
|
||||||
Request.ExpiresAt = Request.ExpiresAt.ToUniversalTime();
|
Request.ExpiresAt = Request.ExpiresAt.ToUniversalTime();
|
||||||
|
|
||||||
var response = await ApiClient.PostJson<CreateApiKeyResponse>("api/admin/apikeys", Request);
|
var response = await ApiClient.PostJson<CreateApiKeyResponse>("api/admin/apikeys", Request);
|
||||||
|
|
||||||
await DownloadService.DownloadString(
|
await DownloadService.Download(
|
||||||
$"moonlight-key-{response.Id}.txt",
|
$"moonlight-key-{response.Id}.txt",
|
||||||
response.Secret
|
response.Secret
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-1 card card-body border-l-4 border-tertiary">
|
<div class="col-span-1 card card-body border-l-4 border-tertiary">
|
||||||
<p class="font-medium tracking-wide text-slate-100">
|
<p class="font-medium tracking-wide text-base-content">
|
||||||
Learn about the api usage
|
Learn about the api usage
|
||||||
</p>
|
</p>
|
||||||
<a href="https://help.moonlightpanel.xyz" target="_blank" class="mt-2 flex items-center justify-between text-primary">
|
<a href="https://help.moonlightpanel.xyz" target="_blank" class="mt-2 flex items-center justify-between text-primary">
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-1 card card-body border-l-4 border-success">
|
<div class="col-span-1 card card-body border-l-4 border-success">
|
||||||
<p class="font-medium tracking-wide text-slate-100">
|
<p class="font-medium tracking-wide text-base-content">
|
||||||
Open API Specification
|
Open API Specification
|
||||||
</p>
|
</p>
|
||||||
<a href="/api/swagger/main" target="_blank" class="mt-2 flex items-center justify-between text-primary">
|
<a href="/api/swagger/main" target="_blank" class="mt-2 flex items-center justify-between text-primary">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<DataTableColumn TItem="ApiKeyResponse" Field="@(x => x.Description)" Name="Description"/>
|
<DataTableColumn TItem="ApiKeyResponse" Field="@(x => x.Description)" Name="Description"/>
|
||||||
<DataTableColumn TItem="ApiKeyResponse" Field="@(x => x.ExpiresAt)" Name="Expires at">
|
<DataTableColumn TItem="ApiKeyResponse" Field="@(x => x.ExpiresAt)" Name="Expires at">
|
||||||
<ColumnTemplate>
|
<ColumnTemplate>
|
||||||
@(Formatter.FormatDate(context.ExpiresAt))
|
@(Formatter.FormatDate(context.ExpiresAt.UtcDateTime))
|
||||||
</ColumnTemplate>
|
</ColumnTemplate>
|
||||||
</DataTableColumn>
|
</DataTableColumn>
|
||||||
<DataTableColumn TItem="ApiKeyResponse">
|
<DataTableColumn TItem="ApiKeyResponse">
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="#" @onclick="() => Delete(context)" @onclick:preventDefault
|
<a href="#" @onclick="() => Delete(context)" @onclick:preventDefault
|
||||||
class="text-danger">
|
class="text-error">
|
||||||
<i class="icon-trash text-base"></i>
|
<i class="icon-trash text-base"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,11 +12,11 @@
|
|||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<PageHeader Title="Update API Key">
|
<PageHeader Title="Update API Key">
|
||||||
<a href="/admin/api" class="btn btn-secondary">
|
<a href="/admin/api" class="btn btn-secondary">
|
||||||
<i class="icon-chevron-left mr-1"></i>
|
<i class="icon-chevron-left"></i>
|
||||||
Back
|
Back
|
||||||
</a>
|
</a>
|
||||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||||
<i class="icon-check mr-1"></i>
|
<i class="icon-check"></i>
|
||||||
Update
|
Update
|
||||||
</WButton>
|
</WButton>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
||||||
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Description</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Description</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.Description" type="text" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.Description" type="text" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@page "/admin/system/advanced"
|
@page "/admin/system/advanced"
|
||||||
|
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@using MoonCore.Blazor.FlyonUi.Helpers
|
||||||
@using MoonCore.Helpers
|
@using MoonCore.Helpers
|
||||||
|
|
||||||
@attribute [Authorize(Policy = "permissions:admin.system.advanced")]
|
@attribute [Authorize(Policy = "permissions:admin.system.advanced")]
|
||||||
@@ -36,6 +37,6 @@
|
|||||||
{
|
{
|
||||||
var stream = await ApiClient.GetStream("api/admin/system/advanced/frontend");
|
var stream = await ApiClient.GetStream("api/admin/system/advanced/frontend");
|
||||||
|
|
||||||
await DownloadService.DownloadStream("frontend.zip", stream);
|
await DownloadService.Download("frontend.zip", stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@page "/admin/system/diagnose"
|
@page "/admin/system/diagnose"
|
||||||
|
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@using MoonCore.Blazor.FlyonUi.Helpers
|
||||||
@using MoonCore.Helpers
|
@using MoonCore.Helpers
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Sys
|
@using Moonlight.Shared.Http.Requests.Admin.Sys
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.Sys
|
@using Moonlight.Shared.Http.Responses.Admin.Sys
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
|
|
||||||
<div class="@(DropdownOpen ? "" : "hidden")">
|
<div class="@(DropdownOpen ? "" : "hidden")">
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<div class="mb-2 py-2 border-b border-gray-700 flex items-center gap-3">
|
<div class="mb-2 py-2 border-b border-base-content/70 flex items-center gap-3">
|
||||||
<input id="selectall_checkbox" @bind="SelectAll" type="checkbox" class="form-checkbox">
|
<input id="selectall_checkbox" @bind="SelectAll" type="checkbox" class="form-checkbox">
|
||||||
<label for="selectall_checkbox">Select all</label>
|
<label for="selectall_checkbox">Select all</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,7 +100,7 @@
|
|||||||
|
|
||||||
if (!SelectAll)
|
if (!SelectAll)
|
||||||
{
|
{
|
||||||
// filter the providers which have been selected if not all providers have been selected
|
// Filter the providers which have been selected if not all providers have been selected
|
||||||
request.Providers = AvailableProviders
|
request.Providers = AvailableProviders
|
||||||
.Where(x => x.Value)
|
.Where(x => x.Value)
|
||||||
.Select(x => x.Key.Type)
|
.Select(x => x.Key.Type)
|
||||||
@@ -108,7 +109,7 @@
|
|||||||
|
|
||||||
var stream = await ApiClient.PostStream("api/admin/system/diagnose", request);
|
var stream = await ApiClient.PostStream("api/admin/system/diagnose", request);
|
||||||
|
|
||||||
await DownloadService.DownloadStream("diagnose.zip", stream);
|
await DownloadService.Download("diagnose.zip", stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,31 +3,28 @@
|
|||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using MoonCore.Blazor.Services
|
@using MoonCore.Blazor.Services
|
||||||
@using MoonCore.Helpers
|
@using MoonCore.Helpers
|
||||||
@using MoonCore.Blazor.Tailwind.Fm
|
|
||||||
@using Moonlight.Client.Implementations
|
@using Moonlight.Client.Implementations
|
||||||
|
@using MoonCore.Blazor.FlyonUi.Files.Manager
|
||||||
|
|
||||||
@attribute [Authorize(Policy = "permissions:admin.system.overview")]
|
@attribute [Authorize(Policy = "permissions:admin.system.overview")]
|
||||||
|
|
||||||
@inject HttpApiClient ApiClient
|
@inject HttpApiClient ApiClient
|
||||||
@inject DownloadService DownloadService
|
|
||||||
@inject LocalStorageService LocalStorageService
|
|
||||||
|
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
<NavTabs Index="2" Names="UiConstants.AdminNavNames" Links="UiConstants.AdminNavLinks"/>
|
<NavTabs Index="2" Names="UiConstants.AdminNavNames" Links="UiConstants.AdminNavLinks"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FileManager FileSystemProvider="FileSystemProvider" MaxUploadSize="4096"/>
|
<FileManager FsAccess="FsAccess" TransferChunkSize="TransferChunkSize" UploadLimit="UploadLimit"/>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private IFileSystemProvider FileSystemProvider;
|
private IFsAccess FsAccess;
|
||||||
|
|
||||||
|
private static readonly long TransferChunkSize = ByteConverter.FromMegaBytes(20).Bytes;
|
||||||
|
private static readonly long UploadLimit = ByteConverter.FromGigaBytes(20).Bytes;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
FileSystemProvider = new SysFileSystemProvider(
|
FsAccess = new SystemFsAccess(ApiClient);
|
||||||
ApiClient,
|
|
||||||
DownloadService,
|
|
||||||
LocalStorageService
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
|
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<WButton OnClick="Restart" CssClasses="btn btn-danger w-full">
|
<WButton OnClick="Restart" CssClasses="btn btn-error w-full">
|
||||||
<i class="icon-repeat text-xl text-white me-2"></i>
|
<i class="icon-repeat text-xl text-base-content me-2"></i>
|
||||||
Restart/Shutdown
|
Restart/Shutdown
|
||||||
</WButton>
|
</WButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@using System.Text.Json
|
@using System.Text.Json
|
||||||
@using MoonCore.Helpers
|
@using MoonCore.Helpers
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Users
|
@using Moonlight.Shared.Http.Requests.Admin.Users
|
||||||
|
@using MoonCore.Blazor.FlyonUi.Forms
|
||||||
|
|
||||||
@inject HttpApiClient ApiClient
|
@inject HttpApiClient ApiClient
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@@ -10,11 +11,11 @@
|
|||||||
|
|
||||||
<PageHeader Title="Create User">
|
<PageHeader Title="Create User">
|
||||||
<a href="/admin/users" class="btn btn-secondary">
|
<a href="/admin/users" class="btn btn-secondary">
|
||||||
<i class="icon-chevron-left mr-1"></i>
|
<i class="icon-chevron-left"></i>
|
||||||
Back
|
Back
|
||||||
</a>
|
</a>
|
||||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||||
<i class="icon-check mr-1"></i>
|
<i class="icon-check"></i>
|
||||||
Create
|
Create
|
||||||
</WButton>
|
</WButton>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
@@ -23,27 +24,27 @@
|
|||||||
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
||||||
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Username</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Username</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.Username" type="text" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.Username" type="text" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Email</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Email</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.Email" type="email" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.Email" type="email" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Permissions</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Permissions</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<InputTags @bind-Value="Permissions" />
|
<InputTags Value="Permissions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Password</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Password</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.Password" type="password" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.Password" type="password" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
private HandleForm Form;
|
private HandleForm Form;
|
||||||
private CreateUserRequest Request;
|
private CreateUserRequest Request;
|
||||||
|
|
||||||
private string[] Permissions = [];
|
private List<string> Permissions = [];
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
|
|
||||||
private async Task OnSubmit()
|
private async Task OnSubmit()
|
||||||
{
|
{
|
||||||
Request.PermissionsJson = JsonSerializer.Serialize(Permissions);
|
Request.Permissions = Permissions.ToArray();
|
||||||
|
|
||||||
await ApiClient.Post("api/admin/users", Request);
|
await ApiClient.Post("api/admin/users", Request);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="#" @onclick="() => Delete(context)" @onclick:preventDefault
|
<a href="#" @onclick="() => Delete(context)" @onclick:preventDefault
|
||||||
class="text-danger">
|
class="text-error">
|
||||||
<i class="icon-trash text-base"></i>
|
<i class="icon-trash text-base"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
@using MoonCore.Helpers
|
@using MoonCore.Helpers
|
||||||
@using Moonlight.Shared.Http.Requests.Admin.Users
|
@using Moonlight.Shared.Http.Requests.Admin.Users
|
||||||
@using Moonlight.Shared.Http.Responses.Admin.Users
|
@using Moonlight.Shared.Http.Responses.Admin.Users
|
||||||
|
@using MoonCore.Blazor.FlyonUi.Forms
|
||||||
|
|
||||||
@inject HttpApiClient ApiClient
|
@inject HttpApiClient ApiClient
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@@ -12,11 +13,11 @@
|
|||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<PageHeader Title="Update User">
|
<PageHeader Title="Update User">
|
||||||
<a href="/admin/users" class="btn btn-secondary">
|
<a href="/admin/users" class="btn btn-secondary">
|
||||||
<i class="icon-chevron-left mr-1"></i>
|
<i class="icon-chevron-left"></i>
|
||||||
Back
|
Back
|
||||||
</a>
|
</a>
|
||||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||||
<i class="icon-check mr-1"></i>
|
<i class="icon-check"></i>
|
||||||
Update
|
Update
|
||||||
</WButton>
|
</WButton>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
@@ -25,27 +26,27 @@
|
|||||||
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
||||||
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Username</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Username</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.Username" type="text" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.Username" type="text" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Email</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Email</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.Email" type="email" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.Email" type="email" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Permissions</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Permissions</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<InputTags @bind-Value="Permissions" />
|
<InputTags Value="Permissions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label class="block text-sm font-medium leading-6 text-white">Password</label>
|
<label class="block text-sm font-medium leading-6 text-base-content">Password</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input @bind="Request.Password" type="password" autocomplete="off" class="form-input w-full">
|
<input @bind="Request.Password" type="password" autocomplete="off" class="input w-full">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,25 +61,25 @@
|
|||||||
private HandleForm Form;
|
private HandleForm Form;
|
||||||
private UpdateUserRequest Request;
|
private UpdateUserRequest Request;
|
||||||
|
|
||||||
private string[] Permissions = [];
|
private List<string> Permissions = [];
|
||||||
|
|
||||||
private async Task Load(LazyLoader _)
|
private async Task Load(LazyLoader _)
|
||||||
{
|
{
|
||||||
var detail = await ApiClient.GetJson<UserResponse>($"api/admin/users/{Id}");
|
var detail = await ApiClient.GetJson<UserResponse>($"api/admin/users/{Id}");
|
||||||
|
|
||||||
Permissions = JsonSerializer.Deserialize<string[]>(detail.PermissionsJson) ?? [];
|
Permissions = detail.Permissions.ToList();
|
||||||
|
|
||||||
Request = new()
|
Request = new()
|
||||||
{
|
{
|
||||||
Email = detail.Email,
|
Email = detail.Email,
|
||||||
PermissionsJson = detail.PermissionsJson,
|
Permissions = detail.Permissions,
|
||||||
Username = detail.Username
|
Username = detail.Username
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnSubmit()
|
private async Task OnSubmit()
|
||||||
{
|
{
|
||||||
Request.PermissionsJson = JsonSerializer.Serialize(Permissions);
|
Request.Permissions = Permissions.ToArray();
|
||||||
|
|
||||||
await ApiClient.Patch($"api/admin/users/{Id}", Request);
|
await ApiClient.Patch($"api/admin/users/{Id}", Request);
|
||||||
|
|
||||||
|
|||||||
@@ -1,316 +0,0 @@
|
|||||||
window.moonCore = {
|
|
||||||
window: {
|
|
||||||
getSize: function () {
|
|
||||||
return [window.innerWidth, window.innerHeight];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keyBinds: {
|
|
||||||
storage: {},
|
|
||||||
|
|
||||||
registerHotkey: function (key, modifier, action, dotNetObjRef) {
|
|
||||||
|
|
||||||
const hotkeyListener = async (event) => {
|
|
||||||
if (event.code === key && (!modifier || event[modifier + 'Key'])) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
await dotNetObjRef.invokeMethodAsync("OnHotkeyPressed", action);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
moonCore.keyBinds.storage[`${key}${modifier}`] = hotkeyListener;
|
|
||||||
window.addEventListener('keydown', hotkeyListener);
|
|
||||||
},
|
|
||||||
|
|
||||||
unregisterHotkey: function (key, modifier) {
|
|
||||||
const listenerKey = `${key}${modifier}`;
|
|
||||||
if (moonCore.keyBinds.storage[listenerKey]) {
|
|
||||||
window.removeEventListener('keydown', moonCore.keyBinds.storage[listenerKey]);
|
|
||||||
delete moonCore.keyBinds.storage[listenerKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
downloadService: {
|
|
||||||
download: async function (fileName, contentStreamReference, id, reportRef) {
|
|
||||||
const promise = new Promise(async resolve => {
|
|
||||||
const stream = await contentStreamReference.stream();
|
|
||||||
const reader = stream.getReader();
|
|
||||||
|
|
||||||
let lastReportTime = 0;
|
|
||||||
let receivedLength = 0; // Track downloaded size
|
|
||||||
let chunks = []; // Store downloaded chunks
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const {done, value} = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
|
|
||||||
chunks.push(value);
|
|
||||||
receivedLength += value.length;
|
|
||||||
|
|
||||||
if (reportRef) {
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
if (now - lastReportTime >= 500) { // Only log once per second
|
|
||||||
await reportRef.invokeMethodAsync("ReceiveReport", id, receivedLength, -1, false);
|
|
||||||
lastReportTime = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine chunks into a single Blob
|
|
||||||
const blob = new Blob(chunks);
|
|
||||||
|
|
||||||
this.downloadBlob(fileName, blob);
|
|
||||||
|
|
||||||
if (reportRef)
|
|
||||||
await reportRef.invokeMethodAsync("ReceiveReport", id, receivedLength, -1, true);
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
await promise;
|
|
||||||
},
|
|
||||||
downloadUrl: async function (fileName, url, reportRef, id, headers) {
|
|
||||||
const promise = new Promise(async resolve => {
|
|
||||||
let loadRequest = new XMLHttpRequest();
|
|
||||||
let lastReported = Date.now();
|
|
||||||
|
|
||||||
loadRequest.open("GET", url, true);
|
|
||||||
|
|
||||||
for(let headerKey in headers) {
|
|
||||||
loadRequest.setRequestHeader(headerKey, headers[headerKey]);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRequest.responseType = "blob";
|
|
||||||
|
|
||||||
if (reportRef) {
|
|
||||||
loadRequest.onprogress = async ev => {
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
if (now - lastReported >= 500) {
|
|
||||||
await reportRef.invokeMethodAsync("ReceiveReport", id, ev.loaded, ev.total, false);
|
|
||||||
lastReported = now;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadRequest.onloadend = async ev => {
|
|
||||||
await reportRef.invokeMethodAsync("ReceiveReport", id, ev.loaded, ev.total, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRequest.onload = _ => {
|
|
||||||
this.downloadBlob(fileName, loadRequest.response);
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRequest.send();
|
|
||||||
});
|
|
||||||
|
|
||||||
await promise;
|
|
||||||
},
|
|
||||||
downloadBlob: function (fileName, blob)
|
|
||||||
{
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
// Trigger file download
|
|
||||||
const anchor = document.createElement("a");
|
|
||||||
anchor.href = url;
|
|
||||||
anchor.download = fileName;
|
|
||||||
document.body.appendChild(anchor);
|
|
||||||
anchor.click();
|
|
||||||
|
|
||||||
document.body.removeChild(anchor);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fileManager: {
|
|
||||||
uploadCache: [],
|
|
||||||
addFilesToCache: async function(id) {
|
|
||||||
let files = document.getElementById(id).files;
|
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
moonCore.fileManager.uploadCache.push(files[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.ref.invokeMethodAsync("TriggerUpload", moonCore.fileManager.uploadCache.length);
|
|
||||||
},
|
|
||||||
getNextFromCache: async function() {
|
|
||||||
if(moonCore.fileManager.uploadCache.length === 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
let nextItem = moonCore.fileManager.uploadCache.pop();
|
|
||||||
|
|
||||||
if(!nextItem)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
let file;
|
|
||||||
let path;
|
|
||||||
|
|
||||||
if(nextItem instanceof File)
|
|
||||||
{
|
|
||||||
file = nextItem;
|
|
||||||
path = file.name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
file = await this.openFileEntry(nextItem);
|
|
||||||
path = nextItem.fullPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(file.size === 0)
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
path: null,
|
|
||||||
stream: null,
|
|
||||||
left: moonCore.fileManager.uploadCache.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let stream = await this.createStreamRef(file);
|
|
||||||
|
|
||||||
return {
|
|
||||||
path: path,
|
|
||||||
stream: stream,
|
|
||||||
left: moonCore.fileManager.uploadCache.length
|
|
||||||
};
|
|
||||||
},
|
|
||||||
openFileEntry: async function (fileEntry) {
|
|
||||||
const promise = new Promise(resolve => {
|
|
||||||
fileEntry.file(file => {
|
|
||||||
resolve(file);
|
|
||||||
}, err => console.log(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
return await promise;
|
|
||||||
},
|
|
||||||
createStreamRef: async function (processedFile) {
|
|
||||||
// Prevent uploads of empty files
|
|
||||||
if (processedFile.size <= 0) {
|
|
||||||
console.log("Skipping upload of '" + processedFile.name + "' as its empty");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileReader = new FileReader();
|
|
||||||
|
|
||||||
const readerPromise = new Promise(resolve => {
|
|
||||||
fileReader.addEventListener("loadend", ev => {
|
|
||||||
resolve(fileReader.result)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
fileReader.readAsArrayBuffer(processedFile);
|
|
||||||
|
|
||||||
const arrayBuffer = await readerPromise;
|
|
||||||
|
|
||||||
return DotNet.createJSStreamReference(arrayBuffer);
|
|
||||||
},
|
|
||||||
setup: function (id, callbackRef) {
|
|
||||||
this.ref = callbackRef;
|
|
||||||
|
|
||||||
// Check which features are supported by the browser
|
|
||||||
const supportsFileSystemAccessAPI =
|
|
||||||
'getAsFileSystemHandle' in DataTransferItem.prototype;
|
|
||||||
const supportsWebkitGetAsEntry =
|
|
||||||
'webkitGetAsEntry' in DataTransferItem.prototype;
|
|
||||||
|
|
||||||
// This is the drag and drop zone.
|
|
||||||
const elem = document.getElementById(id);
|
|
||||||
|
|
||||||
// Prevent navigation.
|
|
||||||
elem.addEventListener('dragover', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
elem.addEventListener('drop', async (e) => {
|
|
||||||
// Prevent navigation.
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!supportsFileSystemAccessAPI && !supportsWebkitGetAsEntry) {
|
|
||||||
// Cannot handle directories.
|
|
||||||
console.log("Cannot handle directories");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getAllWebkitFileEntries(e.dataTransfer.items).then(async value => {
|
|
||||||
value.forEach(a => moonCore.fileManager.uploadCache.push(a));
|
|
||||||
await this.ref.invokeMethodAsync("TriggerUpload", this.uploadCache.length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getAllWebkitFileEntries: async function (dataTransferItemList) {
|
|
||||||
function readAllEntries(reader) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const entries = [];
|
|
||||||
function readEntries() {
|
|
||||||
reader.readEntries((batch) => {
|
|
||||||
if (batch.length === 0) {
|
|
||||||
resolve(entries);
|
|
||||||
} else {
|
|
||||||
entries.push(...batch);
|
|
||||||
readEntries();
|
|
||||||
}
|
|
||||||
}, reject);
|
|
||||||
}
|
|
||||||
readEntries();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function traverseEntry(entry) {
|
|
||||||
if (entry.isFile) {
|
|
||||||
return [entry];
|
|
||||||
} else if (entry.isDirectory) {
|
|
||||||
const reader = entry.createReader();
|
|
||||||
const entries = await readAllEntries(reader);
|
|
||||||
const subEntries = await Promise.all(entries.map(traverseEntry));
|
|
||||||
return subEntries.flat();
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const entries = [];
|
|
||||||
|
|
||||||
// Convert DataTransferItemList to entries
|
|
||||||
for (let i = 0; i < dataTransferItemList.length; i++) {
|
|
||||||
const item = dataTransferItemList[i];
|
|
||||||
const entry = item.webkitGetAsEntry();
|
|
||||||
if (entry) {
|
|
||||||
entries.push(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traverse all entries and collect file entries
|
|
||||||
const allFileEntries = await Promise.all(entries.map(traverseEntry));
|
|
||||||
return allFileEntries.flat();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
codeEditor: {
|
|
||||||
instances: new Map(),
|
|
||||||
attach: function (id, options) {
|
|
||||||
const editor = ace.edit(id, options);
|
|
||||||
|
|
||||||
moonCore.codeEditor.instances.set(id, editor);
|
|
||||||
},
|
|
||||||
updateOptions: function (id, options) {
|
|
||||||
const editor = moonCore.codeEditor.instances.get(id);
|
|
||||||
|
|
||||||
editor.setOptions(options);
|
|
||||||
},
|
|
||||||
getValue: function (id) {
|
|
||||||
const editor = moonCore.codeEditor.instances.get(id);
|
|
||||||
|
|
||||||
return editor.getValue();
|
|
||||||
},
|
|
||||||
destroy: function (id){
|
|
||||||
const editor = moonCore.codeEditor.instances.get(id);
|
|
||||||
|
|
||||||
if(!editor)
|
|
||||||
return;
|
|
||||||
|
|
||||||
editor.destroy();
|
|
||||||
editor.container.remove();
|
|
||||||
|
|
||||||
moonCore.codeEditor.instances.delete(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ public class CreateApiKeyRequest
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "You need to specify permissions for the api key")]
|
[Required(ErrorMessage = "You need to specify permissions for the api key")]
|
||||||
public string PermissionsJson { get; set; } = "[]";
|
public string[] Permissions { get; set; } = [];
|
||||||
|
|
||||||
[Required(ErrorMessage = "You need to specify an expire date")]
|
[Required(ErrorMessage = "You need to specify an expire date")]
|
||||||
public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(30);
|
public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(30);
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ public class CreateUserRequest
|
|||||||
[MaxLength(256, ErrorMessage = "Your password should not exceed the length of 256 characters")]
|
[MaxLength(256, ErrorMessage = "Your password should not exceed the length of 256 characters")]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
public string PermissionsJson { get; set; } = "[]";
|
public string[] Permissions { get; set; } = [];
|
||||||
}
|
}
|
||||||
@@ -15,5 +15,5 @@ public class UpdateUserRequest
|
|||||||
public string? Password { get; set; }
|
public string? Password { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "You need to provide permissions")]
|
[Required(ErrorMessage = "You need to provide permissions")]
|
||||||
public string PermissionsJson { get; set; } = "[]";
|
public string[] Permissions { get; set; } = [];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,6 @@ public class ApiKeyResponse
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string PermissionsJson { get; set; } = "[]";
|
public string[] Permissions { get; set; } = [];
|
||||||
public DateTime ExpiresAt { get; set; }
|
public DateTimeOffset ExpiresAt { get; set; }
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,6 @@ public class CreateApiKeyResponse
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Secret { get; set; }
|
public string Secret { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string PermissionsJson { get; set; } = "[]";
|
public string[] Permissions { get; set; } = [];
|
||||||
public DateTime ExpiresAt { get; set; }
|
public DateTimeOffset ExpiresAt { get; set; }
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
public class FileSystemEntryResponse
|
public class FileSystemEntryResponse
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool IsFile { get; set; }
|
public bool IsFolder { get; set; }
|
||||||
public long Size { get; set; }
|
public long Size { get; set; }
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ public class UserResponse
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string PermissionsJson { get; set; }
|
public string[] Permissions { get; set; }
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,5 @@ public class CheckResponse
|
|||||||
{
|
{
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string Permissions { get; set; }
|
public string[] Permissions { get; set; }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user