Skip to content
This repository has been archived by the owner on Nov 20, 2023. It is now read-only.

Add start stop button on ServiceDetails page #948

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/Microsoft.Tye.Hosting/Dashboard/Pages/ServiceDetails.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@page "/services/{ServiceName}"
@inject Application application
@inject HttpClient Http
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment regarding HttpClient injection. Can you try to see if you can get away with creating a HttpClient instance directly?


@if (Service == null)
{
Expand All @@ -9,6 +10,14 @@ else
{
<h3>@Service.Description.Name</h3>

@if (_service?.Replicas != null) {
if (_service.Replicas.Any()) {
<button @onclick="OnServiceStop" class="btn btn-danger">Stop</button>
} else {
<button @onclick="OnServiceStart" class="btn btn-success">Start</button>
}
}

switch (Service.ServiceType)
{
case ServiceType.Container:
Expand Down Expand Up @@ -36,4 +45,14 @@ else
{
application.Services.TryGetValue(ServiceName, out _service);
}

private async Task OnServiceStop()
{
await Http.PostAsync($"/api/v1/services/{ServiceName}/stop", null);
}

private async Task OnServiceStart()
{
await Http.PostAsync($"/api/v1/services/{ServiceName}/start", null);
}
}
4 changes: 2 additions & 2 deletions src/Microsoft.Tye.Hosting/ProcessRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ service.Description.RunInfo is ProjectRunInfo project2 &&
}
}

private void LaunchService(Application application, Service service)
public void LaunchService(Application application, Service service)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void LaunchService(Application application, Service service)
internal void LaunchService(Application application, Service service)

{
var serviceDescription = service.Description;
var processInfo = new ProcessInfo(new Task[service.Description.Replicas]);
Expand Down Expand Up @@ -460,7 +460,7 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string?
service.Items[typeof(ProcessInfo)] = processInfo;
}

private Task KillRunningProcesses(IDictionary<string, Service> services)
public Task KillRunningProcesses(IDictionary<string, Service> services)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public Task KillRunningProcesses(IDictionary<string, Service> services)
internal Task KillRunningProcesses(IDictionary<string, Service> services)

{
static Task KillProcessAsync(Service service)
{
Expand Down
38 changes: 37 additions & 1 deletion src/Microsoft.Tye.Hosting/TyeDashboardApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ namespace Microsoft.Tye.Hosting
public class TyeDashboardApi
{
private readonly JsonSerializerOptions _options;
private readonly ProcessRunner _processRunner;

public TyeDashboardApi()
public TyeDashboardApi(ProcessRunner processRunner)
{
_options = new JsonSerializerOptions()
{
Expand All @@ -34,6 +35,7 @@ public TyeDashboardApi()
};

_options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
_processRunner = processRunner;
}

public void MapRoutes(IEndpointRouteBuilder endpoints)
Expand All @@ -42,6 +44,8 @@ public void MapRoutes(IEndpointRouteBuilder endpoints)
endpoints.MapGet("/api/v1/application", ApplicationIndex);
endpoints.MapDelete("/api/v1/control", ControlPlaneShutdown);
endpoints.MapGet("/api/v1/services", Services);
endpoints.MapPost("/api/v1/services/{name}/stop", ServiceStop);
endpoints.MapPost("/api/v1/services/{name}/start", ServiceStart);
endpoints.MapGet("/api/v1/services/{name}", Service);
endpoints.MapGet("/api/v1/logs/{name}", Logs);
endpoints.MapGet("/api/v1/metrics", AllMetrics);
Expand Down Expand Up @@ -128,6 +132,38 @@ private Task Service(HttpContext context)
return JsonSerializer.SerializeAsync(context.Response.Body, serviceJson, _options);
}

private Task ServiceStart(HttpContext context)
{
var app = context.RequestServices.GetRequiredService<Application>();

var name = (string?)context.Request.RouteValues["name"];
if (!string.IsNullOrEmpty(name) && app.Services.TryGetValue(name, out var service))
{
_processRunner.LaunchService(app, service);
}

context.Response.Redirect($"/services/{name}");

return Task.CompletedTask;
}

private async Task ServiceStop(HttpContext context)
{
var app = context.RequestServices.GetRequiredService<Application>();

var name = (string?)context.Request.RouteValues["name"];
if (!string.IsNullOrEmpty(name) && app.Services.TryGetValue(name, out var service))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, if no service with given name is found, 404 and no redirect, please.

{
var services = new Dictionary<string, Service>();
services.Add(name, service);
await _processRunner.KillRunningProcesses(services);
}

context.Response.Redirect($"/services/{name}");

return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant return, please remove.

}

private static V1Service CreateServiceJson(Service service)
{
var description = service.Description;
Expand Down
24 changes: 17 additions & 7 deletions src/Microsoft.Tye.Hosting/TyeHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ private IHost BuildWebApplication(Application application, HostOptions options,
});
});
services.AddSingleton(application);
services.AddScoped(sp =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is necessary. You should be able to access HttpContext.Request from Razor pages/views: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-7.0#access-httpcontext-from-razor-pages and build the URL to the start/stop API request from that.

{
var server = sp.GetRequiredService<AspNetCore.Hosting.Server.IServer>();
var addressFeature = server.Features.Get<AspNetCore.Hosting.Server.Features.IServerAddressesFeature>();
var baseAddress = addressFeature?.Addresses.FirstOrDefault() ?? string.Empty;
return new System.Net.Http.HttpClient { BaseAddress = new Uri(baseAddress) };
});
})
.Build();
}
Expand All @@ -218,14 +225,17 @@ private void ConfigureApplication(IApplicationBuilder app)

app.UseRouting();

var api = new TyeDashboardApi();

app.UseEndpoints(endpoints =>
if (_logger != null && _replicaRegistry != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would these be null?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compiler shows null warnings

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if it makes a lot of difference to delay creation of _logger and _replicaRegistry to when StartAsync() is called. I would consider making them non-nullable and create/have them injected in the constructor, which would enable using them here without a null check.

{
api.MapRoutes(endpoints);
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
var api = new TyeDashboardApi(new ProcessRunner(_logger, _replicaRegistry, ProcessRunnerOptions.FromHostOptions(_options)));

app.UseEndpoints(endpoints =>
{
api.MapRoutes(endpoints);
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}

private int ComputePort(int? port)
Expand Down