mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2025-08-10 13:40:40 +02:00
Add form for sending commands to instances
This commit is contained in:
parent
0695ee8405
commit
7f1e838331
Agent/Phantom.Agent.Services
Common
Phantom.Common.Data/Replies
Phantom.Common.Messages
Server
@ -124,6 +124,10 @@ sealed class Instance : IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SendCommand(string command, CancellationToken cancellationToken) {
|
||||
return await currentState.SendCommand(command, cancellationToken);
|
||||
}
|
||||
|
||||
private sealed class InstanceContextImpl : InstanceContext {
|
||||
private readonly Instance instance;
|
||||
private int statusUpdateCounter;
|
||||
|
@ -131,6 +131,28 @@ sealed class InstanceSessionManager : IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SendCommandToInstanceResult> SendCommand(Guid instanceGuid, string command) {
|
||||
try {
|
||||
await semaphore.WaitAsync(shutdownCancellationToken);
|
||||
} catch (OperationCanceledException) {
|
||||
return SendCommandToInstanceResult.AgentShuttingDown;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!instances.TryGetValue(instanceGuid, out var instance)) {
|
||||
return SendCommandToInstanceResult.InstanceDoesNotExist;
|
||||
}
|
||||
|
||||
if (!await instance.SendCommand(command, shutdownCancellationToken)) {
|
||||
return SendCommandToInstanceResult.UnknownError;
|
||||
}
|
||||
|
||||
return SendCommandToInstanceResult.Success;
|
||||
} finally {
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAll() {
|
||||
shutdownCancellationTokenSource.Cancel();
|
||||
|
||||
|
@ -3,4 +3,5 @@
|
||||
interface IInstanceState {
|
||||
IInstanceState Launch(InstanceContext context);
|
||||
IInstanceState Stop();
|
||||
Task<bool> SendCommand(string command, CancellationToken cancellationToken);
|
||||
}
|
||||
|
@ -94,6 +94,10 @@ sealed class InstanceLaunchingState : IInstanceState, IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task<bool> SendCommand(string command, CancellationToken cancellationToken) {
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
cancellationTokenSource.Dispose();
|
||||
}
|
||||
|
@ -23,4 +23,8 @@ sealed class InstanceNotRunningState : IInstanceState {
|
||||
public IInstanceState Stop() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task<bool> SendCommand(string command, CancellationToken cancellationToken) {
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,19 @@ sealed class InstanceRunningState : IInstanceState {
|
||||
return new InstanceStoppingState(context, session, sessionObjects);
|
||||
}
|
||||
|
||||
public async Task<bool> SendCommand(string command, CancellationToken cancellationToken) {
|
||||
try {
|
||||
context.Logger.Information("Sending command: {Command}", command);
|
||||
await session.SendCommand(command, cancellationToken);
|
||||
return true;
|
||||
} catch (OperationCanceledException) {
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
context.Logger.Warning(e, "Caught exception while sending command.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SessionObjects {
|
||||
private readonly InstanceContext context;
|
||||
private readonly InstanceSession session;
|
||||
|
@ -65,6 +65,10 @@ sealed class InstanceStoppingState : IInstanceState, IDisposable {
|
||||
return this; // TODO maybe provide a way to kill?
|
||||
}
|
||||
|
||||
public Task<bool> SendCommand(string command, CancellationToken cancellationToken) {
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
sessionObjects.Dispose();
|
||||
}
|
||||
|
@ -61,4 +61,8 @@ public sealed class MessageListener : IMessageToAgentListener {
|
||||
public async Task HandleStopInstance(StopInstanceMessage message) {
|
||||
await socket.SendSimpleReply(message, await agent.InstanceSessionManager.Stop(message.InstanceGuid));
|
||||
}
|
||||
|
||||
public async Task HandleSendCommandToInstance(SendCommandToInstanceMessage message) {
|
||||
await socket.SendSimpleReply(message, await agent.InstanceSessionManager.SendCommand(message.InstanceGuid, message.Command));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
namespace Phantom.Common.Data.Replies;
|
||||
|
||||
public enum SendCommandToInstanceResult {
|
||||
Success,
|
||||
InstanceDoesNotExist,
|
||||
AgentShuttingDown,
|
||||
AgentCommunicationError,
|
||||
UnknownError
|
||||
}
|
||||
|
||||
public static class SendCommandToInstanceResultExtensions {
|
||||
public static string ToSentence(this SendCommandToInstanceResult reason) {
|
||||
return reason switch {
|
||||
SendCommandToInstanceResult.Success => "Command sent.",
|
||||
SendCommandToInstanceResult.InstanceDoesNotExist => "Instance does not exist.",
|
||||
SendCommandToInstanceResult.AgentShuttingDown => "Agent is shutting down.",
|
||||
SendCommandToInstanceResult.AgentCommunicationError => "Agent did not reply in time.",
|
||||
_ => "Unknown error."
|
||||
};
|
||||
}
|
||||
}
|
@ -8,4 +8,5 @@ public interface IMessageToAgentListener {
|
||||
Task HandleConfigureInstance(ConfigureInstanceMessage message);
|
||||
Task HandleLaunchInstance(LaunchInstanceMessage message);
|
||||
Task HandleStopInstance(StopInstanceMessage message);
|
||||
Task HandleSendCommandToInstance(SendCommandToInstanceMessage message);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ public static class MessageRegistries {
|
||||
ToAgent.Add<ConfigureInstanceMessage>(2);
|
||||
ToAgent.Add<LaunchInstanceMessage>(3);
|
||||
ToAgent.Add<StopInstanceMessage>(4);
|
||||
ToAgent.Add<SendCommandToInstanceMessage>(5);
|
||||
|
||||
ToServer.Add<RegisterAgentMessage>(0);
|
||||
ToServer.Add<UnregisterAgentMessage>(1);
|
||||
|
@ -0,0 +1,14 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace Phantom.Common.Messages.ToAgent;
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed record SendCommandToInstanceMessage(
|
||||
[property: Key(0)] uint SequenceId,
|
||||
[property: Key(1)] Guid InstanceGuid,
|
||||
[property: Key(2)] string Command
|
||||
) : IMessageToAgent, IMessageWithReply {
|
||||
public Task Accept(IMessageToAgentListener listener) {
|
||||
return listener.HandleSendCommandToInstance(this);
|
||||
}
|
||||
}
|
@ -144,6 +144,16 @@ public sealed class InstanceManager {
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SendCommandToInstanceResult> SendCommand(Guid instanceGuid, string command) {
|
||||
var instance = GetInstance(instanceGuid);
|
||||
if (instance != null) {
|
||||
var reply = (SendCommandToInstanceResult?) await agentManager.SendMessageWithReply(instance.Configuration.AgentGuid, sequenceId => new SendCommandToInstanceMessage(sequenceId, instanceGuid, command), TimeSpan.FromSeconds(10));
|
||||
return reply ?? SendCommandToInstanceResult.AgentCommunicationError;
|
||||
}
|
||||
|
||||
return SendCommandToInstanceResult.InstanceDoesNotExist;
|
||||
}
|
||||
|
||||
internal ImmutableArray<InstanceConfiguration> GetInstanceConfigurationsForAgent(Guid agentGuid) {
|
||||
return instances.GetInstances().Values.Select(static instance => instance.Configuration).Where(configuration => configuration.AgentGuid == agentGuid).ToImmutableArray();
|
||||
}
|
||||
|
@ -23,6 +23,20 @@ else {
|
||||
}
|
||||
|
||||
<InstanceLog InstanceGuid="InstanceGuid" />
|
||||
|
||||
<div class="mb-3">
|
||||
<form @onsubmit="ExecuteCommand" class="@(commandError == null ? "" : "is-invalid")">
|
||||
<label for="command-input" class="form-label">Instance Name</label>
|
||||
<div class="input-group flex-nowrap">
|
||||
<span class="input-group-text" style="padding-top: 0.3rem;">/</span>
|
||||
<input id="command-input" class="form-control" type="text" placeholder="command" @bind="commandInput" @bind:event="oninput" disabled="@(isSendingCommand || !Instance.Status.CanSendCommand())" @ref="commandInputElement" />
|
||||
<button type="submit" class="btn btn-primary" disabled="@(string.IsNullOrWhiteSpace(commandInput) || isSendingCommand)">Execute</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="invalid-feedback mt-2">
|
||||
@commandError
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
@ -34,6 +48,11 @@ else {
|
||||
private bool isLaunchingInstance = false;
|
||||
private bool isStoppingInstance = false;
|
||||
|
||||
private ElementReference commandInputElement;
|
||||
private string commandInput = string.Empty;
|
||||
private string? commandError = null;
|
||||
private bool isSendingCommand = false;
|
||||
|
||||
private Instance? Instance { get; set; }
|
||||
|
||||
protected override void OnInitialized() {
|
||||
@ -64,6 +83,23 @@ else {
|
||||
isStoppingInstance = false;
|
||||
}
|
||||
|
||||
private async Task ExecuteCommand() {
|
||||
isSendingCommand = true;
|
||||
commandError = null;
|
||||
await Task.Yield();
|
||||
|
||||
var result = await InstanceManager.SendCommand(InstanceGuid, commandInput);
|
||||
if (result == SendCommandToInstanceResult.Success) {
|
||||
commandInput = string.Empty;
|
||||
}
|
||||
else {
|
||||
commandError = result.ToSentence();
|
||||
}
|
||||
|
||||
isSendingCommand = false;
|
||||
await commandInputElement.FocusAsync(preventScroll: true);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
InstanceManager.InstancesChanged.Unsubscribe(this);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user