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>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" 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>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -8,12 +8,9 @@ public class ApiKey
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
[Column(TypeName="jsonb")]
|
||||
public string PermissionsJson { get; set; } = "[]";
|
||||
public string[] Permissions { get; set; } = [];
|
||||
|
||||
[Column(TypeName = "timestamp with time zone")]
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public DateTimeOffset ExpiresAt { get; set; }
|
||||
|
||||
[Column(TypeName = "timestamp with time zone")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
@@ -9,10 +9,6 @@ public class User
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
|
||||
[Column(TypeName="timestamp with time zone")]
|
||||
public DateTime TokenValidTimestamp { get; set; } = DateTime.MinValue;
|
||||
|
||||
[Column(TypeName="jsonb")]
|
||||
public string PermissionsJson { get; set; } = "[]";
|
||||
public DateTimeOffset TokenValidTimestamp { get; set; } = DateTimeOffset.MinValue;
|
||||
public string[] Permissions { 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
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("ProductVersion", "9.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
@@ -283,19 +283,19 @@ namespace Moonlight.ApiServer.Database.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
b.Property<DateTimeOffset>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("PermissionsJson")
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb");
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -318,11 +318,11 @@ namespace Moonlight.ApiServer.Database.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PermissionsJson")
|
||||
b.PrimitiveCollection<string[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb");
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTime>("TokenValidTimestamp")
|
||||
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ApiKeysController : Controller
|
||||
.Select(x => new ApiKeyResponse()
|
||||
{
|
||||
Id = x.Id,
|
||||
PermissionsJson = x.PermissionsJson,
|
||||
Permissions = x.Permissions,
|
||||
Description = x.Description,
|
||||
ExpiresAt = x.ExpiresAt
|
||||
})
|
||||
@@ -75,7 +75,7 @@ public class ApiKeysController : Controller
|
||||
return new ApiKeyResponse()
|
||||
{
|
||||
Id = apiKey.Id,
|
||||
PermissionsJson = apiKey.PermissionsJson,
|
||||
Permissions = apiKey.Permissions,
|
||||
Description = apiKey.Description,
|
||||
ExpiresAt = apiKey.ExpiresAt
|
||||
};
|
||||
@@ -88,7 +88,7 @@ public class ApiKeysController : Controller
|
||||
var apiKey = new ApiKey()
|
||||
{
|
||||
Description = request.Description,
|
||||
PermissionsJson = request.PermissionsJson,
|
||||
Permissions = request.Permissions,
|
||||
ExpiresAt = request.ExpiresAt
|
||||
};
|
||||
|
||||
@@ -97,7 +97,7 @@ public class ApiKeysController : Controller
|
||||
var response = new CreateApiKeyResponse
|
||||
{
|
||||
Id = finalApiKey.Id,
|
||||
PermissionsJson = finalApiKey.PermissionsJson,
|
||||
Permissions = finalApiKey.Permissions,
|
||||
Description = finalApiKey.Description,
|
||||
ExpiresAt = finalApiKey.ExpiresAt,
|
||||
Secret = ApiKeyService.GenerateJwt(finalApiKey)
|
||||
@@ -125,7 +125,7 @@ public class ApiKeysController : Controller
|
||||
{
|
||||
Id = apiKey.Id,
|
||||
Description = apiKey.Description,
|
||||
PermissionsJson = apiKey.PermissionsJson,
|
||||
Permissions = apiKey.Permissions,
|
||||
ExpiresAt = apiKey.ExpiresAt
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,39 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||
public class FilesController : Controller
|
||||
{
|
||||
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")]
|
||||
public Task<FileSystemEntryResponse[]> List([FromQuery] string path)
|
||||
@@ -38,7 +70,7 @@ public class FilesController : Controller
|
||||
Name = fi.Name,
|
||||
Size = fi.Length,
|
||||
CreatedAt = fi.CreationTimeUtc,
|
||||
IsFile = true,
|
||||
IsFolder = false,
|
||||
UpdatedAt = fi.LastWriteTimeUtc
|
||||
});
|
||||
}
|
||||
@@ -55,7 +87,7 @@ public class FilesController : Controller
|
||||
Size = 0,
|
||||
CreatedAt = di.CreationTimeUtc,
|
||||
UpdatedAt = di.LastWriteTimeUtc,
|
||||
IsFile = false
|
||||
IsFolder = true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,23 +97,23 @@ public class FilesController : Controller
|
||||
}
|
||||
|
||||
[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)
|
||||
throw new HttpApiException("You need to provide exactly one file", 400);
|
||||
|
||||
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);
|
||||
|
||||
var chunks = totalSize / ChunkSize;
|
||||
chunks += totalSize % ChunkSize > 0 ? 1 : 0;
|
||||
var chunks = totalSize / chunkSize;
|
||||
chunks += totalSize % chunkSize > 0 ? 1 : 0;
|
||||
|
||||
if (chunkId > chunks)
|
||||
throw new HttpApiException("Invalid chunk id: Out of bounds", 400);
|
||||
|
||||
var positionToSkipTo = ChunkSize * chunkId;
|
||||
var positionToSkipTo = chunkSize * chunkId;
|
||||
|
||||
var safePath = SanitizePath(path);
|
||||
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||
@@ -156,16 +188,6 @@ public class FilesController : Controller
|
||||
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")]
|
||||
public async Task Download([FromQuery] string path)
|
||||
{
|
||||
@@ -431,5 +453,23 @@ public class FilesController : Controller
|
||||
#endregion
|
||||
|
||||
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.Mvc;
|
||||
using MoonCore.Attributes;
|
||||
using Moonlight.ApiServer.Interfaces;
|
||||
using Moonlight.ApiServer.Services;
|
||||
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
||||
|
||||
@@ -12,13 +10,10 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||
public class SystemController : Controller
|
||||
{
|
||||
private readonly ApplicationService ApplicationService;
|
||||
private readonly IEnumerable<IDiagnoseProvider> DiagnoseProviders;
|
||||
|
||||
|
||||
public SystemController(ApplicationService applicationService, IEnumerable<IDiagnoseProvider> diagnoseProviders)
|
||||
public SystemController(ApplicationService applicationService)
|
||||
{
|
||||
ApplicationService = applicationService;
|
||||
DiagnoseProviders = diagnoseProviders;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
||||
@@ -45,7 +45,7 @@ public class UsersController : Controller
|
||||
Id = x.Id,
|
||||
Email = x.Email,
|
||||
Username = x.Username,
|
||||
PermissionsJson = x.PermissionsJson
|
||||
Permissions = x.Permissions
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
@@ -75,7 +75,7 @@ public class UsersController : Controller
|
||||
Id = user.Id,
|
||||
Email = user.Email,
|
||||
Username = user.Username,
|
||||
PermissionsJson = user.PermissionsJson
|
||||
Permissions = user.Permissions
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class UsersController : Controller
|
||||
Email = request.Email,
|
||||
Username = request.Username,
|
||||
Password = hashedPassword,
|
||||
PermissionsJson = request.PermissionsJson
|
||||
Permissions = request.Permissions
|
||||
};
|
||||
|
||||
var finalUser = await UserRepository.Add(user);
|
||||
@@ -111,7 +111,7 @@ public class UsersController : Controller
|
||||
Id = finalUser.Id,
|
||||
Email = finalUser.Email,
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ public class UsersController : Controller
|
||||
Id = user.Id,
|
||||
Email = user.Email,
|
||||
Username = user.Username,
|
||||
PermissionsJson = user.PermissionsJson
|
||||
Permissions = user.Permissions
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -73,9 +73,6 @@ public class AuthController : Controller
|
||||
if (user == null)
|
||||
throw new HttpApiException("Unable to load user data", 500);
|
||||
|
||||
//
|
||||
var permissions = JsonSerializer.Deserialize<string[]>(user.PermissionsJson) ?? [];
|
||||
|
||||
// Generate token
|
||||
var securityTokenDescriptor = new SecurityTokenDescriptor()
|
||||
{
|
||||
@@ -90,7 +87,7 @@ public class AuthController : Controller
|
||||
},
|
||||
{
|
||||
"permissions",
|
||||
string.Join(";", permissions)
|
||||
string.Join(";", user.Permissions)
|
||||
}
|
||||
},
|
||||
SigningCredentials = new SigningCredentials(
|
||||
@@ -122,13 +119,11 @@ public class AuthController : Controller
|
||||
var userId = int.Parse(userIdClaim.Value);
|
||||
var user = await UserRepository.Get().FirstAsync(x => x.Id == userId);
|
||||
|
||||
var permissions = JsonSerializer.Deserialize<string[]>(user.PermissionsJson) ?? [];
|
||||
|
||||
return new()
|
||||
{
|
||||
Email = user.Email,
|
||||
Username = user.Username,
|
||||
Permissions = string.Join(";", permissions)
|
||||
Permissions = user.Permissions
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@using Moonlight.Shared.Misc
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="bg-background text-base-content font-inter">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -18,15 +18,15 @@
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/img/icon-192.png" />
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-950 text-white font-inter h-full">
|
||||
<body>
|
||||
<div id="app">
|
||||
|
||||
<div class="flex h-screen justify-center items-center">
|
||||
<div class="sm:max-w-lg">
|
||||
<div id="blazor-loader-label" class="text-center mb-2 text-lg font-semibold"></div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="progress min-w-sm md:min-w-md" role="progressbar">
|
||||
<div id="blazor-loader-progress" class="progress-bar"></div>
|
||||
<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 progress-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -291,7 +291,7 @@ public partial class OAuth2Controller : Controller
|
||||
var userCount = await UserRepository.Get().CountAsync();
|
||||
|
||||
if (userCount == 0)
|
||||
user.PermissionsJson = "[\"*\"]";
|
||||
user.Permissions = ["*"];
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public class CoreStartup : IPluginStartup
|
||||
{
|
||||
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"
|
||||
],
|
||||
Styles = ["/css/style.min.css"]
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Moonlight.ApiServer</PackageId>
|
||||
<Version>2.1.1</Version>
|
||||
<Version>2.1.2</Version>
|
||||
<Authors>Moonlight Panel</Authors>
|
||||
<Description>A build of the api server for moonlight development</Description>
|
||||
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
||||
@@ -34,9 +34,9 @@
|
||||
<PackageReference Include="Hangfire.EntityFrameworkCore" Version="0.7.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" 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.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.Extensions.Hosting" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
||||
@@ -58,5 +58,6 @@
|
||||
<Compile Remove="storage\**\*" />
|
||||
<Content Remove="storage\**\*" />
|
||||
<None Remove="storage\**\*" />
|
||||
<None Remove="Properties\launchSettings.json" />
|
||||
</ItemGroup>
|
||||
</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)
|
||||
{
|
||||
var permissions = JsonSerializer.Deserialize<string[]>(apiKey.PermissionsJson) ?? [];
|
||||
|
||||
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
var descriptor = new SecurityTokenDescriptor()
|
||||
{
|
||||
Expires = apiKey.ExpiresAt,
|
||||
Expires = apiKey.ExpiresAt.UtcDateTime,
|
||||
IssuedAt = DateTime.Now,
|
||||
NotBefore = DateTime.Now.AddMinutes(-1),
|
||||
Claims = new Dictionary<string, object>()
|
||||
@@ -37,7 +35,7 @@ public class ApiKeyService
|
||||
},
|
||||
{
|
||||
"permissions",
|
||||
string.Join(";", permissions)
|
||||
string.Join(";", apiKey.Permissions)
|
||||
}
|
||||
},
|
||||
SigningCredentials = new SigningCredentials(
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.7"/>
|
||||
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.1"/>
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.8" />
|
||||
<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.DevServer" Version="9.0.5" PrivateAssets="all"/>
|
||||
@@ -50,6 +50,11 @@
|
||||
<PackagePath>styles</PackagePath>
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Styles\mappings\mooncore.map">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>styles</PackagePath>
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
</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": {
|
||||
"@tailwindcss/cli": "^4.1.4",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"name": "styles",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"pretailwind-build": "node resolveNuget.js ../Moonlight.Client.Runtime.csproj",
|
||||
"tailwind-build": "npx tailwindcss -i style.css -o ../wwwroot/css/style.min.css",
|
||||
"pretailwind": "node resolveNuget.js ../Moonlight.Client.Runtime.csproj",
|
||||
"tailwind": "npx tailwindcss -i style.css -o ../wwwroot/css/style.min.css --watch"
|
||||
"tailwind": "npx postcss styles.css -o ../wwwroot/css/style.min.css --watch",
|
||||
"tailwind-build": "npx postcss styles.css -o ../wwwroot/css/style.min.css",
|
||||
"mappings": "EXTRACT_CLASSES=true npx postcss styles.css -o ../wwwroot/css/style.min.css "
|
||||
},
|
||||
"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>
|
||||
<PackageTags>frontend</PackageTags>
|
||||
<PackageId>Moonlight.Client</PackageId>
|
||||
<Version>2.1.1</Version>
|
||||
<Version>2.1.2</Version>
|
||||
<Authors>Moonlight Panel</Authors>
|
||||
<Description>A build of the client for moonlight development</Description>
|
||||
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
||||
@@ -22,9 +22,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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.FlyonUi" Version="1.0.4" />
|
||||
<PackageReference Include="MoonCore.Blazor.FlyonUi" Version="1.0.7" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
||||
@@ -58,6 +58,11 @@
|
||||
<Folder Include="Styles\" />
|
||||
</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>
|
||||
</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("email", checkData.Email),
|
||||
new Claim("permissions", checkData.Permissions)
|
||||
new Claim("permissions", string.Join(";", checkData.Permissions))
|
||||
],
|
||||
"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="flex justify-between">
|
||||
<p class="text-xl font-semibold text-slate-200">
|
||||
<p class="text-xl font-semibold text-base-content">
|
||||
@Text
|
||||
</p>
|
||||
<i class="@Icon text-4xl text-primary"></i>
|
||||
</div>
|
||||
<p class="text-base text-slate-300">@Title</p>
|
||||
<p class="text-base-content/80">@Title</p>
|
||||
</div>
|
||||
|
||||
@code
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<i class="icon-palette mix-blend-exclusion"></i>
|
||||
<span class="ms-2 mix-blend-exclusion">@(currentHex)</span>
|
||||
</label>
|
||||
<button @onclick="Reset" class="btn btn-danger">
|
||||
<button @onclick="Reset" class="btn btn-error">
|
||||
<i class="icon-rotate-ccw"></i>
|
||||
</button>
|
||||
</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%">
|
||||
Welcome, @(Username)
|
||||
</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>
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
@using Moonlight.Client.UI.Partials
|
||||
@using MoonCore.Blazor.FlyonUi.Files.Drop
|
||||
|
||||
@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"/>
|
||||
<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">
|
||||
<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">
|
||||
<CascadingValue Value="this" IsFixed="true">
|
||||
@Body
|
||||
@@ -15,20 +16,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
<ToastLauncher/>
|
||||
<ModalLauncher/>
|
||||
|
||||
<DropHandler />
|
||||
|
||||
<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">
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
@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">
|
||||
<span class="relative">
|
||||
<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">
|
||||
<i class="icon-menu text-xl"></i>
|
||||
</button>
|
||||
@@ -18,12 +18,12 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<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"
|
||||
class="inline-grid shrink-0 align-middle">
|
||||
<img
|
||||
class="h-8 rounded-full"
|
||||
src="/img/pfp_placeholder.png"
|
||||
src="/svg/logo.svg"
|
||||
alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,17 +17,17 @@
|
||||
|
||||
<div class="fixed inset-y-0 left-0 w-64 max-lg:hidden">
|
||||
<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">
|
||||
<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
|
||||
class="inline-grid shrink-0 align-middle">
|
||||
<img class="h-8 rounded-full"
|
||||
src="/svg/logo.svg"
|
||||
alt=""/>
|
||||
</span>
|
||||
<span class="truncate">Moonlight</span>
|
||||
<span class="truncate">Moonlight v2.1</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
@@ -37,7 +37,7 @@
|
||||
{
|
||||
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
|
||||
</h3>
|
||||
}
|
||||
@@ -51,10 +51,10 @@
|
||||
@if (isMatch)
|
||||
{
|
||||
<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;">
|
||||
</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">
|
||||
<i class="@sidebarItem.Icon text-lg"></i>
|
||||
<span class="truncate">
|
||||
@@ -66,7 +66,7 @@
|
||||
else
|
||||
{
|
||||
<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">
|
||||
<i class="@sidebarItem.Icon text-lg"></i>
|
||||
<span class="truncate">
|
||||
@@ -79,9 +79,9 @@
|
||||
}
|
||||
</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
|
||||
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">
|
||||
<span class="inline-grid shrink-0 align-middle">
|
||||
<img class="h-8 rounded-full"
|
||||
@@ -89,10 +89,10 @@
|
||||
alt=""/>
|
||||
</span>
|
||||
<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
|
||||
</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
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,22 +108,23 @@
|
||||
<div
|
||||
class="lg:hidden z-50 transition-opacity ease-linear duration-300 @(Layout.ShowMobileNavigation ? "opacity-100" : "opacity-0 pointer-events-none")"
|
||||
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="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")">
|
||||
<div class="border-b p-4 border-white/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">
|
||||
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-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-base-content">
|
||||
<div data-slot="avatar"
|
||||
class="inline-grid shrink-0 align-middle">
|
||||
<img
|
||||
class="h-8 rounded-full" src="/svg/logo.svg" alt=""/>
|
||||
class="h-8 rounded-full" src="/placeholder.jpg" alt=""/>
|
||||
</div>
|
||||
<div class="truncate">Moonlight</div>
|
||||
<div class="truncate">Moonlight v2.1</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
@@ -149,10 +150,10 @@
|
||||
@if (isMatch)
|
||||
{
|
||||
<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;">
|
||||
</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">
|
||||
<i class="@sidebarItem.Icon text-lg"></i>
|
||||
<span class="truncate">
|
||||
@@ -164,7 +165,7 @@
|
||||
else
|
||||
{
|
||||
<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">
|
||||
<i class="@sidebarItem.Icon text-lg"></i>
|
||||
<span class="truncate">
|
||||
@@ -180,8 +181,7 @@
|
||||
<div class="mt-8 flex-1"></div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<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-white"
|
||||
<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"
|
||||
href="#" @onclick:preventDefault @onclick="Logout">
|
||||
<i class="icon-log-out"></i>
|
||||
<span class="truncate">Logout</span>
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
@using MoonCore.Helpers
|
||||
@using Moonlight.Shared.Http.Requests.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 NavigationManager Navigation
|
||||
@inject ToastService ToastService
|
||||
@inject AlertService AlertService
|
||||
|
||||
@* @inject DownloadService DownloadService *@
|
||||
@inject DownloadService DownloadService
|
||||
|
||||
<PageHeader Title="Create API Key">
|
||||
<a href="/admin/api" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Create
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
@@ -28,21 +28,21 @@
|
||||
<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="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">
|
||||
<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 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">
|
||||
<InputTags @bind-Value="Permissions" />
|
||||
<InputTags Value="Permissions" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
@@ -54,7 +54,7 @@
|
||||
private HandleForm Form;
|
||||
private CreateApiKeyRequest Request;
|
||||
|
||||
private string[] Permissions = [];
|
||||
private List<string> Permissions = [];
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
@@ -63,12 +63,12 @@
|
||||
|
||||
private async Task OnSubmit()
|
||||
{
|
||||
Request.PermissionsJson = JsonSerializer.Serialize(Permissions);
|
||||
Request.Permissions = Permissions.ToArray();
|
||||
Request.ExpiresAt = Request.ExpiresAt.ToUniversalTime();
|
||||
|
||||
var response = await ApiClient.PostJson<CreateApiKeyResponse>("api/admin/apikeys", Request);
|
||||
|
||||
await DownloadService.DownloadString(
|
||||
await DownloadService.Download(
|
||||
$"moonlight-key-{response.Id}.txt",
|
||||
response.Secret
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
|
||||
<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
|
||||
</p>
|
||||
<a href="https://help.moonlightpanel.xyz" target="_blank" class="mt-2 flex items-center justify-between text-primary">
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
|
||||
<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
|
||||
</p>
|
||||
<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.ExpiresAt)" Name="Expires at">
|
||||
<ColumnTemplate>
|
||||
@(Formatter.FormatDate(context.ExpiresAt))
|
||||
@(Formatter.FormatDate(context.ExpiresAt.UtcDateTime))
|
||||
</ColumnTemplate>
|
||||
</DataTableColumn>
|
||||
<DataTableColumn TItem="ApiKeyResponse">
|
||||
@@ -72,7 +72,7 @@
|
||||
</a>
|
||||
|
||||
<a href="#" @onclick="() => Delete(context)" @onclick:preventDefault
|
||||
class="text-danger">
|
||||
class="text-error">
|
||||
<i class="icon-trash text-base"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
<LazyLoader Load="Load">
|
||||
<PageHeader Title="Update API Key">
|
||||
<a href="/admin/api" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Update
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
@@ -25,9 +25,9 @@
|
||||
<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="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">
|
||||
<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>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@page "/admin/system/advanced"
|
||||
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using MoonCore.Blazor.FlyonUi.Helpers
|
||||
@using MoonCore.Helpers
|
||||
|
||||
@attribute [Authorize(Policy = "permissions:admin.system.advanced")]
|
||||
@@ -36,6 +37,6 @@
|
||||
{
|
||||
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"
|
||||
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using MoonCore.Blazor.FlyonUi.Helpers
|
||||
@using MoonCore.Helpers
|
||||
@using Moonlight.Shared.Http.Requests.Admin.Sys
|
||||
@using Moonlight.Shared.Http.Responses.Admin.Sys
|
||||
@@ -47,7 +48,7 @@
|
||||
|
||||
<div class="@(DropdownOpen ? "" : "hidden")">
|
||||
<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">
|
||||
<label for="selectall_checkbox">Select all</label>
|
||||
</div>
|
||||
@@ -99,7 +100,7 @@
|
||||
|
||||
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
|
||||
.Where(x => x.Value)
|
||||
.Select(x => x.Key.Type)
|
||||
@@ -108,7 +109,7 @@
|
||||
|
||||
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 MoonCore.Blazor.Services
|
||||
@using MoonCore.Helpers
|
||||
@using MoonCore.Blazor.Tailwind.Fm
|
||||
@using Moonlight.Client.Implementations
|
||||
@using MoonCore.Blazor.FlyonUi.Files.Manager
|
||||
|
||||
@attribute [Authorize(Policy = "permissions:admin.system.overview")]
|
||||
|
||||
@inject HttpApiClient ApiClient
|
||||
@inject DownloadService DownloadService
|
||||
@inject LocalStorageService LocalStorageService
|
||||
|
||||
<div class="mb-5">
|
||||
<NavTabs Index="2" Names="UiConstants.AdminNavNames" Links="UiConstants.AdminNavLinks"/>
|
||||
</div>
|
||||
|
||||
<FileManager FileSystemProvider="FileSystemProvider" MaxUploadSize="4096"/>
|
||||
<FileManager FsAccess="FsAccess" TransferChunkSize="TransferChunkSize" UploadLimit="UploadLimit"/>
|
||||
|
||||
@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()
|
||||
{
|
||||
FileSystemProvider = new SysFileSystemProvider(
|
||||
ApiClient,
|
||||
DownloadService,
|
||||
LocalStorageService
|
||||
);
|
||||
FsAccess = new SystemFsAccess(ApiClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
|
||||
<div class="card card-body">
|
||||
<div class="flex justify-center">
|
||||
<WButton OnClick="Restart" CssClasses="btn btn-danger w-full">
|
||||
<i class="icon-repeat text-xl text-white me-2"></i>
|
||||
<WButton OnClick="Restart" CssClasses="btn btn-error w-full">
|
||||
<i class="icon-repeat text-xl text-base-content me-2"></i>
|
||||
Restart/Shutdown
|
||||
</WButton>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@using System.Text.Json
|
||||
@using MoonCore.Helpers
|
||||
@using Moonlight.Shared.Http.Requests.Admin.Users
|
||||
@using MoonCore.Blazor.FlyonUi.Forms
|
||||
|
||||
@inject HttpApiClient ApiClient
|
||||
@inject NavigationManager Navigation
|
||||
@@ -10,11 +11,11 @@
|
||||
|
||||
<PageHeader Title="Create User">
|
||||
<a href="/admin/users" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Create
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
@@ -23,27 +24,27 @@
|
||||
<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="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">
|
||||
<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 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">
|
||||
<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 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">
|
||||
<InputTags @bind-Value="Permissions" />
|
||||
<InputTags Value="Permissions" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
@@ -55,7 +56,7 @@
|
||||
private HandleForm Form;
|
||||
private CreateUserRequest Request;
|
||||
|
||||
private string[] Permissions = [];
|
||||
private List<string> Permissions = [];
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
@@ -64,7 +65,7 @@
|
||||
|
||||
private async Task OnSubmit()
|
||||
{
|
||||
Request.PermissionsJson = JsonSerializer.Serialize(Permissions);
|
||||
Request.Permissions = Permissions.ToArray();
|
||||
|
||||
await ApiClient.Post("api/admin/users", Request);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</a>
|
||||
|
||||
<a href="#" @onclick="() => Delete(context)" @onclick:preventDefault
|
||||
class="text-danger">
|
||||
class="text-error">
|
||||
<i class="icon-trash text-base"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@using MoonCore.Helpers
|
||||
@using Moonlight.Shared.Http.Requests.Admin.Users
|
||||
@using Moonlight.Shared.Http.Responses.Admin.Users
|
||||
@using MoonCore.Blazor.FlyonUi.Forms
|
||||
|
||||
@inject HttpApiClient ApiClient
|
||||
@inject NavigationManager Navigation
|
||||
@@ -12,11 +13,11 @@
|
||||
<LazyLoader Load="Load">
|
||||
<PageHeader Title="Update User">
|
||||
<a href="/admin/users" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Update
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
@@ -25,27 +26,27 @@
|
||||
<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="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">
|
||||
<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 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">
|
||||
<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 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">
|
||||
<InputTags @bind-Value="Permissions" />
|
||||
<InputTags Value="Permissions" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
@@ -60,25 +61,25 @@
|
||||
private HandleForm Form;
|
||||
private UpdateUserRequest Request;
|
||||
|
||||
private string[] Permissions = [];
|
||||
private List<string> Permissions = [];
|
||||
|
||||
private async Task Load(LazyLoader _)
|
||||
{
|
||||
var detail = await ApiClient.GetJson<UserResponse>($"api/admin/users/{Id}");
|
||||
|
||||
Permissions = JsonSerializer.Deserialize<string[]>(detail.PermissionsJson) ?? [];
|
||||
Permissions = detail.Permissions.ToList();
|
||||
|
||||
Request = new()
|
||||
{
|
||||
Email = detail.Email,
|
||||
PermissionsJson = detail.PermissionsJson,
|
||||
Permissions = detail.Permissions,
|
||||
Username = detail.Username
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OnSubmit()
|
||||
{
|
||||
Request.PermissionsJson = JsonSerializer.Serialize(Permissions);
|
||||
Request.Permissions = Permissions.ToArray();
|
||||
|
||||
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; }
|
||||
|
||||
[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")]
|
||||
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")]
|
||||
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; }
|
||||
|
||||
[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 string Description { get; set; }
|
||||
public string PermissionsJson { get; set; } = "[]";
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public string[] Permissions { get; set; } = [];
|
||||
public DateTimeOffset ExpiresAt { get; set; }
|
||||
}
|
||||
@@ -5,6 +5,6 @@ public class CreateApiKeyResponse
|
||||
public int Id { get; set; }
|
||||
public string Secret { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string PermissionsJson { get; set; } = "[]";
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public string[] Permissions { get; set; } = [];
|
||||
public DateTimeOffset ExpiresAt { get; set; }
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
public class FileSystemEntryResponse
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool IsFile { get; set; }
|
||||
public bool IsFolder { get; set; }
|
||||
public long Size { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
@@ -5,5 +5,5 @@ public class UserResponse
|
||||
public int Id { get; set; }
|
||||
public string Username { 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 Email { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
public string[] Permissions { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user