Browse Source

Merge a73240ba57 into 75465614e6

pull/1566/merge
Razvan Goga 3 years ago
committed by GitHub
parent
commit
86d7de32a3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 55
      src/Microsoft.Tye.Hosting/Ansi2Html/Constants.cs
  2. 49
      src/Microsoft.Tye.Hosting/Ansi2Html/Converter.cs
  3. 21
      src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor
  4. 9
      src/Microsoft.Tye.Hosting/Dashboard/Pages/Logs.razor
  5. 51
      src/Microsoft.Tye.Hosting/Model/Service.cs
  6. 5
      src/Microsoft.Tye.Hosting/ProcessRunner.cs
  7. 2
      src/Microsoft.Tye.Hosting/TyeHost.cs
  8. 21
      test/UnitTests/Ansi2HtmlConverterTests.cs
  9. 8
      test/UnitTests/Microsoft.Tye.UnitTests.csproj
  10. 52
      test/UnitTests/ServiceUnitTests.cs

55
src/Microsoft.Tye.Hosting/Ansi2Html/Constants.cs

@ -0,0 +1,55 @@
using System.Collections.Generic;
namespace Microsoft.Tye.Hosting.Ansi2Html
{
public static class Constants
{
public const string Red = "#800000";
public const string Black = "#000000";
public const string Green = "#008000";
public const string Yellow = "#808000";
public const string Blue = "#000080";
public const string Purple = "#800080";
public const string Cyan = "#008080";
public const string LightGray = "#c0c0c0";
public const string DarkGray = "#808080";
public const string BrightRed = "#ff0000";
public const string BrightGreen = "#00ff00";
public const string BrightYellow = "#ffff00";
public const string BrightBlue = "#0000ff";
public const string BrightPurple = "#ff00ff";
public const string BrightCyan = "#00ffff";
public const string White = "#ffffff";
public static Dictionary<string, string> ColorMap = new Dictionary<string, string>()
{
{ "0", Black }, // Black
{ "1", Red }, // Red
{ "2", Green }, // Green
{ "3", Yellow }, // Yellow
{ "4", Blue }, // Blue
{ "5", Purple }, // Purple
{ "6", Cyan }, // Cyan
{ "7", LightGray }, // Light Gray
{ "8", DarkGray }, // Dark Gray
{ "9", BrightRed }, // Bright Red
{ "10", BrightGreen }, // Bright Green
{ "11", BrightYellow }, // Bright Yellow
{ "12", BrightBlue }, // Bright Blue
{ "13", BrightPurple }, // Bright Purple
{ "14", BrightCyan }, // Bright Cyan
{ "15", White } // White
};
public static class SelectGraphicRenditionParameters
{
public const int Reset = 0;
public static HashSet<int> SetForeground = new HashSet<int>()
{
30, 31, 32, 33, 34, 35, 36, 37,38
};
}
}
}

49
src/Microsoft.Tye.Hosting/Ansi2Html/Converter.cs

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.Tye.Hosting.Ansi2Html
{
public class Converter
{
private readonly Regex _rule = new Regex("\u001b\\[(?<code>\\d+)(?<args>;\\d+)*m", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
public string Parse(string input)
{
var htmlText = _rule.Replace(input, match =>
{
var code = Convert.ToInt32(match.Groups["code"].Value);
var args = match.Groups["args"].Value;
List<string> attributes = args.Split(';').ToList();
var tagBuilder = new StringBuilder();
if (code == Constants.SelectGraphicRenditionParameters.Reset)
{
tagBuilder.Append("</span>");
}
else
{
var color = Constants.White;
if (attributes.Count > 0)
{
string colorCode = attributes.Last();
if (Constants.ColorMap.ContainsKey(colorCode))
{
color = Constants.ColorMap[colorCode];
}
}
tagBuilder.Append("<span style=\"color:");
tagBuilder.Append(color);
tagBuilder.Append(";\">");
}
return tagBuilder.ToString();
});
return htmlText;
}
}
}

21
src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor

@ -12,13 +12,14 @@
<table class="table service-table">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Type</th>
<th>Source</th>
<th>Bindings</th>
<th>Replicas</th>
<th>Restarts</th>
<th>Logs</th>
<th></th>
</tr>
</thead>
<tbody>
@ -26,7 +27,11 @@
{
var logsPath = $"logs/{service.Description.Name}";
var servicePath = $"services/{service.Description.Name}";
var serviceState = service.State;
<tr @key="service.Description.Name">
<td>
<span class="badge @GetServiceStateClass(serviceState)">@serviceState</span>
</td>
<td>
@if(service.ServiceType == ServiceType.External)
{
@ -34,7 +39,7 @@
}
else
{
<a href="@servicePath">@service.Description.Name</a>
<a href="@logsPath">@service.Description.Name</a>
}
</td>
<td>
@ -91,7 +96,7 @@
{
<td>@service.Replicas.Count/@service.Description.Replicas</td>
<td>@service.Restarts</td>
<td><NavLink href="@logsPath">View</NavLink></td>
<td><NavLink href="@logsPath">Logs</NavLink> | <NavLink href="@servicePath">Metrics</NavLink></td>
}
</tr>
}
@ -102,6 +107,16 @@
private List<IDisposable> _subscriptions = new List<IDisposable>();
string GetServiceStateClass(ServiceState serviceState) => serviceState switch
{
ServiceState.Starting => "badge-secondary",
ServiceState.Started => "badge-success",
ServiceState.Degraded => "badge-danger",
ServiceState.Failed => "badge-warning",
ServiceState.Stopped => "badge-light",
_ => "badge-dark"
};
string GetUrl(ServiceBinding b)
{
return $"{(b.Protocol ?? "tcp")}://{b.Host ?? "localhost"}:{b.Port}";

9
src/Microsoft.Tye.Hosting/Dashboard/Pages/Logs.razor

@ -1,6 +1,8 @@
@page "/logs/{ServiceName}"
@using Microsoft.Tye.Hosting.Ansi2Html;
@inject IJSRuntime JS
@inject Application application
@inject Converter ansi2HtmlParser
@implements IDisposable
<style>
@ -42,7 +44,7 @@ else
[Parameter]
public string ServiceName { get; set; } = default!;
public List<(string Text, int Id)>? ApplicationLogs { get; set; }
public List<(MarkupString Text, int Id)>? ApplicationLogs { get; set; }
private IDisposable? _subscription;
@ -53,7 +55,7 @@ else
// TODO: handle this returning false
if (application.Services.TryGetValue(ServiceName, out var service))
{
ApplicationLogs = service.CachedLogs.Select((item, index) => (item, index)).ToList();
ApplicationLogs = service.CachedLogs.Select((item, index) => ((MarkupString)item, index)).ToList();
var count = ApplicationLogs.Count;
StateHasChanged();
@ -62,7 +64,8 @@ else
count++;
InvokeAsync(() =>
{
ApplicationLogs.Add((log, count));
string htmlLog = ansi2HtmlParser.Parse(log);
ApplicationLogs.Add(((MarkupString)htmlLog, count));
StateHasChanged();
});
});

51
src/Microsoft.Tye.Hosting/Model/Service.cs

@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
namespace Microsoft.Tye.Hosting.Model
@ -83,5 +84,55 @@ namespace Microsoft.Tye.Hosting.Model
public Subject<string> Logs { get; } = new Subject<string>();
public Subject<ReplicaEvent> ReplicaEvents { get; } = new Subject<ReplicaEvent>();
public ServiceState State
{
get
{
var replicaStates = Replicas.Values.Select(r => r.State);
int replicaCount = replicaStates.Count();
if (replicaCount == 0)
return ServiceState.Unknown;
if (replicaStates.Any(r => r == ReplicaState.Added))
return ServiceState.Starting;
if (replicaStates.All(r => r == ReplicaState.Started || r == ReplicaState.Ready || r == ReplicaState.Healthy))
return ServiceState.Started;
if (replicaCount == 1)
{
ReplicaState? replicaState = replicaStates.Single();
if (replicaState == ReplicaState.Removed)
return ServiceState.Failed;
if (replicaState == ReplicaState.Stopped)
return ServiceState.Stopped;
}
else
{
if (replicaStates.All(r => r == ReplicaState.Stopped))
return ServiceState.Stopped;
if (replicaStates.Any(r => r == ReplicaState.Removed || r == ReplicaState.Stopped))
return ServiceState.Degraded;
}
return ServiceState.Unknown;
}
}
}
public enum ServiceState
{
Unknown,
Starting,
Started,
Degraded,
Failed,
Stopped
}
}

5
src/Microsoft.Tye.Hosting/ProcessRunner.cs

@ -200,10 +200,7 @@ namespace Microsoft.Tye.Hosting
{
// Default to development environment
["DOTNET_ENVIRONMENT"] = "Development",
["ASPNETCORE_ENVIRONMENT"] = "Development",
// Remove the color codes from the console output
["DOTNET_LOGGING__CONSOLE__DISABLECOLORS"] = "true",
["ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS"] = "true"
["ASPNETCORE_ENVIRONMENT"] = "Development"
};
// Set up environment variables to use the version of dotnet we're using to run

2
src/Microsoft.Tye.Hosting/TyeHost.cs

@ -20,6 +20,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Tye.Hosting.Ansi2Html;
using Microsoft.Tye.Hosting.Diagnostics;
using Microsoft.Tye.Hosting.Model;
using Serilog;
@ -204,6 +205,7 @@ namespace Microsoft.Tye.Hosting
});
});
services.AddSingleton(application);
services.AddTransient<Converter>();
})
.Build();
}

21
test/UnitTests/Ansi2HtmlConverterTests.cs

@ -0,0 +1,21 @@
using Microsoft.Tye.Hosting.Ansi2Html;
using Xunit;
namespace Microsoft.Tye.UnitTests;
public class Ansi2HtmlConverterTests
{
[Theory]
[InlineData("\u001b[31;1mThis text is red\u001b[0m", $"<span style=\"color:{Constants.Red};\">This text is red</span>")]
[InlineData(
"\u001b[31;1m\u001b[0m\u001b[36;1m\u001b[36;1m 3 | \u001b[0m \u001b[36;1muvicorn\u001b[0m app.main:app --port $Env:PORT --reload --log-level debug\u001b[0m",
$"<span style=\"color:{Constants.Red};\"></span><span style=\"color:{Constants.Red};\"><span style=\"color:{Constants.Red};\"> 3 | </span> <span style=\"color:{Constants.Red};\">uvicorn</span> app.main:app --port $Env:PORT --reload --log-level debug</span>")]
public void ShouldParse(string input, string expected)
{
Converter converter = new Converter();
string actual = converter.Parse(input);
Assert.Equal(expected, actual);
}
}

8
test/UnitTests/Microsoft.Tye.UnitTests.csproj

@ -9,6 +9,14 @@
<TestRunnerName>XUnit</TestRunnerName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<LangVersion>10.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<LangVersion>10.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="1.0.1" />
<PackageReference Include="coverlet.msbuild" Version="2.8.0">

52
test/UnitTests/ServiceUnitTests.cs

@ -0,0 +1,52 @@
using System.Collections.Generic;
using Microsoft.Tye.Hosting.Model;
using Xunit;
namespace Microsoft.Tye.UnitTests
{
public class ServiceUnitTests
{
[Theory]
[MemberData(nameof(ServiceStateTestData))]
public void ServiceStateIsBasedOnReplicaStates(ServiceState expected, List<ReplicaState> replicaStates)
{
Service service = new(new ServiceDescription("test", null), ServiceSource.Unknown);
for (int i = 0; i < replicaStates.Count; i++)
{
string replicaName = i.ToString();
service.Replicas.TryAdd(replicaName, new ReplicaStatus(service, replicaName)
{
State = replicaStates[i],
});
}
Assert.Equal(expected, service.State);
}
public static IEnumerable<object[]> ServiceStateTestData =>
new List<object[]>
{
//no replica - should not happen
new object[] { ServiceState.Unknown, new List<ReplicaState>() },
//one replica
new object[] { ServiceState.Starting, new List<ReplicaState>() { ReplicaState.Added } },
new object[] { ServiceState.Started, new List<ReplicaState>() { ReplicaState.Started } },
new object[] { ServiceState.Started, new List<ReplicaState>() { ReplicaState.Ready } },
new object[] { ServiceState.Started, new List<ReplicaState>() { ReplicaState.Healthy } },
new object[] { ServiceState.Failed, new List<ReplicaState>() { ReplicaState.Removed } },
new object[] { ServiceState.Stopped, new List<ReplicaState>() { ReplicaState.Stopped } },
//multiple replicas
new object[] { ServiceState.Starting, new List<ReplicaState>() { ReplicaState.Added, ReplicaState.Started, ReplicaState.Ready, ReplicaState.Healthy } },
new object[] { ServiceState.Started, new List<ReplicaState>() { ReplicaState.Started, ReplicaState.Ready, ReplicaState.Healthy } },
new object[] { ServiceState.Degraded, new List<ReplicaState>() { ReplicaState.Removed, ReplicaState.Started, ReplicaState.Ready, ReplicaState.Healthy } },
new object[] { ServiceState.Degraded, new List<ReplicaState>() { ReplicaState.Stopped, ReplicaState.Started, ReplicaState.Ready, ReplicaState.Healthy } },
new object[] { ServiceState.Degraded, new List<ReplicaState>() { ReplicaState.Removed, ReplicaState.Stopped, ReplicaState.Started, ReplicaState.Ready, ReplicaState.Healthy } },
new object[] { ServiceState.Stopped, new List<ReplicaState>() { ReplicaState.Stopped, ReplicaState.Stopped, ReplicaState.Stopped } },
};
}
}
Loading…
Cancel
Save