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:
@@ -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,8 +32,8 @@
|
||||
</a>
|
||||
|
||||
<a href="#" @onclick="() => Delete(context)" @onclick:preventDefault
|
||||
class="text-danger">
|
||||
<i class="icon-trash text-base"></i>
|
||||
class="text-error">
|
||||
<i class="icon-trash text-base"></i>
|
||||
</a>
|
||||
</div>
|
||||
</ColumnTemplate>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user