Merged logs view into edit page. WIP text editor for server files.
parent
1a84c4c51a
commit
c60cf157cd
|
@ -0,0 +1,31 @@
|
||||||
|
namespace LANCommander.Extensions
|
||||||
|
{
|
||||||
|
public static class FileExtensions
|
||||||
|
{
|
||||||
|
public static bool IsBinaryFile(this FileInfo fileInfo, int checkLength = 8000, int requiredConsecutiveNul = 1)
|
||||||
|
{
|
||||||
|
const char nulChar = '\0';
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
using (var reader = new StreamReader(fileInfo.FullName))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < checkLength && !reader.EndOfStream; i++) {
|
||||||
|
if ((char)reader.Read() == nulChar)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count >= requiredConsecutiveNul)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
@page "/Servers/{id:guid}/Logs"
|
@using Microsoft.AspNetCore.SignalR.Client
|
||||||
@using Microsoft.AspNetCore.SignalR.Client
|
|
||||||
@using NLog;
|
@using NLog;
|
||||||
@using XtermBlazor
|
@using XtermBlazor
|
||||||
@attribute [Authorize]
|
|
||||||
@inject ServerService ServerService
|
@inject ServerService ServerService
|
||||||
@inject ServerProcessService ServerProcessService
|
@inject ServerProcessService ServerProcessService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
|
@ -0,0 +1,152 @@
|
||||||
|
@using LANCommander.Extensions;
|
||||||
|
|
||||||
|
<GridRow>
|
||||||
|
<GridCol Span="6">
|
||||||
|
<Tree TItem="FileTreeNode"
|
||||||
|
DataSource="FileTree"
|
||||||
|
TitleExpression="x => x.DataItem.Name"
|
||||||
|
ChildrenExpression="x => x.DataItem.Children"
|
||||||
|
IsLeafExpression="x => !x.DataItem.HasChildren"
|
||||||
|
OnDblClick="(args) => OpenFile(args.Node.DataItem)">
|
||||||
|
</Tree>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
|
<GridCol Span="18">
|
||||||
|
<Breadcrumb>
|
||||||
|
@foreach (var part in CurrentFile.Split(Path.DirectorySeparatorChar))
|
||||||
|
{
|
||||||
|
<BreadcrumbItem>@part</BreadcrumbItem>
|
||||||
|
}
|
||||||
|
</Breadcrumb>
|
||||||
|
<StandaloneCodeEditor @ref="Editor" Id="editor" ConstructionOptions="EditorConstructionOptions" />
|
||||||
|
</GridCol>
|
||||||
|
</GridRow>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.monaco-editor-container {
|
||||||
|
min-height: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public string WorkingDirectory { get; set; }
|
||||||
|
|
||||||
|
StandaloneCodeEditor? Editor;
|
||||||
|
|
||||||
|
IEnumerable<string> Files;
|
||||||
|
HashSet<FileTreeNode> FileTree { get; set; } = new HashSet<FileTreeNode>();
|
||||||
|
string CurrentFile = "";
|
||||||
|
|
||||||
|
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
|
||||||
|
{
|
||||||
|
return new StandaloneEditorConstructionOptions
|
||||||
|
{
|
||||||
|
AutomaticLayout = true,
|
||||||
|
Theme = "vs-dark"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
var root = new FileTreeNode
|
||||||
|
{
|
||||||
|
Name = "/",
|
||||||
|
FullName = WorkingDirectory,
|
||||||
|
IsExpanded = true
|
||||||
|
};
|
||||||
|
|
||||||
|
root.PopulateChildren(WorkingDirectory);
|
||||||
|
|
||||||
|
FileTree.Add(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenFile(FileTreeNode node)
|
||||||
|
{
|
||||||
|
if (File.Exists(node.FullName))
|
||||||
|
{
|
||||||
|
var file = new FileInfo(node.FullName);
|
||||||
|
|
||||||
|
CurrentFile = node.FullName;
|
||||||
|
|
||||||
|
if (!file.IsBinaryFile())
|
||||||
|
{
|
||||||
|
Editor.SetValue(File.ReadAllText(file.FullName));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Editor.SetValue("This file cannot be edited because it is a binary file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileTreeNode
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string FullName { get; set; }
|
||||||
|
public bool IsExpanded { get; set; } = false;
|
||||||
|
public bool IsDirectory { get; set; } = false;
|
||||||
|
public bool HasChildren => Children != null && Children.Count > 0;
|
||||||
|
public HashSet<FileTreeNode> Children { get; set; } = new HashSet<FileTreeNode>();
|
||||||
|
|
||||||
|
public void PopulateChildren(string path)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.GetFiles(path))
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(file);
|
||||||
|
|
||||||
|
Children.Add(new FileTreeNode
|
||||||
|
{
|
||||||
|
Name = fileInfo.Name,
|
||||||
|
FullName = fileInfo.FullName,
|
||||||
|
IsDirectory = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var directory in Directory.GetDirectories(path))
|
||||||
|
{
|
||||||
|
var directoryInfo = new DirectoryInfo(directory);
|
||||||
|
|
||||||
|
var child = new FileTreeNode
|
||||||
|
{
|
||||||
|
Name = directoryInfo.Name,
|
||||||
|
FullName = directoryInfo.FullName,
|
||||||
|
IsDirectory = true
|
||||||
|
};
|
||||||
|
|
||||||
|
child.PopulateChildren(directoryInfo.FullName);
|
||||||
|
|
||||||
|
Children.Add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopulateChildren(IEnumerable<string> paths)
|
||||||
|
{
|
||||||
|
var childPaths = paths.Where(p => p.StartsWith(FullName) && p.EndsWith(Path.PathSeparator));
|
||||||
|
var directChildren = childPaths.Where(p => p != FullName && p.Substring(FullName.Length + 1).TrimEnd(Path.PathSeparator).Split(Path.PathSeparator).Length == 1);
|
||||||
|
|
||||||
|
foreach (var directChild in directChildren)
|
||||||
|
{
|
||||||
|
var child = new FileTreeNode()
|
||||||
|
{
|
||||||
|
FullName = directChild,
|
||||||
|
Name = directChild.Substring(FullName.Length).TrimEnd(Path.PathSeparator)
|
||||||
|
};
|
||||||
|
|
||||||
|
child.PopulateChildren(paths);
|
||||||
|
|
||||||
|
Children.Add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +1,132 @@
|
||||||
@page "/Servers/{id:guid}/Edit"
|
@page "/Servers/{id:guid}/Edit"
|
||||||
|
@page "/Servers/{id:guid}/Edit/{panel}"
|
||||||
@page "/Servers/Add"
|
@page "/Servers/Add"
|
||||||
|
@using LANCommander.Pages.Servers.Components
|
||||||
@inject ServerService ServerService
|
@inject ServerService ServerService
|
||||||
|
@inject ServerProcessService ServerProcessService
|
||||||
@inject IMessageService MessageService
|
@inject IMessageService MessageService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
<Card Title="Server Details">
|
<Layout Class="site-layout-background" Style="padding: 24px 0;">
|
||||||
<Body>
|
<Sider Class="site-layout-background" Width="200">
|
||||||
<Form Model="@Server" Layout="@FormLayout.Vertical">
|
<Menu Mode="@MenuMode.Inline" Style="height: 100%;">
|
||||||
<FormItem Label="Name">
|
<MenuItem RouterLink="@($"/Servers/{Server.Id}/Edit/General")">General</MenuItem>
|
||||||
<Input @bind-Value="@context.Name" />
|
|
||||||
</FormItem>
|
@if (Server != null && Server.Id != Guid.Empty)
|
||||||
<FormItem Label="Path">
|
{
|
||||||
<Input @bind-Value="@context.Path" />
|
<MenuItem RouterLink="@($"/Servers/{Server.Id}/Edit/Logs")">Logs</MenuItem>
|
||||||
</FormItem>
|
<MenuItem RouterLink="@($"/Servers/{Server.Id}/Edit/Files")">Files</MenuItem>
|
||||||
<FormItem Label="Arguments">
|
}
|
||||||
<Input @bind-Value="@context.Arguments" />
|
</Menu>
|
||||||
</FormItem>
|
</Sider>
|
||||||
<FormItem Label="Working Directory">
|
|
||||||
<Input @bind-Value="@context.WorkingDirectory" />
|
<Content>
|
||||||
</FormItem>
|
@if (Panel == "Logs")
|
||||||
<FormItem>
|
{
|
||||||
<Button Type="@ButtonType.Primary" OnClick="Save" Icon="@IconType.Fill.Save">Save</Button>
|
<PageHeader>
|
||||||
</FormItem>
|
<PageHeaderTitle>Logs</PageHeaderTitle>
|
||||||
</Form>
|
<PageHeaderExtra>
|
||||||
</Body>
|
<Space Size="@("large")">
|
||||||
</Card>
|
<SpaceItem Style="margin-right: 24px;">
|
||||||
|
@switch (Status)
|
||||||
|
{
|
||||||
|
case ServerProcessStatus.Running:
|
||||||
|
<Badge Status="success" Text="Running" />
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ServerProcessStatus.Starting:
|
||||||
|
<Badge Status="processing" Text="Starting" />
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ServerProcessStatus.Error:
|
||||||
|
<Badge Status="error" Text="Error" />
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ServerProcessStatus.Stopped:
|
||||||
|
default:
|
||||||
|
<Badge Status="default" Text="Stopped" />
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</SpaceItem>
|
||||||
|
|
||||||
|
@if (Status == ServerProcessStatus.Error || Status == ServerProcessStatus.Stopped)
|
||||||
|
{
|
||||||
|
<Button Type="@ButtonType.Primary" OnClick="() => Start()">Start</Button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Popconfirm OnConfirm="() => Stop()" Title="Are you sure you want to kill this server process?">
|
||||||
|
<Button Danger Type="@ButtonType.Primary">Stop</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
}
|
||||||
|
</Space>
|
||||||
|
</PageHeaderExtra>
|
||||||
|
</PageHeader>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<PageHeader>
|
||||||
|
<PageHeaderTitle>@Panel</PageHeaderTitle>
|
||||||
|
</PageHeader>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="site-layout-content">
|
||||||
|
@if (Panel == "General")
|
||||||
|
{
|
||||||
|
<Form Model="@Server" Layout="@FormLayout.Vertical">
|
||||||
|
<FormItem Label="Name">
|
||||||
|
<Input @bind-Value="@context.Name" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="Path">
|
||||||
|
<Input @bind-Value="@context.Path" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="Arguments">
|
||||||
|
<Input @bind-Value="@context.Arguments" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="Working Directory">
|
||||||
|
<Input @bind-Value="@context.WorkingDirectory" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<Button Type="@ButtonType.Primary" OnClick="Save" Icon="@IconType.Fill.Save">Save</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Panel == "Logs")
|
||||||
|
{
|
||||||
|
<Logs Id="@Server.Id" />
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Panel == "Files")
|
||||||
|
{
|
||||||
|
<TextEditor WorkingDirectory="@Server.WorkingDirectory" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.site-layout-background {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-layout-content {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-content > .ant-page-header-heading {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter] public Guid Id { get; set; }
|
[Parameter] public Guid Id { get; set; }
|
||||||
|
[Parameter] public string Panel { get; set; }
|
||||||
|
|
||||||
Server Server;
|
Server Server;
|
||||||
|
ServerProcessStatus Status;
|
||||||
|
Timer Timer;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
@ -37,6 +134,32 @@
|
||||||
Server = new Server();
|
Server = new Server();
|
||||||
else
|
else
|
||||||
Server = await ServerService.Get(Id);
|
Server = await ServerService.Get(Id);
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(Server.WorkingDirectory))
|
||||||
|
Server.WorkingDirectory = "C:\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
Timer = new Timer(async (object? stateInfo) =>
|
||||||
|
{
|
||||||
|
Status = ServerProcessService.GetStatus(Server);
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}, new AutoResetEvent(false), 1000, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
ServerProcessService.StartServer(Server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Stop()
|
||||||
|
{
|
||||||
|
ServerProcessService.StopServer(Server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Save()
|
private async Task Save()
|
||||||
|
|
|
@ -105,12 +105,12 @@
|
||||||
|
|
||||||
private void Edit(Server server)
|
private void Edit(Server server)
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo($"/Servers/{server.Id}/Edit");
|
NavigationManager.NavigateTo($"/Servers/{server.Id}/Edit/General");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Logs(Server server)
|
private void Logs(Server server)
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo($"/Servers/{server.Id}/Logs");
|
NavigationManager.NavigateTo($"/Servers/{server.Id}/Edit/Logs");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start(Server server)
|
private void Start(Server server)
|
||||||
|
|
Loading…
Reference in New Issue