Implementing new file manager. Not completed

This commit is contained in:
Marcel Baumgartner 2023-03-29 21:34:14 +02:00
parent b38d9d2924
commit 02f6386f95
14 changed files with 1002 additions and 508 deletions

View file

@ -0,0 +1,8 @@
namespace Moonlight.App.Helpers.Files;
public class ContextAction
{
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public Action<FileData> Action { get; set; }
}

View file

@ -0,0 +1,8 @@
namespace Moonlight.App.Helpers.Files;
public class FileData
{
public string Name { get; set; } = "";
public long Size { get; set; }
public bool IsFile { get; set; }
}

View file

@ -0,0 +1,19 @@
namespace Moonlight.App.Helpers.Files;
public interface IFileAccess
{
public Task<FileData[]> Ls();
public Task Cd(string dir);
public Task Up();
public Task SetDir(string dir);
public Task<string> Read(FileData fileData);
public Task Write(FileData fileData, string content);
public Task Upload(string name, Stream stream, Action<int>? progressUpdated = null);
public Task MkDir(string name);
public Task<string> Pwd();
public Task<string> DownloadUrl(FileData fileData);
public Task<Stream> DownloadStream(FileData fileData);
public Task Delete(FileData fileData);
public Task Move(FileData fileData, string newPath);
public Task<string> GetLaunchUrl();
}

View file

@ -0,0 +1,118 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Wings.Resources;
namespace Moonlight.App.Helpers.Files;
public class WingsFileAccess : IFileAccess
{
private readonly WingsApiHelper WingsApiHelper;
private readonly WingsJwtHelper WingsJwtHelper;
private readonly Server Server;
private string CurrentPath = "/";
public WingsFileAccess(WingsApiHelper wingsApiHelper, WingsJwtHelper wingsJwtHelper,Server server)
{
WingsApiHelper = wingsApiHelper;
WingsJwtHelper = wingsJwtHelper;
Server = server;
if (server.Node == null)
{
throw new ArgumentException("The wings file access server model needs to include the node data");
}
}
public async Task<FileData[]> Ls()
{
var res = await WingsApiHelper.Get<ListDirectoryRequest[]>(
Server.Node,
$"api/servers/{Server.Uuid}/files/list-directory?directory={CurrentPath}"
);
var x = new List<FileData>();
foreach (var response in res)
{
x.Add(new()
{
Name = response.Name,
Size = response.File ? response.Size : 0,
IsFile = response.File,
});
}
return x.ToArray();
}
public Task Cd(string dir)
{
var x = Path.Combine(CurrentPath, dir).Replace("\\", "/") + "/";
x = x.Replace("//", "/");
CurrentPath = x;
return Task.CompletedTask;
}
public Task Up()
{
CurrentPath = Path.GetFullPath(Path.Combine(CurrentPath, "..")).Replace("\\", "/").Replace("C:", "");
return Task.CompletedTask;
}
public Task SetDir(string dir)
{
CurrentPath = dir;
return Task.CompletedTask;
}
public async Task<string> Read(FileData fileData)
{
return await WingsApiHelper.GetRaw(Server.Node,$"api/servers/{Server.Uuid}/files/contents?file={CurrentPath}{fileData.Name}");
}
public async Task Write(FileData fileData, string content)
{
await WingsApiHelper.PostRaw(Server.Node,$"api/servers/{Server.Uuid}/files/write?file={CurrentPath}{fileData.Name}", content);
}
public Task Upload(string name, Stream stream, Action<int>? progressUpdated = null)
{
throw new NotImplementedException();
}
public Task MkDir(string name)
{
throw new NotImplementedException();
}
public Task<string> Pwd()
{
return Task.FromResult(CurrentPath);
}
public Task<string> DownloadUrl(FileData fileData)
{
throw new NotImplementedException();
}
public Task<Stream> DownloadStream(FileData fileData)
{
throw new NotImplementedException();
}
public Task Delete(FileData fileData)
{
throw new NotImplementedException();
}
public Task Move(FileData fileData, string newPath)
{
throw new NotImplementedException();
}
public Task<string> GetLaunchUrl()
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,8 @@
namespace Moonlight.App.Models.Files;
public class FileContextAction
{
public string Id { get; set; }
public string Name { get; set; }
public Action<FileManagerObject> Action { get; set; }
}

View file

@ -1,97 +1,20 @@
@using Moonlight.App.Helpers
@using BlazorContextMenu
@using BlazorDownloadFile
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Helpers
@using Logging.Net
@using Moonlight.App.Models.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject AlertService AlertService
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@inject SmartTranslateService TranslationService
@inject IBlazorDownloadFileService FileService
<div class="card card-flush">
@if (Editing == null)
@if (Editing)
{
<div class="card-header pt-8">
<div class="card-title">
<div class="d-flex align-items-center position-relative my-1">
<span class="svg-icon svg-icon-1 position-absolute ms-6">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.5" x="17.0365" y="15.1223" width="8.15546" height="2" rx="1" transform="rotate(45 17.0365 15.1223)" fill="currentColor"></rect>
<path d="M11 19C6.55556 19 3 15.4444 3 11C3 6.55556 6.55556 3 11 3C15.4444 3 19 6.55556 19 11C19 15.4444 15.4444 19 11 19ZM11 5C7.53333 5 5 7.53333 5 11C5 14.4667 7.53333 17 11 17C14.4667 17 17 14.4667 17 11C17 7.53333 14.4667 5 11 5Z" fill="currentColor"></path>
</svg>
</span>
<input type="text" @bind="Search" @bind:event="oninput" class="form-control form-control-solid w-250px ps-15" placeholder="@(TranslationService.Translate("Search files and folders"))">
</div>
</div>
<div class="card-toolbar">
@if (SelectedFiles.Count == 0)
{
<div class="d-flex justify-content-end">
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
<span class="svg-icon svg-icon-muted svg-icon-2hx">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
</svg>
</span>
<TL>Launch WinSCP</TL>
</button>
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
</svg>
</span>
<TL>New folder</TL>
</button>
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
<label for="fileUpload" class="btn btn-primary me-3 pt-5 @(Uploading ? "disabled" : "")">
@if (Uploading)
{
<span>@(Percent)%</span>
}
else
{
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
</svg>
</span>
<TL>Upload</TL>
}
</label>
</div>
}
else
{
<div class="d-flex justify-content-end align-items-center">
<div class="fw-bold me-5">
<span class="me-2">
@(SelectedFiles.Count)
</span>
<TL>Selected</TL>
</div>
<button type="button" class="btn btn-primary me-3">
<TL>Move deleted</TL>
</button>
<button type="button" class="btn btn-danger">
<TL>Delete selected</TL>
</button>
</div>
}
</div>
</div>
<div class="card-body">
<FileEditor @ref="Editor"
InitialData="@EditorInitialData"
Language="@EditorLanguage"
OnCancel="() => Cancel()"
OnSubmit="(_) => Cancel(true)"
HideControls="false">
</FileEditor>
}
else
{
<div class="card card-body mb-7">
<div class="d-flex flex-stack">
<div class="badge badge-lg badge-light-primary">
<div class="d-flex align-items-center flex-wrap">
@ -124,406 +47,84 @@
</div>
</div>
</div>
<div class="dt-bootstrap4 no-footer">
@if (Loading)
{
<div class="mt-5 alert alert-info">
<span>
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
</span>
</div>
}
else
{
<div class="table-responsive">
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
<thead>
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
<th class="w-10px pe-2">
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
<input class="form-check-input" type="checkbox" @onchange="OnAllFileToggle">
</div>
</th>
<th class="min-w-250px">
<TL>File name</TL>
</th>
<th class="min-w-10px">
<TL>File size</TL>
</th>
<th class="min-w-125px">
<TL>Last modified</TL>
</th>
<th class="w-125px"></th>
</tr>
</thead>
<tbody class="fw-semibold text-gray-600">
@foreach (var obj in Objects.Where(x => x.Name.Contains(Search)))
{
<tr class="odd">
<td>
<div class="form-check form-check-sm form-check-custom form-check-solid">
<input class="form-check-input" type="checkbox" @onchange="(e) => OnFileToggle(e, obj)">
</div>
</td>
<td>
<div class="d-flex align-items-center">
@if (obj.IsFile)
{
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M19 22H5C4.4 22 4 21.6 4 21V3C4 2.4 4.4 2 5 2H14L20 8V21C20 21.6 19.6 22 19 22Z" fill="currentColor"></path>
<path d="M15 8H20L14 2V7C14 7.6 14.4 8 15 8Z" fill="currentColor"></path>
</svg>
</span>
}
else
{
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M9.2 3H3C2.4 3 2 3.4 2 4V19C2 19.6 2.4 20 3 20H21C21.6 20 22 19.6 22 19V7C22 6.4 21.6 6 21 6H12L10.4 3.60001C10.2 3.20001 9.7 3 9.2 3Z" fill="currentColor"></path>
</svg>
</span>
}
</div>
@if (obj.IsFile)
{
<a href="#" @onclick:preventDefault @onclick="() => OpenFile(obj)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
}
else
{
<a href="#" @onclick:preventDefault @onclick="() => CdPath(obj.Name)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
}
</div>
</td>
<td>
@(Formatter.FormatSize(obj.Size))
</td>
<td>
@(obj.UpdatedAt.ToShortDateString()) @(obj.UpdatedAt.ToShortTimeString())
</td>
<td class="text-end">
<div class="d-flex justify-content-end">
<div class="ms-2">
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="obj">
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
<span class="svg-icon svg-icon-5 m-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
</svg>
</span>
</button>
</ContextMenuTrigger>
</div>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<div class="card card-body">
<FileView @ref="View"
Access="Access"
ContextActions="Actions"
OnPathChanged="OnPathChanged"
OnElementClicked="OnElementClicked">
</FileView>
</div>
}
else
{
if (Loading)
{
<div class="mt-5 alert alert-info">
<span>
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
</span>
</div>
}
else
{
<FileEditor OnSubmit="SaveFile" OnCancel="CloseFile" InitialData="@(InitialEditorData)" Language="@(Language)"></FileEditor>
}
}
</div>
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
<Item Id="rename" OnClick="OnContextMenuClick"><TL>Rename</TL></Item>
<Item Id="move" OnClick="OnContextMenuClick"><TL>Move</TL></Item>
<Item Id="archive" OnClick="OnContextMenuClick"><TL>Archive</TL></Item>
<Item Id="unarchive" OnClick="OnContextMenuClick"><TL>Unarchive</TL></Item>
<Item Id="download" OnClick="OnContextMenuClick"><TL>Download</TL></Item>
<Item Id="delete" OnClick="OnContextMenuClick"><TL>Delete</TL></Item>
</ContextMenu>
@code
{
[Parameter]
public IFileAccess FileAccess { get; set; }
public IFileAccess Access { get; set; }
// Data
private bool Editing = false;
private string EditorInitialData = "";
private string EditorLanguage = "";
private FileData EditingFile;
private FileEditor Editor;
private List<FileManagerObject> SelectedFiles { get; set; } = new();
private List<FileManagerObject> Objects { get; set; } = new();
private string CurrentPath = "";
private FileView View;
private string CurrentPath = "/";
// Search
private string SearchValue = "";
private string Search
private ContextAction[] Actions =
{
get { return SearchValue; }
set
new()
{
SearchValue = value;
InvokeAsync(StateHasChanged);
Id = "rename",
Name = "Rename",
Action = (x) => { }
}
}
};
// States
private bool Loading = false;
// States - Editor
private FileManagerObject? Editing = null;
private string InitialEditorData = "";
private string Language = "plaintext";
// States - File Upload
private bool Uploading = false;
private int Percent = 0;
protected override async Task OnAfterRenderAsync(bool firstRender)
private async Task<bool> OnElementClicked(FileData fileData)
{
if (firstRender)
if (fileData.IsFile)
{
await RefreshActive();
}
}
EditorInitialData = await Access.Read(fileData);
EditorLanguage = MonacoTypeHelper.GetEditorType(fileData.Name);
EditingFile = fileData;
private async Task RefreshActive()
{
Loading = true;
await InvokeAsync(StateHasChanged);
await Refresh(false);
Loading = false;
await InvokeAsync(StateHasChanged);
}
private async Task Refresh(bool rerender = true)
{
SelectedFiles.Clear();
Objects.Clear();
CurrentPath = await FileAccess.GetCurrentPath();
var data = await FileAccess.GetDirectoryContent();
Objects = data.ToList();
if (rerender)
Editing = true;
await InvokeAsync(StateHasChanged);
}
private async Task CdPath(string path)
{
await FileAccess.ChangeDirectory(path);
await RefreshActive();
}
private async Task SetPath(string path)
{
await FileAccess.SetDirectory(path);
await RefreshActive();
}
private async Task OnContextMenuClick(ItemClickEventArgs e)
{
var data = e.Data as FileManagerObject;
if (data == null)
return;
switch (e.MenuItem.Id)
{
case "delete":
await FileAccess.Delete(data);
break;
case "download":
if (data.IsFile)
{
try
{
var stream = await FileAccess.GetDownloadStream(data);
await ToastService.Info(TranslationService.Translate("Starting download"));
await FileService.AddBuffer(stream);
await FileService.DownloadBinaryBuffers(data.Name);
}
catch (NotImplementedException)
{
try
{
var url = await FileAccess.GetDownloadUrl(data);
NavigationManager.NavigateTo(url, true);
await ToastService.Info(TranslationService.Translate("Starting download"));
}
catch (Exception exception)
{
await ToastService.Error(TranslationService.Translate("Error starting download"));
Logger.Error("Error downloading file");
Logger.Error(exception.Message);
}
}
catch (Exception exception)
{
await ToastService.Error(TranslationService.Translate("Error starting download"));
Logger.Error("Error downloading file stream");
Logger.Error(exception.Message);
}
}
break;
case "rename":
var newName = await AlertService.Text(TranslationService.Translate("Rename"), TranslationService.Translate("Enter a new name"), data.Name);
var path = await FileAccess.GetCurrentPath();
await FileAccess.Move(data, path + "/" + newName);
break;
}
await Refresh(false);
}
private async Task OnFileToggle(ChangeEventArgs obj, FileManagerObject o)
{
if ((bool)obj.Value)
{
if (SelectedFiles.Contains(o))
return;
SelectedFiles.Add(o);
await InvokeAsync(StateHasChanged);
return true;
}
else
{
if (!SelectedFiles.Contains(o))
return;
SelectedFiles.Remove(o);
await InvokeAsync(StateHasChanged);
return false;
}
}
private async Task OnAllFileToggle(ChangeEventArgs obj)
public async Task SetPath(string path)
{
if ((bool)obj.Value)
await Access.SetDir(path);
CurrentPath = await Access.Pwd();
await InvokeAsync(StateHasChanged);
}
private async void Cancel(bool save = false)
{
if (save)
{
foreach (var o in Objects)
{
if (SelectedFiles.Contains(o))
continue;
SelectedFiles.Add(o);
}
await InvokeAsync(StateHasChanged);
}
else
{
foreach (var o in Objects)
{
if (!SelectedFiles.Contains(o))
continue;
SelectedFiles.Remove(o);
}
await InvokeAsync(StateHasChanged);
}
}
private async Task CreateFolder()
{
var name = await AlertService.Text(TranslationService.Translate("Create a new folder"), TranslationService.Translate("Enter a name"), "");
if (string.IsNullOrEmpty(name))
return;
await FileAccess.CreateDirectory(name);
await Refresh();
}
private async void SaveFile(string data)
{
if (Editing == null)
return;
await FileAccess.WriteFile(Editing, data);
Editing = null;
await Refresh();
}
private async Task OpenFile(FileManagerObject o)
{
Editing = o;
Loading = true;
await InvokeAsync(StateHasChanged);
InitialEditorData = await FileAccess.ReadFile(Editing);
Language = MonacoTypeHelper.GetEditorType(Editing.Name);
Loading = false;
await InvokeAsync(StateHasChanged);
}
private async void CloseFile()
{
Editing = null;
await InvokeAsync(StateHasChanged);
}
private async Task Launch()
{
NavigationManager.NavigateTo(await FileAccess.GetLaunchUrl());
}
private async Task OnFileChanged(InputFileChangeEventArgs arg)
{
Uploading = true;
await InvokeAsync(StateHasChanged);
foreach (var browserFile in arg.GetMultipleFiles())
{
if (browserFile.Size < 1024 * 1024 * 100)
{
Percent = 0;
try
{
await FileAccess.UploadFile(
browserFile.Name,
browserFile.OpenReadStream(1024 * 1024 * 100),
async (i) =>
{
Percent = i;
Task.Run(() => { InvokeAsync(StateHasChanged); });
});
await Refresh();
}
catch (Exception e)
{
await ToastService.Error(TranslationService.Translate("An unknown error occured while uploading a file"));
Logger.Error("Error uploading file");
Logger.Error(e);
}
}
else
{
await ToastService.Error(TranslationService.Translate("The uploaded file should not be bigger than 100MB"));
}
var data = await Editor.GetData();
await Access.Write(EditingFile, data);
}
Uploading = false;
Editing = false;
await InvokeAsync(StateHasChanged);
}
await ToastService.Success(TranslationService.Translate("File upload complete"));
private async void OnPathChanged(string path)
{
CurrentPath = path;
await InvokeAsync(StateHasChanged);
}
}

View file

@ -0,0 +1,529 @@
@using Moonlight.App.Helpers
@using BlazorContextMenu
@using BlazorDownloadFile
@using Logging.Net
@using Moonlight.App.Models.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject AlertService AlertService
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@inject SmartTranslateService TranslationService
@inject IBlazorDownloadFileService FileService
<div class="card card-flush">
@if (Editing == null)
{
<div class="card-header pt-8">
<div class="card-title">
<div class="d-flex align-items-center position-relative my-1">
<span class="svg-icon svg-icon-1 position-absolute ms-6">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.5" x="17.0365" y="15.1223" width="8.15546" height="2" rx="1" transform="rotate(45 17.0365 15.1223)" fill="currentColor"></rect>
<path d="M11 19C6.55556 19 3 15.4444 3 11C3 6.55556 6.55556 3 11 3C15.4444 3 19 6.55556 19 11C19 15.4444 15.4444 19 11 19ZM11 5C7.53333 5 5 7.53333 5 11C5 14.4667 7.53333 17 11 17C14.4667 17 17 14.4667 17 11C17 7.53333 14.4667 5 11 5Z" fill="currentColor"></path>
</svg>
</span>
<input type="text" @bind="Search" @bind:event="oninput" class="form-control form-control-solid w-250px ps-15" placeholder="@(TranslationService.Translate("Search files and folders"))">
</div>
</div>
<div class="card-toolbar">
@if (SelectedFiles.Count == 0)
{
<div class="d-flex justify-content-end">
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
<span class="svg-icon svg-icon-muted svg-icon-2hx">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
</svg>
</span>
<TL>Launch WinSCP</TL>
</button>
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
</svg>
</span>
<TL>New folder</TL>
</button>
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
<label for="fileUpload" class="btn btn-primary me-3 pt-5 @(Uploading ? "disabled" : "")">
@if (Uploading)
{
<span>@(Percent)%</span>
}
else
{
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
</svg>
</span>
<TL>Upload</TL>
}
</label>
</div>
}
else
{
<div class="d-flex justify-content-end align-items-center">
<div class="fw-bold me-5">
<span class="me-2">
@(SelectedFiles.Count)
</span>
<TL>Selected</TL>
</div>
<button type="button" class="btn btn-primary me-3">
<TL>Move deleted</TL>
</button>
<button type="button" class="btn btn-danger">
<TL>Delete selected</TL>
</button>
</div>
}
</div>
</div>
<div class="card-body">
<div class="d-flex flex-stack">
<div class="badge badge-lg badge-light-primary">
<div class="d-flex align-items-center flex-wrap">
@{
var vx = "/";
}
<a @onclick:preventDefault @onclick="() => SetPath(vx)" href="#">/</a>
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
</svg>
</span>
@{
var cp = "/";
var lp = "/";
var pathParts = CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries);
foreach (var path in pathParts)
{
lp = cp;
<a @onclick:preventDefault @onclick="() => SetPath(lp)" href="#">@(path)</a>
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
</svg>
</span>
cp += path + "/";
}
}
</div>
</div>
</div>
<div class="dt-bootstrap4 no-footer">
@if (Loading)
{
<div class="mt-5 alert alert-info">
<span>
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
</span>
</div>
}
else
{
<div class="table-responsive">
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
<thead>
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
<th class="w-10px pe-2">
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
<input class="form-check-input" type="checkbox" @onchange="OnAllFileToggle">
</div>
</th>
<th class="min-w-250px">
<TL>File name</TL>
</th>
<th class="min-w-10px">
<TL>File size</TL>
</th>
<th class="min-w-125px">
<TL>Last modified</TL>
</th>
<th class="w-125px"></th>
</tr>
</thead>
<tbody class="fw-semibold text-gray-600">
@foreach (var obj in Objects.Where(x => x.Name.Contains(Search)))
{
<tr class="odd">
<td>
<div class="form-check form-check-sm form-check-custom form-check-solid">
<input class="form-check-input" type="checkbox" @onchange="(e) => OnFileToggle(e, obj)">
</div>
</td>
<td>
<div class="d-flex align-items-center">
@if (obj.IsFile)
{
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M19 22H5C4.4 22 4 21.6 4 21V3C4 2.4 4.4 2 5 2H14L20 8V21C20 21.6 19.6 22 19 22Z" fill="currentColor"></path>
<path d="M15 8H20L14 2V7C14 7.6 14.4 8 15 8Z" fill="currentColor"></path>
</svg>
</span>
}
else
{
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M9.2 3H3C2.4 3 2 3.4 2 4V19C2 19.6 2.4 20 3 20H21C21.6 20 22 19.6 22 19V7C22 6.4 21.6 6 21 6H12L10.4 3.60001C10.2 3.20001 9.7 3 9.2 3Z" fill="currentColor"></path>
</svg>
</span>
}
@if (obj.IsFile)
{
<a href="#" @onclick:preventDefault @onclick="() => OpenFile(obj)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
}
else
{
<a href="#" @onclick:preventDefault @onclick="() => CdPath(obj.Name)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
}
</div>
</td>
<td>
@(Formatter.FormatSize(obj.Size))
</td>
<td>
@(obj.UpdatedAt.ToShortDateString()) @(obj.UpdatedAt.ToShortTimeString())
</td>
<td class="text-end">
<div class="d-flex justify-content-end">
<div class="ms-2">
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="obj">
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
<span class="svg-icon svg-icon-5 m-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
</svg>
</span>
</button>
</ContextMenuTrigger>
</div>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
</div>
}
else
{
if (Loading)
{
<div class="mt-5 alert alert-info">
<span>
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
</span>
</div>
}
else
{
<FileEditor OnSubmit="SaveFile" OnCancel="CloseFile" InitialData="@(InitialEditorData)" Language="@(Language)"></FileEditor>
}
}
</div>
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
<Item Id="rename" OnClick="OnContextMenuClick"><TL>Rename</TL></Item>
<Item Id="move" OnClick="OnContextMenuClick"><TL>Move</TL></Item>
<Item Id="archive" OnClick="OnContextMenuClick"><TL>Archive</TL></Item>
<Item Id="unarchive" OnClick="OnContextMenuClick"><TL>Unarchive</TL></Item>
<Item Id="download" OnClick="OnContextMenuClick"><TL>Download</TL></Item>
<Item Id="delete" OnClick="OnContextMenuClick"><TL>Delete</TL></Item>
</ContextMenu>
@code
{
[Parameter]
public IFileAccess FileAccess { get; set; }
// Data
private List<FileManagerObject> SelectedFiles { get; set; } = new();
private List<FileManagerObject> Objects { get; set; } = new();
private string CurrentPath = "";
// Search
private string SearchValue = "";
private string Search
{
get { return SearchValue; }
set
{
SearchValue = value;
InvokeAsync(StateHasChanged);
}
}
// States
private bool Loading = false;
// States - Editor
private FileManagerObject? Editing = null;
private string InitialEditorData = "";
private string Language = "plaintext";
// States - File Upload
private bool Uploading = false;
private int Percent = 0;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await RefreshActive();
}
}
private async Task RefreshActive()
{
Loading = true;
await InvokeAsync(StateHasChanged);
await Refresh(false);
Loading = false;
await InvokeAsync(StateHasChanged);
}
private async Task Refresh(bool rerender = true)
{
SelectedFiles.Clear();
Objects.Clear();
CurrentPath = await FileAccess.GetCurrentPath();
var data = await FileAccess.GetDirectoryContent();
Objects = data.ToList();
if (rerender)
await InvokeAsync(StateHasChanged);
}
private async Task CdPath(string path)
{
await FileAccess.ChangeDirectory(path);
await RefreshActive();
}
private async Task SetPath(string path)
{
await FileAccess.SetDirectory(path);
await RefreshActive();
}
private async Task OnContextMenuClick(ItemClickEventArgs e)
{
var data = e.Data as FileManagerObject;
if (data == null)
return;
switch (e.MenuItem.Id)
{
case "delete":
await FileAccess.Delete(data);
break;
case "download":
if (data.IsFile)
{
try
{
var stream = await FileAccess.GetDownloadStream(data);
await ToastService.Info(TranslationService.Translate("Starting download"));
await FileService.AddBuffer(stream);
await FileService.DownloadBinaryBuffers(data.Name);
}
catch (NotImplementedException)
{
try
{
var url = await FileAccess.GetDownloadUrl(data);
NavigationManager.NavigateTo(url, true);
await ToastService.Info(TranslationService.Translate("Starting download"));
}
catch (Exception exception)
{
await ToastService.Error(TranslationService.Translate("Error starting download"));
Logger.Error("Error downloading file");
Logger.Error(exception.Message);
}
}
catch (Exception exception)
{
await ToastService.Error(TranslationService.Translate("Error starting download"));
Logger.Error("Error downloading file stream");
Logger.Error(exception.Message);
}
}
break;
case "rename":
var newName = await AlertService.Text(TranslationService.Translate("Rename"), TranslationService.Translate("Enter a new name"), data.Name);
var path = await FileAccess.GetCurrentPath();
await FileAccess.Move(data, path + "/" + newName);
break;
}
await Refresh(false);
}
private async Task OnFileToggle(ChangeEventArgs obj, FileManagerObject o)
{
if ((bool)obj.Value)
{
if (SelectedFiles.Contains(o))
return;
SelectedFiles.Add(o);
await InvokeAsync(StateHasChanged);
}
else
{
if (!SelectedFiles.Contains(o))
return;
SelectedFiles.Remove(o);
await InvokeAsync(StateHasChanged);
}
}
private async Task OnAllFileToggle(ChangeEventArgs obj)
{
if ((bool)obj.Value)
{
foreach (var o in Objects)
{
if (SelectedFiles.Contains(o))
continue;
SelectedFiles.Add(o);
}
await InvokeAsync(StateHasChanged);
}
else
{
foreach (var o in Objects)
{
if (!SelectedFiles.Contains(o))
continue;
SelectedFiles.Remove(o);
}
await InvokeAsync(StateHasChanged);
}
}
private async Task CreateFolder()
{
var name = await AlertService.Text(TranslationService.Translate("Create a new folder"), TranslationService.Translate("Enter a name"), "");
if (string.IsNullOrEmpty(name))
return;
await FileAccess.CreateDirectory(name);
await Refresh();
}
private async void SaveFile(string data)
{
if (Editing == null)
return;
await FileAccess.WriteFile(Editing, data);
Editing = null;
await Refresh();
}
private async Task OpenFile(FileManagerObject o)
{
Editing = o;
Loading = true;
await InvokeAsync(StateHasChanged);
InitialEditorData = await FileAccess.ReadFile(Editing);
Language = MonacoTypeHelper.GetEditorType(Editing.Name);
Loading = false;
await InvokeAsync(StateHasChanged);
}
private async void CloseFile()
{
Editing = null;
await InvokeAsync(StateHasChanged);
}
private async Task Launch()
{
NavigationManager.NavigateTo(await FileAccess.GetLaunchUrl());
}
private async Task OnFileChanged(InputFileChangeEventArgs arg)
{
Uploading = true;
await InvokeAsync(StateHasChanged);
foreach (var browserFile in arg.GetMultipleFiles())
{
if (browserFile.Size < 1024 * 1024 * 100)
{
Percent = 0;
try
{
await FileAccess.UploadFile(
browserFile.Name,
browserFile.OpenReadStream(1024 * 1024 * 100),
async (i) =>
{
Percent = i;
Task.Run(() => { InvokeAsync(StateHasChanged); });
});
await Refresh();
}
catch (Exception e)
{
await ToastService.Error(TranslationService.Translate("An unknown error occured while uploading a file"));
Logger.Error("Error uploading file");
Logger.Error(e);
}
}
else
{
await ToastService.Error(TranslationService.Translate("The uploaded file should not be bigger than 100MB"));
}
}
Uploading = false;
await InvokeAsync(StateHasChanged);
await ToastService.Success(TranslationService.Translate("File upload complete"));
}
}

View file

@ -0,0 +1,223 @@
@using Moonlight.App.Helpers.Files
@using Logging.Net
@using BlazorContextMenu
@using Moonlight.App.Helpers
<div class="table-responsive">
<div class="dataTables_scroll">
<div class="dataTables_scrollHead">
<div class="dataTables_scrollHeadInner">
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
<thead>
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
<th class="w-10px pe-2 sorting_disabled">
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
@if (AllToggled)
{
<input @onclick="() => SetToggleState(false)" class="form-check-input" type="checkbox" checked="">
}
else
{
<input @onclick="() => SetToggleState(true)" class="form-check-input" type="checkbox">
}
</div>
</th>
<th class="min-w-250px sorting_disabled">Name</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="dataTables_scrollBody" style="position: relative; overflow: auto; max-height: 700px; width: 100%;">
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;">
<tbody class="fw-semibold text-gray-600">
<LazyLoader Load="Load">
<tr class="even">
<td class="w-10px">
</td>
<td>
<div class="d-flex align-items-center">
<span class="icon-wrapper">
<i class="bx bx-md bx-folder text-primary"></i>
</span>
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
<TL>Go up</TL>
</a>
</div>
</td>
<td></td>
<td class="text-end">
<div class="d-flex justify-content-end">
<div class="ms-2">
</div>
</div>
</td>
</tr>
@foreach (var file in Data)
{
<tr class="even">
<td class="w-10px">
<div class="form-check form-check-sm form-check-custom form-check-solid">
@{
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
}
@if (toggle)
{
<input @onclick="() => SetToggleState(file, false)" class="form-check-input" type="checkbox" checked="checked">
}
else
{
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
}
</div>
</td>
<td>
<div class="d-flex align-items-center">
<span class="icon-wrapper">
@if (file.IsFile)
{
<i class="bx bx-md bx-file text-primary"></i>
}
else
{
<i class="bx bx-md bx-folder text-primary"></i>
}
</span>
<a href="#" @onclick:preventDefault @onclick="() => OnClicked(file)" class="ms-3 text-gray-800 text-hover-primary">@(file.Name)</a>
</div>
</td>
<td>@(Formatter.FormatSize(file.Size))</td>
<td class="text-end">
<div class="d-flex justify-content-end">
<div class="ms-2 me-7">
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
<span class="svg-icon svg-icon-5 m-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
</svg>
</span>
</button>
</ContextMenuTrigger>
</div>
</div>
</td>
</tr>
}
</LazyLoader>
</tbody>
</table>
</div>
</div>
</div>
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
@foreach (var action in ContextActions)
{
<Item Id="@action.Id" OnClick="OnContextMenuClick">
<TL>@action.Name</TL>
</Item>
}
</ContextMenu>
@code
{
[Parameter]
public IFileAccess Access { get; set; }
[Parameter]
public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
[Parameter]
public Action<string>? OnPathChanged { get; set; }
[Parameter]
public ContextAction[] ContextActions { get; set; } = Array.Empty<ContextAction>();
private FileData[] Data = Array.Empty<FileData>();
private Dictionary<FileData, bool> ToggleStatusCache = new();
private bool AllToggled = false;
public async Task Refresh()
{
Data = await Access.Ls();
ToggleStatusCache.Clear();
foreach (var fileData in Data)
{
ToggleStatusCache.Add(fileData, false);
}
await InvokeAsync(StateHasChanged);
}
private async Task Load(LazyLoader arg)
{
await Refresh();
}
private async Task SetToggleState(FileData fileData, bool status)
{
if (ToggleStatusCache.ContainsKey(fileData))
ToggleStatusCache[fileData] = status;
else
ToggleStatusCache.Add(fileData, status);
await InvokeAsync(StateHasChanged);
}
private async Task SetToggleState(bool status)
{
AllToggled = status;
foreach (var fd in ToggleStatusCache.Keys)
{
ToggleStatusCache[fd] = status;
}
await InvokeAsync(StateHasChanged);
}
private async Task OnClicked(FileData fileData)
{
if (OnElementClicked != null)
{
var canceled = await OnElementClicked.Invoke(fileData);
if (canceled)
return;
}
if (!fileData.IsFile)
{
await Access.Cd(fileData.Name);
await Refresh();
OnPathChanged?.Invoke(await Access.Pwd());
}
}
private async Task GoUp()
{
await Access.Up();
await Refresh();
OnPathChanged?.Invoke(await Access.Pwd());
}
private Task OnContextMenuClick(ItemClickEventArgs eventArgs)
{
var action = ContextActions.FirstOrDefault(x => x.Id == eventArgs.MenuItem.Id);
if (action != null)
{
action.Action.Invoke((FileData)eventArgs.Data);
}
return Task.CompletedTask;
}
}

View file

@ -1,7 +1,7 @@
<div class="alert alert-primary d-flex rounded p-6">
<div class="d-flex flex-stack flex-grow-1 flex-wrap flex-md-nowrap">
<div class="mb-3 mb-md-0 fw-semibold">
<h4 class="text-gray-900 fw-bold"><TL>Plugins</TL></h4>
<h4 class="text-gray-900 fw-bold"><TL>Addons</TL></h4>
<div class="fs-6 text-gray-700 pe-7">
<TL>This feature is currently not available</TL>
</div>

View file

@ -9,7 +9,7 @@
@inject IdentityService IdentityService
<LazyLoader Load="Load">
<FileManager FileAccess="FileAccess"></FileManager>
<FileManager2 FileAccess="FileAccess"></FileManager2>
</LazyLoader>
@code

View file

@ -134,10 +134,10 @@
</a>
</li>
<li class="nav-item w-100 me-0 mb-md-2">
<a href="/server/@(CurrentServer.Uuid)/plugins" class="nav-link w-100 btn btn-flex @(Index == 4 ? "active" : "") btn-active-light-primary">
<a href="/server/@(CurrentServer.Uuid)/addons" class="nav-link w-100 btn btn-flex @(Index == 4 ? "active" : "") btn-active-light-primary">
<i class="bx bx-plug bx-sm me-2"></i>
<span class="d-flex flex-column align-items-start">
<span class="fs-5"><TL>Plugins</TL></span>
<span class="fs-5"><TL>Addons</TL></span>
</span>
</a>
</li>

View file

@ -96,8 +96,8 @@
case "network":
<ServerNetwork></ServerNetwork>
break;
case "plugins":
<ServerPlugins></ServerPlugins>
case "addons":
<ServerAddons></ServerAddons>
break;
case "settings":
<ServerSettings></ServerSettings>
@ -185,7 +185,7 @@
.Include(x => x.Owner)
.First(x => x.Uuid == uuid);
if (CurrentServer.Owner.Id != User!.Id)
if (CurrentServer.Owner.Id != User!.Id && User.Admin)
CurrentServer = null;
}
catch (Exception)

View file

@ -1,55 +1,33 @@
@page "/test"
@using Moonlight.App.Services.Interop
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories.Domains
@inject ToastService ToastService
@inject SharedDomainRepository SharedDomainRepository
@using Moonlight.Shared.Components.FileManagerPartials
@using Moonlight.App.Repositories.Servers
@using Moonlight.App.Helpers.Files
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Helpers
@inject ServerRepository ServerRepository
@inject WingsApiHelper WingsApiHelper
@inject WingsJwtHelper WingsJwtHelper
<LazyLoader Load="Load">
<SmartForm Model="Domain" OnValidSubmit="Callback">
<div class="mb-3">
<label class="form-label">
<TL>Domain name</TL>
</label>
<InputText @bind-Value="Domain.Name" class="form-control"></InputText>
</div>
<div class="mb-3">
<label class="form-label">
<TL>Shared domain</TL>
</label>
<SmartSelect TField="SharedDomain"
@bind-Value="Domain.SharedDomain"
Items="SharedDomains"
DisplayField="@(x => x.Name)">
</SmartSelect>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Save</button>
</div>
</SmartForm>
<FileManager Access="FileAccess">
</FileManager>
</LazyLoader>
@code
{
private App.Database.Entities.Domain Domain = new();
private SharedDomain[] SharedDomains;
private async Task Callback(EditContext obj)
{
Console.WriteLine(Domain.Name);
Console.WriteLine(Domain.SharedDomain.Name);
await ToastService.Success("SUCCESS");
}
private IFileAccess FileAccess;
private Task Load(LazyLoader arg)
{
SharedDomains = SharedDomainRepository
var server = ServerRepository
.Get()
.ToArray();
.Include(x => x.Node)
.First();
FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, server);
return Task.CompletedTask;
}
}

View file

@ -409,3 +409,5 @@ Street and house number requered;Street and house number requered
Max lenght reached;Max lenght reached
Server not found;Server not found
A server with that id cannot be found or you have no access for this server;A server with that id cannot be found or you have no access for this server
Addons;Addons
Go up;Go up