diff --git a/src/Microsoft.Tye.Hosting/Dashboard/Pages/ServiceDetails.razor b/src/Microsoft.Tye.Hosting/Dashboard/Pages/ServiceDetails.razor index 875912c55..e8f3d4f38 100644 --- a/src/Microsoft.Tye.Hosting/Dashboard/Pages/ServiceDetails.razor +++ b/src/Microsoft.Tye.Hosting/Dashboard/Pages/ServiceDetails.razor @@ -1,5 +1,6 @@ @page "/services/{ServiceName}" @inject Application application +@inject HttpClient Http @if (Service == null) { @@ -9,6 +10,14 @@ else {

@Service.Description.Name

+ @if (_service?.Replicas != null) { + if (_service.Replicas.Any()) { + + } else { + + } + } + switch (Service.ServiceType) { case ServiceType.Container: @@ -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); + } } \ No newline at end of file diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs index 4dc8f1248..6b9891851 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs @@ -178,7 +178,7 @@ service.Description.RunInfo is ProjectRunInfo project2 && } } - private void LaunchService(Application application, Service service) + public void LaunchService(Application application, Service service) { var serviceDescription = service.Description; var processInfo = new ProcessInfo(new Task[service.Description.Replicas]); @@ -460,7 +460,7 @@ async Task RunApplicationAsync(IEnumerable<(int ExternalPort, int Port, string? service.Items[typeof(ProcessInfo)] = processInfo; } - private Task KillRunningProcesses(IDictionary services) + public Task KillRunningProcesses(IDictionary services) { static Task KillProcessAsync(Service service) { diff --git a/src/Microsoft.Tye.Hosting/TyeDashboardApi.cs b/src/Microsoft.Tye.Hosting/TyeDashboardApi.cs index 90cc787b4..22b90f0cb 100644 --- a/src/Microsoft.Tye.Hosting/TyeDashboardApi.cs +++ b/src/Microsoft.Tye.Hosting/TyeDashboardApi.cs @@ -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() { @@ -34,6 +35,7 @@ public TyeDashboardApi() }; _options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); + _processRunner = processRunner; } public void MapRoutes(IEndpointRouteBuilder endpoints) @@ -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); @@ -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(); + + 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(); + + var name = (string?)context.Request.RouteValues["name"]; + if (!string.IsNullOrEmpty(name) && app.Services.TryGetValue(name, out var service)) + { + var services = new Dictionary(); + services.Add(name, service); + await _processRunner.KillRunningProcesses(services); + } + + context.Response.Redirect($"/services/{name}"); + + return; + } + private static V1Service CreateServiceJson(Service service) { var description = service.Description; diff --git a/src/Microsoft.Tye.Hosting/TyeHost.cs b/src/Microsoft.Tye.Hosting/TyeHost.cs index 86b142a3a..70536fd39 100644 --- a/src/Microsoft.Tye.Hosting/TyeHost.cs +++ b/src/Microsoft.Tye.Hosting/TyeHost.cs @@ -204,6 +204,13 @@ private IHost BuildWebApplication(Application application, HostOptions options, }); }); services.AddSingleton(application); + services.AddScoped(sp => + { + var server = sp.GetRequiredService(); + var addressFeature = server.Features.Get(); + var baseAddress = addressFeature?.Addresses.FirstOrDefault() ?? string.Empty; + return new System.Net.Http.HttpClient { BaseAddress = new Uri(baseAddress) }; + }); }) .Build(); } @@ -218,14 +225,17 @@ private void ConfigureApplication(IApplicationBuilder app) app.UseRouting(); - var api = new TyeDashboardApi(); - - app.UseEndpoints(endpoints => + if (_logger != null && _replicaRegistry != null) { - 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)