New file manager complete. Server settings for py, js, mc complete. Fixes

This commit is contained in:
Marcel Baumgartner
2023-04-03 00:09:03 +02:00
parent 02f6386f95
commit 6b8c75d014
39 changed files with 1332 additions and 356 deletions

View File

@@ -1,6 +1,15 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Helpers
@using Logging.Net
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using BlazorDownloadFile
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@inject AlertService AlertService
@inject SmartTranslateService SmartTranslateService
@inject IBlazorDownloadFileService FileService
@if (Editing)
{
@@ -14,35 +23,63 @@
}
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">
@{
var vx = "/";
<div class="card mb-7">
<div class="card-header">
<div class="card-title">
<div class="d-flex flex-stack">
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged" />
</div>
</div>
<div class="card-toolbar">
<div class="d-flex justify-content-end align-items-center">
@if (View != null && View.SelectedFiles.Any())
{
<div class="fw-bold me-5">
<span class="me-2">@(View.SelectedFiles.Length) <TL>selected</TL></span>
</div>
<WButton Text="@(SmartTranslateService.Translate("Move"))"
WorkingText="@(SmartTranslateService.Translate("Moving"))"
CssClasses="btn-primary me-3"
OnClick="StartMoveFiles">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Compress"))"
WorkingText="@(SmartTranslateService.Translate("Compressing"))"
CssClasses="btn-primary me-3"
OnClick="CompressMultiple">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
OnClick="DeleteMultiple">
</WButton>
}
<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">
else
{
<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 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>
<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>
cp += path + "/";
}
<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>
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged" />
}
</div>
</div>
@@ -53,35 +90,141 @@ else
<FileView @ref="View"
Access="Access"
ContextActions="Actions"
OnPathChanged="OnPathChanged"
OnElementClicked="OnElementClicked">
OnSelectionChanged="OnSelectionChanged"
OnElementClicked="OnElementClicked"
DisableScrolling="true">
</FileView>
</div>
<FileSelectModal @ref="FileSelectModal"
OnlyFolder="true"
Title="@(SmartTranslateService.Translate("Select folder to move the file(s) to"))"
Access="MoveAccess"
OnSubmit="OnFileMoveSubmit">
</FileSelectModal>
}
@code
{
[Parameter]
public IFileAccess Access { get; set; }
public FileAccess Access { get; set; }
// File Editor
private bool Editing = false;
private string EditorInitialData = "";
private string EditorLanguage = "";
private FileData EditingFile;
private FileEditor Editor;
private FileView View;
private string CurrentPath = "/";
// File View
private FileView? View;
private ContextAction[] Actions =
// File Move
private FileAccess MoveAccess;
private FileSelectModal FileSelectModal;
private FileData? SingleMoveFile = null;
// Config
private ContextAction[] Actions = Array.Empty<ContextAction>();
protected override void OnInitialized()
{
new()
MoveAccess = (FileAccess)Access.Clone();
List<ContextAction> actions = new();
actions.Add(new()
{
Id = "rename",
Name = "Rename",
Action = (x) => { }
}
};
Action = async (x) =>
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Rename"),
SmartTranslateService.Translate("Enter a new name"),
x.Name
);
if (name != x.Name)
{
await Access.Move(x, Access.CurrentPath + name);
}
await View!.Refresh();
}
});
actions.Add(new ()
{
Id = "download",
Name = "Download",
Action = async (x) =>
{
if (x.IsFile)
{
try
{
var stream = await Access.DownloadStream(x);
await ToastService.Info(SmartTranslateService.Translate("Starting download"));
await FileService.AddBuffer(stream);
await FileService.DownloadBinaryBuffers(x.Name);
}
catch (NotImplementedException)
{
var url = await Access.DownloadUrl(x);
NavigationManager.NavigateTo(url, true);
await ToastService.Info(SmartTranslateService.Translate("Starting download"));
}
}
}
});
actions.Add(new()
{
Id = "compress",
Name = "Compress",
Action = async (x) =>
{
await Access.Compress(x);
await View!.Refresh();
}
});
actions.Add(new ()
{
Id = "decompress",
Name = "Decompress",
Action = async (x) =>
{
await Access.Decompress(x);
await View!.Refresh();
}
});
actions.Add(new()
{
Id = "move",
Name = "Move",
Action = async (x) =>
{
SingleMoveFile = x;
await StartMoveFiles();
}
});
actions.Add(new()
{
Id = "delete",
Name = "Delete",
Action = async (x) =>
{
await Access.Delete(x);
await View!.Refresh();
}
});
Actions = actions.ToArray();
}
private async Task<bool> OnElementClicked(FileData fileData)
{
@@ -92,22 +235,13 @@ else
EditingFile = fileData;
Editing = true;
await InvokeAsync(StateHasChanged);
await InvokeAsync(StateHasChanged);
return true;
}
else
{
return false;
}
}
public async Task SetPath(string path)
{
await Access.SetDir(path);
CurrentPath = await Access.Pwd();
await InvokeAsync(StateHasChanged);
return false;
}
private async void Cancel(bool save = false)
@@ -122,9 +256,74 @@ else
await InvokeAsync(StateHasChanged);
}
private async void OnPathChanged(string path)
private async Task Launch()
{
CurrentPath = path;
var url = await Access.GetLaunchUrl();
NavigationManager.NavigateTo(url, true);
}
private async Task CreateFolder()
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Create a new folder"),
SmartTranslateService.Translate("Enter a name"),
""
);
if (string.IsNullOrEmpty(name))
return;
await Access.MkDir(name);
await View!.Refresh();
}
private async Task OnSelectionChanged()
{
await InvokeAsync(StateHasChanged);
}
private async Task StartMoveFiles()
{
await FileSelectModal.Show();
}
private async Task DeleteMultiple()
{
foreach (var data in View!.SelectedFiles)
{
await Access.Delete(data);
}
await View!.Refresh();
}
private async Task CompressMultiple()
{
await Access.Compress(View!.SelectedFiles);
await View!.Refresh();
}
private async Task OnFileMoveSubmit(string path)
{
foreach (var sFile in View!.SelectedFiles)
{
await Access.Move(sFile, path + sFile.Name);
}
if (SingleMoveFile != null)
{
await Access.Move(SingleMoveFile, path + SingleMoveFile.Name);
SingleMoveFile = null;
}
await View.Refresh();
}
// This method can be called by every component to refresh the view
private async Task OnComponentStateChanged()
{
await View!.Refresh();
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -0,0 +1,47 @@
@using Moonlight.App.Helpers.Files
<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 = Access.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>
@code
{
[Parameter]
public FileAccess Access { get; set; }
[Parameter]
public Func<Task>? OnPathChanged { get; set; }
public async Task SetPath(string path)
{
await Access.SetDir(path);
OnPathChanged?.Invoke();
}
}

View File

@@ -0,0 +1,132 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject ModalService ModalService
@inject SmartTranslateService SmartTranslateService
<div class="modal" id="fileView@(Id)" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
@(Title)
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<FileView @ref="FileView"
Access="Access"
HideSelect="true"
Filter="DoFilter"
OnElementClicked="OnElementClicked">
</FileView>
</div>
<div class="modal-footer">
<WButton Text="@(SmartTranslateService.Translate("Submit"))"
WorkingText="@(SmartTranslateService.Translate("Processing"))"
CssClasses="btn-primary"
OnClick="Submit">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Cancel"))"
WorkingText="@(SmartTranslateService.Translate("Processing"))"
CssClasses="btn-danger"
OnClick="Cancel">
</WButton>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public FileAccess Access { get; set; }
[Parameter]
public bool OnlyFolder { get; set; } = false;
[Parameter]
public Func<FileData, bool>? Filter { get; set; }
[Parameter]
public string Title { get; set; } = "Select file or folder";
[Parameter]
public Func<string, Task>? OnSubmit { get; set; }
[Parameter]
public Func<Task>? OnCancel { get; set; }
private int Id = 0;
private string Result = "/";
private FileView FileView;
protected override void OnInitialized()
{
Id = this.GetHashCode();
}
public async Task Show()
{
// Reset
Result = "/";
await Access.SetDir("/");
await FileView.Refresh();
await ModalService.Show("fileView" + Id);
}
public async Task Hide()
{
await Cancel();
}
private async Task Cancel()
{
await ModalService.Hide("fileView" + Id);
await OnCancel?.Invoke()!;
}
private async Task Submit()
{
await ModalService.Hide("fileView" + Id);
await OnSubmit?.Invoke(Result)!;
}
private bool DoFilter(FileData file)
{
if (OnlyFolder)
{
if (file.IsFile)
return false;
else
{
if (Filter != null)
return Filter.Invoke(file);
else
return true;
}
}
else
{
if (Filter != null)
return Filter.Invoke(file);
else
return true;
}
}
private async Task<bool> OnElementClicked(FileData file)
{
Result = Access.CurrentPath + file.Name + (file.IsFile ? "" : "/");
if (!OnlyFolder && file.IsFile)
{
await Submit();
}
return false;
}
}

View File

@@ -0,0 +1,90 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Logging.Net
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
<label for="fileUpload" class="btn btn-primary me-3 @(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>
@code
{
[Parameter]
public FileAccess Access { get; set; }
[Parameter]
public Func<Task> OnUploadComplete { get; set; }
private bool Uploading = false;
private int Percent = 0;
private async Task OnFileChanged(InputFileChangeEventArgs arg)
{
await ToastService.CreateProcessToast("upload", SmartTranslateService.Translate("Uploading files"));
Uploading = true;
await InvokeAsync(StateHasChanged);
int i = 1;
foreach (var browserFile in arg.GetMultipleFiles())
{
if (browserFile.Size < 1024 * 1024 * 100)
{
Percent = 0;
try
{
await Access.Upload(
browserFile.Name,
browserFile.OpenReadStream(1024 * 1024 * 100),
async (i) =>
{
Percent = i;
Task.Run(() => { InvokeAsync(StateHasChanged); });
});
OnUploadComplete?.Invoke();
}
catch (Exception e)
{
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while uploading a file"));
Logger.Error("Error uploading file");
Logger.Error(e);
}
await ToastService.UpdateProcessToast("upload", $"{i}/{arg.GetMultipleFiles().Count} {SmartTranslateService.Translate("complete")}");
}
else
{
await ToastService.Error(SmartTranslateService.Translate("The uploaded file should not be bigger than 100MB"));
}
i++;
}
Uploading = false;
await InvokeAsync(StateHasChanged);
await ToastService.UpdateProcessToast("upload", SmartTranslateService.Translate("Upload complete"));
await ToastService.RemoveProcessToast("upload");
}
}

View File

@@ -11,16 +11,19 @@
<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>
@if (!HideSelect)
{
<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>
@@ -28,7 +31,7 @@
</table>
</div>
</div>
<div class="dataTables_scrollBody" style="position: relative; overflow: auto; max-height: 700px; width: 100%;">
<div class="dataTables_scrollBody" style="@(DisableScrolling ? "" : "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">
@@ -38,7 +41,7 @@
<td>
<div class="d-flex align-items-center">
<span class="icon-wrapper">
<i class="bx bx-md bx-folder text-primary"></i>
<i class="bx bx-md bx-up-arrow-alt text-primary"></i>
</span>
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
<TL>Go up</TL>
@@ -58,20 +61,23 @@
{
<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 (!HideSelect)
{
<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>
@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">
@@ -92,17 +98,20 @@
<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>
@if (ContextActions.Any())
{
<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>
@@ -115,29 +124,46 @@
</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>
@if (ContextActions.Any())
{
<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; }
public FileAccess Access { get; set; }
[Parameter]
public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
[Parameter]
public Action<string>? OnPathChanged { get; set; }
public Func<Task>? OnSelectionChanged { get; set; }
[Parameter]
public ContextAction[] ContextActions { get; set; } = Array.Empty<ContextAction>();
[Parameter]
public bool HideSelect { get; set; } = false;
[Parameter]
public bool DisableScrolling { get; set; } = false;
[Parameter]
public Func<FileData, bool>? Filter { get; set; }
public FileData[] SelectedFiles => ToggleStatusCache
.Where(x => x.Value)
.Select(x => x.Key)
.ToArray();
private FileData[] Data = Array.Empty<FileData>();
private Dictionary<FileData, bool> ToggleStatusCache = new();
@@ -145,8 +171,23 @@
public async Task Refresh()
{
Data = await Access.Ls();
var list = new List<FileData>();
foreach (var fileData in await Access.Ls())
{
if (Filter != null)
{
if(Filter.Invoke(fileData))
list.Add(fileData);
}
else
list.Add(fileData);
}
Data = list.ToArray();
ToggleStatusCache.Clear();
AllToggled = false;
foreach (var fileData in Data)
{
@@ -154,6 +195,7 @@
}
await InvokeAsync(StateHasChanged);
OnSelectionChanged?.Invoke();
}
private async Task Load(LazyLoader arg)
@@ -169,6 +211,7 @@
ToggleStatusCache.Add(fileData, status);
await InvokeAsync(StateHasChanged);
OnSelectionChanged?.Invoke();
}
private async Task SetToggleState(bool status)
@@ -181,6 +224,7 @@
}
await InvokeAsync(StateHasChanged);
OnSelectionChanged?.Invoke();
}
private async Task OnClicked(FileData fileData)
@@ -197,16 +241,25 @@
{
await Access.Cd(fileData.Name);
await Refresh();
OnPathChanged?.Invoke(await Access.Pwd());
}
}
private async Task GoUp()
{
if (OnElementClicked != null)
{
var canceled = await OnElementClicked.Invoke(new()
{
Name = "..",
IsFile = false
});
if (canceled)
return;
}
await Access.Up();
await Refresh();
OnPathChanged?.Invoke(await Access.Pwd());
}
private Task OnContextMenuClick(ItemClickEventArgs eventArgs)

View File

@@ -1,27 +1,27 @@
@using Moonlight.Shared.Components.FileManagerPartials
@using Moonlight.App.Services
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Files
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@inject ServerService ServerService
@inject IdentityService IdentityService
@inject WingsApiHelper WingsApiHelper
@inject WingsJwtHelper WingsJwtHelper
@inject ConfigService ConfigService
<LazyLoader Load="Load">
<FileManager2 FileAccess="FileAccess"></FileManager2>
</LazyLoader>
<FileManager Access="FileAccess"></FileManager>
@code
{
[CascadingParameter]
public Server CurrentServer { get; set; }
[CascadingParameter]
public User User { get; set; }
private IFileAccess FileAccess;
private FileAccess FileAccess;
private async Task Load(LazyLoader arg)
protected override void OnInitialized()
{
var user = await IdentityService.Get(); // User for launch url
FileAccess = await ServerService.CreateFileAccess(CurrentServer, user);
FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, CurrentServer, ConfigService, User);
}
}

View File

@@ -1,51 +1,64 @@
@using PteroConsole.NET
@using Moonlight.App.Database.Entities
@using Moonlight.Shared.Components.ServerControl.Settings
@using Microsoft.AspNetCore.Components.Rendering
<div class="row mb-5">
@if (Tags.Contains("paperversion"))
{
<PaperVersionSetting></PaperVersionSetting>
}
@if (Tags.Contains("pythonversion"))
{
<PythonVersionSetting></PythonVersionSetting>
}
@{
/*
* @if (Tags.Contains("pythonfile"))
{
<PythonFileSetting></PythonFileSetting>
}
@if (Tags.Contains("javascriptfile"))
{
<JavascriptFileSetting></JavascriptFileSetting>
}
*/
}
@if (Tags.Contains("javascriptversion"))
{
<JavascriptVersionSetting></JavascriptVersionSetting>
}
@if (Tags.Contains("join2start"))
{
<Join2StartSetting></Join2StartSetting>
}
</div>
<LazyLoader Load="Load">
<div class="accordion" id="serverSetting">
@foreach (var setting in Settings)
{
<div class="accordion-item">
<h2 class="accordion-header" id="serverSetting-header@(setting.GetHashCode())">
<button class="accordion-button fs-4 fw-semibold" type="button" data-bs-toggle="collapse" data-bs-target="#serverSetting-body@(setting.GetHashCode())" aria-expanded="true" aria-controls="serverSetting-body@(setting.GetHashCode())">
<TL>@(setting.Key)</TL>
</button>
</h2>
<div id="serverSetting-body@(setting.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverSetting-header@(setting.GetHashCode())" data-bs-parent="#serverSetting">
<div class="accordion-body">
@(GetComponent(setting.Value))
</div>
</div>
</div>
}
</div>
</LazyLoader>
@code
{
[CascadingParameter]
public PteroConsole Console { get; set; }
[CascadingParameter]
public Server CurrentServer { get; set; }
[CascadingParameter]
public string[] Tags { get; set; }
private Dictionary<string, Type> Settings = new();
private Task Load(LazyLoader lazyLoader)
{
if(Tags.Contains("paperversion"))
Settings.Add("Paper version", typeof(PaperVersionSetting));
if(Tags.Contains("join2start"))
Settings.Add("Join2Start", typeof(Join2StartSetting));
if(Tags.Contains("javascriptversion"))
Settings.Add("Javascript version", typeof(JavascriptVersionSetting));
if(Tags.Contains("javascriptfile"))
Settings.Add("Javascript file", typeof(JavascriptFileSetting));
if(Tags.Contains("pythonversion"))
Settings.Add("Python version", typeof(PythonVersionSetting));
if(Tags.Contains("pythonfile"))
Settings.Add("Python file", typeof(PythonFileSetting));
return Task.CompletedTask;
}
private RenderFragment GetComponent(Type type) => builder =>
{
builder.OpenComponent(0, type);
builder.CloseComponent();
};
}

View File

@@ -0,0 +1,82 @@
@using Task = System.Threading.Tasks.Task
@using Moonlight.App.Repositories.Servers
@using Moonlight.Shared.Components.FileManagerPartials
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@inject ServerRepository ServerRepository
@inject WingsApiHelper WingsApiHelper
@inject SmartTranslateService SmartTranslateService
<div class="col">
<div class="card card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<label class="mb-2 form-label">
<TL>Javascript file</TL>
</label>
<input type="text" class="mb-2 form-control disabled" disabled="" value="@(PathAndFile)"/>
<button @onclick="Show" class="btn btn-primary"><TL>Change</TL></button>
</LazyLoader>
</div>
</div>
<FileSelectModal @ref="FileSelectModal"
Access="Access"
Filter="@(x => !x.IsFile || x.Name.EndsWith(".js"))"
Title="@(SmartTranslateService.Translate("Select javascript file to execute on start"))"
OnlyFolder="false"
OnCancel="() => { return Task.CompletedTask; }"
OnSubmit="OnSubmit">
</FileSelectModal>
@code
{
[CascadingParameter]
public Server CurrentServer { get; set; }
private string PathAndFile;
private FileAccess Access;
private FileSelectModal FileSelectModal;
private LazyLoader LazyLoader;
protected override void OnInitialized()
{
Access = new WingsFileAccess(WingsApiHelper,
null!,
CurrentServer,
null!,
null!
);
}
private async Task Load(LazyLoader lazyLoader)
{
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_JS_FILE");
PathAndFile = v != null ? v.Value : "";
await InvokeAsync(StateHasChanged);
}
private async Task Show()
{
await FileSelectModal.Show();
}
private async Task OnSubmit(string path)
{
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_JS_FILE");
if (v != null)
{
v.Value = path.TrimStart("/"[0]);
ServerRepository.Update(CurrentServer);
}
await LazyLoader.Reload();
}
}

View File

@@ -8,29 +8,34 @@
@inject ServerRepository ServerRepository
@inject ImageRepository ImageRepository
@inject SmartTranslateService TranslationService
<div class="col">
<div class="card card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<label class="mb-2 form-label"><TL>Javascript Version</TL></label>
<select class="mb-2 form-select" @bind="Image">
@foreach (var image in Images)
<label class="mb-2 form-label"><TL>Javascript version</TL></label>
<select @bind="ImageIndex" class="form-select mb-2">
@foreach (var image in DockerImages)
{
if (image == Image)
if (image.Id == SelectedImage.Id)
{
<option value="@(image)" selected="">@(image)</option>
<option value="@(image.Id)" selected="selected">
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
</option>
}
else
{
<option value="@(image)">@(image)</option>
<option value="@(image.Id)">
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
</option>
}
}
</select>
<WButton
OnClick="Save"
Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary"></WButton>
<WButton
OnClick="Save"
Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary">
</WButton>
</LazyLoader>
</div>
</div>
@@ -40,44 +45,36 @@
[CascadingParameter]
public Server CurrentServer { get; set; }
private string[] Images;
private string Image;
private LazyLoader LazyLoader;
private List<DockerImage> DockerImages;
private DockerImage SelectedImage;
private async Task Load(LazyLoader lazyLoader)
private int ImageIndex
{
//TODO: Check if this is a redundant call
var serverImage = ImageRepository
get => SelectedImage.Id;
set { SelectedImage = DockerImages.First(x => x.Id == value); }
}
private Task Load(LazyLoader lazyLoader)
{
var image = ImageRepository
.Get()
.Include(x => x.DockerImages)
.First(x => x.Id == CurrentServer.Image.Id);
Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name);
var res = new List<string>();
foreach (var image in serverImage.DockerImages)
{
res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name));
}
Images = res.ToArray();
await InvokeAsync(StateHasChanged);
DockerImages = image.DockerImages;
SelectedImage = DockerImages[CurrentServer.DockerImageIndex];
return Task.CompletedTask;
}
private async Task Save()
{
var serverImage = ImageRepository
.Get()
.Include(x => x.DockerImages)
.First(x => x.Id == CurrentServer.Image.Id);
var allImages = serverImage.DockerImages;
var imageToUse = allImages.First(x => x.Name.EndsWith(Image));
CurrentServer.DockerImageIndex = allImages.IndexOf(imageToUse);
ServerRepository.Update(CurrentServer);
CurrentServer.DockerImageIndex = DockerImages.IndexOf(SelectedImage);
ServerRepository.Update(CurrentServer);
await LazyLoader.Reload();
}
}

View File

@@ -1,7 +1,5 @@
@using Moonlight.App.Services
@using Moonlight.Shared.Components.Partials
@using Task = System.Threading.Tasks.Task
@using Logging.Net
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories

View File

@@ -0,0 +1,82 @@
@using Task = System.Threading.Tasks.Task
@using Moonlight.App.Repositories.Servers
@using Moonlight.Shared.Components.FileManagerPartials
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@inject ServerRepository ServerRepository
@inject WingsApiHelper WingsApiHelper
@inject SmartTranslateService SmartTranslateService
<div class="col">
<div class="card card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<label class="mb-2 form-label">
<TL>Python file</TL>
</label>
<input type="text" class="mb-2 form-control disabled" disabled="" value="@(PathAndFile)"/>
<button @onclick="Show" class="btn btn-primary"><TL>Change</TL></button>
</LazyLoader>
</div>
</div>
<FileSelectModal @ref="FileSelectModal"
Access="Access"
Filter="@(x => !x.IsFile || x.Name.EndsWith(".py"))"
Title="@(SmartTranslateService.Translate("Select python file to execute on start"))"
OnlyFolder="false"
OnCancel="() => { return Task.CompletedTask; }"
OnSubmit="OnSubmit">
</FileSelectModal>
@code
{
[CascadingParameter]
public Server CurrentServer { get; set; }
private string PathAndFile;
private FileAccess Access;
private FileSelectModal FileSelectModal;
private LazyLoader LazyLoader;
protected override void OnInitialized()
{
Access = new WingsFileAccess(WingsApiHelper,
null!,
CurrentServer,
null!,
null!
);
}
private async Task Load(LazyLoader lazyLoader)
{
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_PY_FILE");
PathAndFile = v != null ? v.Value : "";
await InvokeAsync(StateHasChanged);
}
private async Task Show()
{
await FileSelectModal.Show();
}
private async Task OnSubmit(string path)
{
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_PY_FILE");
if (v != null)
{
v.Value = path.TrimStart("/"[0]);
ServerRepository.Update(CurrentServer);
}
await LazyLoader.Reload();
}
}

View File

@@ -1,6 +1,4 @@
@using Moonlight.App.Services
@using Task = System.Threading.Tasks.Task
@using Moonlight.Shared.Components.Partials
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers
@@ -15,24 +13,29 @@
<div class="card card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<label class="mb-2 form-label"><TL>Python version</TL></label>
<select class="mb-2 form-select" @bind="Image">
@foreach (var image in Images)
<select @bind="ImageIndex" class="form-select mb-2">
@foreach (var image in DockerImages)
{
if (image == Image)
if (image.Id == SelectedImage.Id)
{
<option value="@(image)" selected="">@(image)</option>
<option value="@(image.Id)" selected="selected">
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
</option>
}
else
{
<option value="@(image)">@(image)</option>
<option value="@(image.Id)">
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
</option>
}
}
</select>
<WButton
OnClick="Save"
Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary"></WButton>
<WButton
OnClick="Save"
Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary">
</WButton>
</LazyLoader>
</div>
</div>
@@ -42,43 +45,36 @@
[CascadingParameter]
public Server CurrentServer { get; set; }
private string[] Images;
private string Image;
private LazyLoader LazyLoader;
private List<DockerImage> DockerImages;
private DockerImage SelectedImage;
private async Task Load(LazyLoader lazyLoader)
private int ImageIndex
{
var serverImage = ImageRepository
get => SelectedImage.Id;
set { SelectedImage = DockerImages.First(x => x.Id == value); }
}
private Task Load(LazyLoader lazyLoader)
{
var image = ImageRepository
.Get()
.Include(x => x.DockerImages)
.First(x => x.Id == CurrentServer.Image.Id);
Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name);
var res = new List<string>();
foreach (var image in serverImage.DockerImages)
{
res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name));
}
Images = res.ToArray();
await InvokeAsync(StateHasChanged);
DockerImages = image.DockerImages;
SelectedImage = DockerImages[CurrentServer.DockerImageIndex];
return Task.CompletedTask;
}
private async Task Save()
{
var serverImage = ImageRepository
.Get()
.Include(x => x.DockerImages)
.First(x => x.Id == CurrentServer.Image.Id);
var allImages = serverImage.DockerImages;
var imageToUse = allImages.First(x => x.Name.EndsWith(Image));
CurrentServer.DockerImageIndex = allImages.IndexOf(imageToUse);
ServerRepository.Update(CurrentServer);
CurrentServer.DockerImageIndex = DockerImages.IndexOf(SelectedImage);
ServerRepository.Update(CurrentServer);
await LazyLoader.Reload();
}
}