Commit 8e38c07e authored by Savorboard's avatar Savorboard Committed by GitHub

merge dashboard branch to develop

merge dashboard branch to develop
parents e7d99983 05d92d91

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.3
VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B2AE124-6636-4DE9-83A3-70360DABD0C4}"
EndProject
......@@ -60,7 +60,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.PostgreSql", "samples\Sample.RabbitMQ.PostgreSql\Sample.RabbitMQ.PostgreSql.csproj", "{A17E8E72-DFFC-4822-BB38-73D59A8B264E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetCore.CAP.PostgreSql.Test", "test\DotNetCore.CAP.PostgreSql.Test\DotNetCore.CAP.PostgreSql.Test.csproj", "{7CA3625D-1817-4695-881D-7E79A1E1DED2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql.Test", "test\DotNetCore.CAP.PostgreSql.Test\DotNetCore.CAP.PostgreSql.Test.csproj", "{7CA3625D-1817-4695-881D-7E79A1E1DED2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
......
<Project>
<PropertyGroup>
<VersionMajor>2</VersionMajor>
<VersionMinor>0</VersionMinor>
<VersionPatch>1</VersionPatch>
<VersionMinor>1</VersionMinor>
<VersionPatch>0</VersionPatch>
<VersionQuality></VersionQuality>
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix>
</PropertyGroup>
......
......@@ -24,7 +24,7 @@ namespace Sample.RabbitMQ.MySql.Controllers
public IActionResult PublishMessage()
{
_capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
return Ok();
}
......
using Microsoft.EntityFrameworkCore;
using Sample.RabbitMQ.SqlServer.Controllers;
namespace Sample.RabbitMQ.SqlServer
{
public class AppDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=TestCap;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True");
......
......@@ -2,12 +2,14 @@
using System.Diagnostics;
using System.Threading.Tasks;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Sample.RabbitMQ.SqlServer.Controllers
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
......@@ -33,12 +35,17 @@ namespace Sample.RabbitMQ.SqlServer.Controllers
[Route("~/publish")]
public IActionResult PublishMessage()
{
using(var trans = _dbContext.Database.BeginTransaction())
{
//_capBus.Publish("sample.rabbitmq.mysql22222", DateTime.Now);
_capBus.Publish("sample.rabbitmq.mysql33333", new Person { Name = "宜兴", Age = 11 });
trans.Commit();
}
_capBus.Publish("sample.rabbitmq.sqlserver.order.check", DateTime.Now);
//var person = new Person
//{
// Name = "杨晓东",
// Age = 11,
// Id = 23
//};
//_capBus.Publish("sample.rabbitmq.mysql33333", person);
return Ok();
}
......@@ -48,7 +55,7 @@ namespace Sample.RabbitMQ.SqlServer.Controllers
using (var trans = await _dbContext.Database.BeginTransactionAsync())
{
await _capBus.PublishAsync("sample.rabbitmq.mysql", "");
trans.Commit();
}
return Ok();
......
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Sample.RabbitMQ.SqlServer;
using System;
namespace Sample.RabbitMQ.SqlServer.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20170824130007_AddPersons")]
partial class AddPersons
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Sample.RabbitMQ.SqlServer.Controllers.Person", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Age");
b.Property<string>("Name");
b.HasKey("Id");
b.ToTable("Persons");
});
#pragma warning restore 612, 618
}
}
}
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace Sample.RabbitMQ.SqlServer.Migrations
{
public partial class AddPersons : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Persons",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Age = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Persons", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Persons");
}
}
}
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Sample.RabbitMQ.SqlServer;
using System;
namespace Sample.RabbitMQ.SqlServer.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Sample.RabbitMQ.SqlServer.Controllers.Person", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Age");
b.Property<string>("Name");
b.HasKey("Id");
b.ToTable("Persons");
});
#pragma warning restore 612, 618
}
}
}
......@@ -24,7 +24,7 @@ namespace Sample.RabbitMQ.SqlServer
//host.Run();
public static void Main(string[] args)
{
{
BuildWebHost(args).Run();
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DotNetCore.CAP;
namespace Sample.RabbitMQ.SqlServer.Services
{
public interface ICmsService
{
void Add();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.RabbitMQ.SqlServer.Services
{
public interface IOrderService
{
void Check();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DotNetCore.CAP;
namespace Sample.RabbitMQ.SqlServer.Services.Impl
{
public class CmsService : ICmsService, ICapSubscribe
{
public void Add()
{
throw new NotImplementedException();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DotNetCore.CAP;
namespace Sample.RabbitMQ.SqlServer.Services.Impl
{
public class OrderService : IOrderService, ICapSubscribe
{
[CapSubscribe("sample.rabbitmq.sqlserver.order.check")]
public void Check()
{
Console.WriteLine("out");
}
}
}
......@@ -2,6 +2,8 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sample.RabbitMQ.SqlServer.Services;
using Sample.RabbitMQ.SqlServer.Services.Impl;
namespace Sample.RabbitMQ.SqlServer
{
......@@ -11,14 +13,19 @@ namespace Sample.RabbitMQ.SqlServer
{
services.AddDbContext<AppDbContext>();
services.AddScoped<IOrderService, OrderService>();
services.AddTransient<ICmsService, CmsService>();
services.AddCap(x =>
{
x.UseEntityFramework<AppDbContext>();
x.UseRabbitMQ(y=> {
y.HostName = "192.168.2.206";
y.UserName = "admin";
y.Password = "123123";
x.UseRabbitMQ(xx =>
{
xx.HostName = "192.168.2.206";
xx.UserName = "admin";
xx.Password = "123123";
});
x.UseDashboard();
});
services.AddMvc();
......@@ -32,6 +39,8 @@ namespace Sample.RabbitMQ.SqlServer
app.UseMvc();
app.UseCap();
app.UseCapDashboard();
}
}
}
\ No newline at end of file
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Dashboard;
using Microsoft.Extensions.Logging;
using MySql.Data.MySqlClient;
......@@ -17,6 +18,16 @@ namespace DotNetCore.CAP.MySql
_logger = logger;
}
public IStorageConnection GetConnection()
{
throw new System.NotImplementedException();
}
public IMonitoringApi GetMonitoringApi()
{
throw new System.NotImplementedException();
}
public async Task InitializeAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested) return;
......
......@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor.States;
using MySql.Data.MySqlClient;
namespace DotNetCore.CAP.MySql
......@@ -117,6 +118,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
}
}
public void Dispose()
{
}
......@@ -148,5 +150,20 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction);
}
public List<string> GetRangeFromSet(string key, int startingFrom, int endingAt)
{
throw new NotImplementedException();
}
public bool ChangePublishedState(int messageId, IState state)
{
throw new NotImplementedException();
}
public bool ChangeReceivedState(int messageId, IState state)
{
throw new NotImplementedException();
}
}
}
\ No newline at end of file
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Dashboard;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Npgsql;
......@@ -18,6 +19,16 @@ namespace DotNetCore.CAP.PostgreSql
_logger = logger;
}
public IStorageConnection GetConnection()
{
throw new System.NotImplementedException();
}
public IMonitoringApi GetMonitoringApi()
{
throw new System.NotImplementedException();
}
public async Task InitializeAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested) return;
......
......@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor.States;
using Npgsql;
namespace DotNetCore.CAP.PostgreSql
......@@ -133,5 +134,20 @@ namespace DotNetCore.CAP.PostgreSql
return new PostgreSqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction);
}
public List<string> GetRangeFromSet(string key, int startingFrom, int endingAt)
{
throw new NotImplementedException();
}
public bool ChangePublishedState(int messageId, IState state)
{
throw new NotImplementedException();
}
public bool ChangeReceivedState(int messageId, IState state)
{
throw new NotImplementedException();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Dapper;
using DotNetCore.CAP.Dashboard;
using DotNetCore.CAP.Dashboard.Monitoring;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor.States;
namespace DotNetCore.CAP.SqlServer
{
internal class SqlServerMonitoringApi : IMonitoringApi
{
private readonly SqlServerStorage _storage;
private readonly SqlServerOptions _options;
public SqlServerMonitoringApi(IStorage storage, SqlServerOptions options)
{
if (storage == null) throw new ArgumentNullException(nameof(storage));
if (options == null) throw new ArgumentNullException(nameof(options));
_options = options;
_storage = storage as SqlServerStorage;
}
public StatisticsDto GetStatistics()
{
string sql = String.Format(@"
set transaction isolation level read committed;
select count(Id) from [{0}].Published with (nolock) where StatusName = N'Succeeded';
select count(Id) from [{0}].Received with (nolock) where StatusName = N'Succeeded';
select count(Id) from [{0}].Published with (nolock) where StatusName = N'Failed';
select count(Id) from [{0}].Received with (nolock) where StatusName = N'Failed';
select count(Id) from [{0}].Published with (nolock) where StatusName = N'Processing';
select count(Id) from [{0}].Received with (nolock) where StatusName = N'Processing';",
_options.Schema);
var statistics = UseConnection(connection =>
{
var stats = new StatisticsDto();
using (var multi = connection.QueryMultiple(sql))
{
stats.PublishedSucceeded = multi.ReadSingle<int>();
stats.ReceivedSucceeded = multi.ReadSingle<int>();
stats.PublishedFailed = multi.ReadSingle<int>();
stats.ReceivedFailed = multi.ReadSingle<int>();
stats.PublishedProcessing = multi.ReadSingle<int>();
stats.ReceivedProcessing = multi.ReadSingle<int>();
}
return stats;
});
statistics.Servers = 1;
return statistics;
}
public IDictionary<DateTime, int> HourlyFailedJobs(MessageType type)
{
var tableName = type == MessageType.Publish ? "Published" : "Received";
return UseConnection(connection =>
GetHourlyTimelineStats(connection, tableName, FailedState.StateName));
}
public IDictionary<DateTime, int> HourlySucceededJobs(MessageType type)
{
var tableName = type == MessageType.Publish ? "Published" : "Received";
return UseConnection(connection =>
GetHourlyTimelineStats(connection, tableName, SucceededState.StateName));
}
public IList<MessageDto> Messages(MessageQueryDto queryDto)
{
var tableName = queryDto.MessageType == Models.MessageType.Publish ? "Published" : "Received";
var where = string.Empty;
if (!string.IsNullOrEmpty(queryDto.StatusName))
{
where += " and statusname=@StatusName";
}
if (!string.IsNullOrEmpty(queryDto.Name))
{
where += " and name=@Name";
}
if (!string.IsNullOrEmpty(queryDto.Group))
{
where += " and group=@Group";
}
if (!string.IsNullOrEmpty(queryDto.Content))
{
where += " and content like '%@Content%'";
}
var sqlQuery = $"select * from [{_options.Schema}].{tableName} where 1=1 {where} order by Added desc offset @Offset rows fetch next @Limit rows only";
return UseConnection(conn =>
{
return conn.Query<MessageDto>(sqlQuery, new
{
StatusName = queryDto.StatusName,
Group = queryDto.Group,
Name = queryDto.Name,
Content = queryDto.Content,
Offset = queryDto.CurrentPage * queryDto.PageSize,
Limit = queryDto.PageSize,
}).ToList();
});
}
public int PublishedFailedCount()
{
return UseConnection(conn =>
{
return GetNumberOfMessage(conn, "Published", StatusName.Failed);
});
}
public int PublishedProcessingCount()
{
return UseConnection(conn =>
{
return GetNumberOfMessage(conn, "Published", StatusName.Processing);
});
}
public int PublishedSucceededCount()
{
return UseConnection(conn =>
{
return GetNumberOfMessage(conn, "Published", StatusName.Succeeded);
});
}
public int ReceivedFailedCount()
{
return UseConnection(conn =>
{
return GetNumberOfMessage(conn, "Received", StatusName.Failed);
});
}
public int ReceivedProcessingCount()
{
return UseConnection(conn =>
{
return GetNumberOfMessage(conn, "Received", StatusName.Processing);
});
}
public int ReceivedSucceededCount()
{
return UseConnection(conn =>
{
return GetNumberOfMessage(conn, "Received", StatusName.Succeeded);
});
}
private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName)
{
var sqlQuery = $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName = @state";
var count = connection.ExecuteScalar<int>(sqlQuery, new { state = statusName });
return count;
}
private T UseConnection<T>(Func<IDbConnection, T> action)
{
return _storage.UseConnection(action);
}
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName, string statusName)
{
var endDate = DateTime.Now;
var dates = new List<DateTime>();
for (var i = 0; i < 24; i++)
{
dates.Add(endDate);
endDate = endDate.AddHours(-1);
}
var keyMaps = dates.ToDictionary(x => x.ToString("yyyy-MM-dd-HH"), x => x);
return GetTimelineStats(connection, tableName, statusName, keyMaps);
}
private Dictionary<DateTime, int> GetTimelineStats(
IDbConnection connection,
string tableName,
string statusName,
IDictionary<string, DateTime> keyMaps)
{
string sqlQuery =
$@"
with aggr as (
select FORMAT(Added,'yyyy-MM-dd-HH') as [Key],
count(id) [Count]
from [{_options.Schema}].{tableName}
where StatusName = @statusName
group by FORMAT(Added,'yyyy-MM-dd-HH')
)
select [Key], [Count] from aggr with (nolock) where [Key] in @keys;";
var valuesMap = connection.Query(
sqlQuery,
new { keys = keyMaps.Keys, statusName = statusName })
.ToDictionary(x => (string)x.Key, x => (int)x.Count);
foreach (var key in keyMaps.Keys)
{
if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0);
}
var result = new Dictionary<DateTime, int>();
for (var i = 0; i < keyMaps.Count; i++)
{
var value = valuesMap[keyMaps.ElementAt(i).Key];
result.Add(keyMaps.ElementAt(i).Value, value);
}
return result;
}
}
}
\ No newline at end of file
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Dashboard;
using Microsoft.Extensions.Logging;
namespace DotNetCore.CAP.SqlServer
......@@ -10,6 +13,7 @@ namespace DotNetCore.CAP.SqlServer
{
private readonly SqlServerOptions _options;
private readonly ILogger _logger;
private readonly IDbConnection _existingConnection = null;
public SqlServerStorage(ILogger<SqlServerStorage> logger, SqlServerOptions options)
{
......@@ -17,6 +21,16 @@ namespace DotNetCore.CAP.SqlServer
_logger = logger;
}
public IStorageConnection GetConnection()
{
return new SqlServerStorageConnection(_options);
}
public IMonitoringApi GetMonitoringApi()
{
return new SqlServerMonitoringApi(this, _options);
}
public async Task InitializeAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested) return;
......@@ -83,5 +97,46 @@ CREATE TABLE [{schema}].[Published](
END;";
return batchSql;
}
internal T UseConnection<T>(Func<IDbConnection, T> func)
{
IDbConnection connection = null;
try
{
connection = CreateAndOpenConnection();
return func(connection);
}
finally
{
ReleaseConnection(connection);
}
}
internal IDbConnection CreateAndOpenConnection()
{
var connection = _existingConnection ?? new SqlConnection(_options.ConnectionString);
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
return connection;
}
internal bool IsExistingConnection(IDbConnection connection)
{
return connection != null && ReferenceEquals(connection, _existingConnection);
}
internal void ReleaseConnection(IDbConnection connection)
{
if (connection != null && !IsExistingConnection(connection))
{
connection.Dispose();
}
}
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using DotNetCore.CAP.Processor.States;
namespace DotNetCore.CAP.SqlServer
{
......@@ -65,6 +66,16 @@ OUTPUT DELETED.MessageId,DELETED.[MessageType];";
}
}
public bool ChangePublishedState(int messageId, IState state)
{
var sql = $"UPDATE [{_options.Schema}].[Published] SET Retries=Retries+1,StatusName = '{state.Name}' WHERE Id={messageId}";
using (var connection = new SqlConnection(_options.ConnectionString))
{
return connection.Execute(sql) > 0;
}
}
// CapReceviedMessage
public async Task StoreReceivedMessageAsync(CapReceivedMessage message)
......@@ -108,6 +119,16 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
}
}
public bool ChangeReceivedState(int messageId, IState state)
{
var sql = $"UPDATE [{_options.Schema}].[Received] SET Retries=Retries+1,StatusName = '{state.Name}' WHERE Id={messageId}";
using (var connection = new SqlConnection(_options.ConnectionString))
{
return connection.Execute(sql) > 0;
}
}
public void Dispose()
{
}
......@@ -139,5 +160,12 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
return new SqlServerFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction);
}
// ------------------------------------------
public List<string> GetRangeFromSet(string key, int startingFrom, int endingAt)
{
return new List<string> { "11", "22", "33" };
}
}
}
\ No newline at end of file
using System;
using DotNetCore.CAP;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Builder
......@@ -33,5 +34,24 @@ namespace Microsoft.AspNetCore.Builder
bootstrapper.BootstrapAsync();
return app;
}
public static IApplicationBuilder UseCapDashboard(
this IApplicationBuilder app,
string pathMatch = "/cap")
{
if (app == null) throw new ArgumentNullException(nameof(app));
if (pathMatch == null) throw new ArgumentNullException(nameof(pathMatch));
var marker = app.ApplicationServices.GetService<CapMarkerService>();
if (marker == null)
{
throw new InvalidOperationException("Add Cap must be called on the service collection.");
}
app.Map(new PathString(pathMatch), x => x.UseMiddleware<DashboardMiddleware>());
return app;
}
}
}
\ No newline at end of file
using System;
using System.Net;
using System.Threading.Tasks;
using DotNetCore.CAP.Dashboard;
using Microsoft.AspNetCore.Http;
namespace DotNetCore.CAP
{
public class DashboardMiddleware
{
private readonly DashboardOptions _options;
private readonly RequestDelegate _next;
private readonly IStorage _storage;
private readonly RouteCollection _routes;
public DashboardMiddleware(RequestDelegate next, DashboardOptions options, IStorage storage, RouteCollection routes)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_options = options ?? throw new ArgumentNullException(nameof(options));
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
_routes = routes ?? throw new ArgumentNullException(nameof(routes));
}
public Task Invoke(HttpContext httpContext)
{
var context = new CapDashboardContext(_storage, _options, httpContext);
var findResult = _routes.FindDispatcher(httpContext.Request.Path.Value);
if (findResult == null)
{
return _next.Invoke(httpContext);
}
foreach (var filter in _options.Authorization)
{
if (!filter.Authorize(context))
{
var isAuthenticated = httpContext.User?.Identity?.IsAuthenticated;
httpContext.Response.StatusCode = isAuthenticated == true
? (int)HttpStatusCode.Forbidden
: (int)HttpStatusCode.Unauthorized;
return Task.FromResult(0);
}
}
context.UriMatch = findResult.Item2;
return findResult.Item1.Dispatch(context);
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using DotNetCore.CAP.Dashboard;
namespace DotNetCore.CAP
{
public class DashboardOptions
{
public DashboardOptions()
{
AppPath = "/";
Authorization = new[] { new LocalRequestsOnlyAuthorizationFilter() };
StatsPollingInterval = 2000;
}
/// <summary>
/// The path for the Back To Site link. Set to <see langword="null" /> in order to hide the Back To Site link.
/// </summary>
public string AppPath { get; set; }
public IEnumerable<IDashboardAuthorizationFilter> Authorization { get; set; }
/// <summary>
/// The interval the /stats endpoint should be polled with.
/// </summary>
public int StatsPollingInterval { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace DotNetCore.CAP
{
using DotNetCore.CAP.Dashboard;
using Microsoft.Extensions.DependencyInjection;
internal sealed class DashboardOptionsExtension : ICapOptionsExtension
{
private readonly Action<DashboardOptions> _options;
public DashboardOptionsExtension(Action<DashboardOptions> option)
{
_options = option;
}
public void AddServices(IServiceCollection services)
{
var dashboardOptions = new DashboardOptions();
_options?.Invoke(dashboardOptions);
services.AddSingleton(dashboardOptions);
services.AddSingleton(DashboardRoutes.Routes);
}
}
}
namespace Microsoft.Extensions.DependencyInjection
{
using DotNetCore.CAP;
public static class CapOptionsExtensions
{
public static CapOptions UseDashboard(this CapOptions capOptions)
{
return capOptions.UseDashboard(opt => {});
}
public static CapOptions UseDashboard(this CapOptions capOptions, Action<DashboardOptions> options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
capOptions.RegisterExtension(new DashboardOptionsExtension(options));
return capOptions;
}
}
}
\ No newline at end of file
......@@ -21,9 +21,9 @@ namespace DotNetCore.CAP
public const int DefaultQueueProcessorCount = 2;
/// <summary>
/// Default successed message expriation timespan, in seconds.
/// Default succeeded message expriation timespan, in seconds.
/// </summary>
public const int DefaultSuccessMessageExpirationAfter = 3600;
public const int DefaultSucceedMessageExpirationAfter = 3600;
/// <summary>
/// Failed message retry waiting interval.
......@@ -34,7 +34,7 @@ namespace DotNetCore.CAP
{
PollingDelay = DefaultPollingDelay;
QueueProcessorCount = DefaultQueueProcessorCount;
SuccessedMessageExpiredAfter = DefaultSuccessMessageExpirationAfter;
SucceedMessageExpiredAfter = DefaultSucceedMessageExpirationAfter;
FailedMessageWaitingInterval = DefaultFailedMessageWaitingInterval;
Extensions = new List<ICapOptionsExtension>();
}
......@@ -52,10 +52,10 @@ namespace DotNetCore.CAP
public int QueueProcessorCount { get; set; }
/// <summary>
/// Sent or received successed message after timespan of due, then the message will be deleted at due time.
/// Sent or received succeed message after timespan of due, then the message will be deleted at due time.
/// Dafault is 3600 seconds.
/// </summary>
public int SuccessedMessageExpiredAfter { get; set; }
public int SucceedMessageExpiredAfter { get; set; }
/// <summary>
/// Failed messages polling delay time.
......
using System;
using System.Net;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
internal class BatchCommandDispatcher : IDashboardDispatcher
{
private readonly Action<DashboardContext, int> _command;
public BatchCommandDispatcher(Action<DashboardContext, int> command)
{
_command = command;
}
public async Task Dispatch(DashboardContext context)
{
var messageIds = await context.Request.GetFormValuesAsync("messages[]");
if (messageIds.Count == 0)
{
context.Response.StatusCode = 422;
return;
}
foreach (var messageId in messageIds)
{
var id = int.Parse(messageId);
_command(context, id);
}
context.Response.StatusCode = (int)HttpStatusCode.NoContent;
}
}
}
\ No newline at end of file
using System.Reflection;
namespace DotNetCore.CAP.Dashboard
{
internal class CombinedResourceDispatcher : EmbeddedResourceDispatcher
{
private readonly Assembly _assembly;
private readonly string _baseNamespace;
private readonly string[] _resourceNames;
public CombinedResourceDispatcher(
string contentType,
Assembly assembly,
string baseNamespace,
params string[] resourceNames) : base(contentType, assembly, null)
{
_assembly = assembly;
_baseNamespace = baseNamespace;
_resourceNames = resourceNames;
}
protected override void WriteResponse(DashboardResponse response)
{
foreach (var resourceName in _resourceNames)
{
WriteResource(
response,
_assembly,
$"{_baseNamespace}.{resourceName}");
}
}
}
}
\ No newline at end of file
using System;
using System.Net;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
internal class CommandDispatcher : IDashboardDispatcher
{
private readonly Func<DashboardContext, bool> _command;
public CommandDispatcher(Func<DashboardContext, bool> command)
{
_command = command;
}
public Task Dispatch(DashboardContext context)
{
var request = context.Request;
var response = context.Response;
if (!"POST".Equals(request.Method, StringComparison.OrdinalIgnoreCase))
{
response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
return Task.FromResult(false);
}
if (_command(context))
{
response.StatusCode = (int)HttpStatusCode.NoContent;
}
else
{
response.StatusCode = 422;
}
return Task.FromResult(true);
}
}
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
@charset "UTF-8";.jsonview{font-family:monospace;font-size:1.1em;white-space:pre-wrap}.jsonview .prop{font-weight:700;text-decoration:none;color:#000}.jsonview .null,.jsonview .undefined{color:red}.jsonview .bool,.jsonview .num{color:#00f}.jsonview .string{color:green;white-space:pre-wrap}.jsonview .string.multiline{display:inline-block;vertical-align:text-top}.jsonview .collapser{position:absolute;left:-1em;cursor:pointer}.jsonview .collapsible{transition:height 1.2s;transition:width 1.2s}.jsonview .collapsible.collapsed{height:.8em;width:1em;display:inline-block;overflow:hidden;margin:0}.jsonview .collapsible.collapsed:before{content:"…";width:1em;margin-left:.2em}.jsonview .collapser.collapsed{transform:rotate(0)}.jsonview .q{display:inline-block;width:0;color:transparent}.jsonview li{position:relative}.jsonview ul{list-style:none;margin:0 0 0 2em;padding:0}.jsonview h1{font-size:1.2em}
\ No newline at end of file
.rickshaw_graph .detail .item.left,.rickshaw_graph .detail .x_label.left{left:0}.rickshaw_graph .detail .item.right,.rickshaw_graph .detail .x_label.right{right:0}.rickshaw_graph .detail .item,.rickshaw_graph .detail .x_label,.rickshaw_graph .x_tick .title{font-family:Arial,sans-serif;font-size:12px;white-space:nowrap}.rickshaw_graph .detail{pointer-events:none;position:absolute;top:0;z-index:2;background:rgba(0,0,0,.1);bottom:0;width:1px;transition:opacity .25s linear;-moz-transition:opacity .25s linear;-o-transition:opacity .25s linear;-webkit-transition:opacity .25s linear}.rickshaw_graph .detail.inactive{opacity:0}.rickshaw_graph .detail .item.active{opacity:1}.rickshaw_graph .detail .x_label{border-radius:3px;padding:6px;opacity:.5;border:1px solid #e0e0e0;position:absolute;background:#fff}.rickshaw_graph .detail .item{position:absolute;z-index:2;border-radius:3px;padding:.25em;opacity:0;background:rgba(0,0,0,.4);color:#fff;border:1px solid rgba(0,0,0,.4);margin-left:1em;margin-right:1em;margin-top:-1em}.rickshaw_graph .detail .item.active{background:rgba(0,0,0,.8)}.rickshaw_graph .detail .item:after{position:absolute;display:block;width:0;height:0;content:"";border:5px solid transparent}.rickshaw_graph .detail .item.left:after{top:1em;left:-5px;margin-top:-5px;border-right-color:rgba(0,0,0,.8);border-left-width:0}.rickshaw_graph .detail .item.right:after{top:1em;right:-5px;margin-top:-5px;border-left-color:rgba(0,0,0,.8);border-right-width:0}.rickshaw_graph .detail .dot{width:4px;height:4px;margin-left:-3px;margin-top:-3.5px;border-radius:5px;position:absolute;box-shadow:0 0 2px rgba(0,0,0,.6);box-sizing:content-box;-moz-box-sizing:content-box;background:#fff;border-width:2px;border-style:solid;display:none;background-clip:padding-box}.rickshaw_graph .detail .dot.active{display:block}.rickshaw_graph{position:relative}.rickshaw_graph svg{display:block;overflow:hidden}.rickshaw_graph .x_tick{position:absolute;top:0;bottom:0;width:0;border-left:1px dotted rgba(0,0,0,.2);pointer-events:none}.rickshaw_graph .x_tick .title{position:absolute;opacity:.5;margin-left:3px;bottom:1px}.rickshaw_annotation_timeline{height:1px;border-top:1px solid #e0e0e0;margin-top:10px;position:relative}.rickshaw_annotation_timeline .annotation{position:absolute;height:6px;width:6px;margin-left:-2px;top:-3px;border-radius:5px;background-color:rgba(0,0,0,.25)}.rickshaw_graph .annotation_line{position:absolute;top:0;bottom:-6px;width:0;border-left:2px solid rgba(0,0,0,.3);display:none}.rickshaw_graph .annotation_line.active{display:block}.rickshaw_graph .annotation_range{background:rgba(0,0,0,.1);display:none;position:absolute;top:0;bottom:-6px}.rickshaw_graph .annotation_range.active{display:block}.rickshaw_graph .annotation_range.active.offscreen{display:none}.rickshaw_annotation_timeline .annotation .content{background:#fff;color:#000;opacity:.9;box-shadow:0 0 2px rgba(0,0,0,.8);border-radius:3px;position:relative;z-index:20;font-size:12px;padding:6px 8px 8px;top:18px;left:-11px;width:160px;display:none;cursor:pointer}.rickshaw_annotation_timeline .annotation .content:before{content:"\25b2";position:absolute;top:-11px;color:#fff;text-shadow:0 -1px 1px rgba(0,0,0,.8)}.rickshaw_annotation_timeline .annotation.active,.rickshaw_annotation_timeline .annotation:hover{background-color:rgba(0,0,0,.8);cursor:none}.rickshaw_annotation_timeline .annotation .content:hover{z-index:50}.rickshaw_annotation_timeline .annotation.active .content{display:block}.rickshaw_annotation_timeline .annotation:hover .content{display:block;z-index:50}.rickshaw_graph .x_axis_d3,.rickshaw_graph .y_axis{fill:none}.rickshaw_graph .x_ticks_d3 .tick,.rickshaw_graph .y_ticks .tick line{stroke:rgba(0,0,0,.16);stroke-width:2px;shape-rendering:crisp-edges;pointer-events:none}.rickshaw_graph .x_grid_d3 .tick,.rickshaw_graph .y_grid .tick{z-index:-1;stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:1 1}.rickshaw_graph .y_grid .tick[data-y-value="0"]{stroke-dasharray:1 0}.rickshaw_graph .x_grid_d3 path,.rickshaw_graph .y_grid path{fill:none;stroke:none}.rickshaw_graph .x_ticks_d3 path,.rickshaw_graph .y_ticks path{fill:none;stroke:grey}.rickshaw_graph .x_ticks_d3 text,.rickshaw_graph .y_ticks text{opacity:.5;font-size:12px;pointer-events:none}.rickshaw_graph .x_tick.glow .title,.rickshaw_graph .y_ticks.glow text{fill:#000;color:#000;text-shadow:-1px 1px 0 rgba(255,255,255,.1),1px -1px 0 rgba(255,255,255,.1),1px 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1),0 -1px 0 rgba(255,255,255,.1),1px 0 0 rgba(255,255,255,.1),-1px 0 0 rgba(255,255,255,.1),-1px -1px 0 rgba(255,255,255,.1)}.rickshaw_graph .x_tick.inverse .title,.rickshaw_graph .y_ticks.inverse text{fill:#fff;color:#fff;text-shadow:-1px 1px 0 rgba(0,0,0,.8),1px -1px 0 rgba(0,0,0,.8),1px 1px 0 rgba(0,0,0,.8),0 1px 0 rgba(0,0,0,.8),0 -1px 0 rgba(0,0,0,.8),1px 0 0 rgba(0,0,0,.8),-1px 0 0 rgba(0,0,0,.8),-1px -1px 0 rgba(0,0,0,.8)}.rickshaw_legend{font-family:Arial;font-size:12px;color:#fff;background:#404040;display:inline-block;padding:12px 5px;border-radius:2px;position:relative}.rickshaw_legend:hover{z-index:10}.rickshaw_legend .swatch{width:10px;height:10px;border:1px solid rgba(0,0,0,.2)}.rickshaw_legend .line{clear:both;line-height:140%;padding-right:15px}.rickshaw_legend .line .swatch{display:inline-block;margin-right:3px;border-radius:2px}.rickshaw_legend .label{margin:0;white-space:nowrap;display:inline;font-size:inherit;background-color:transparent;color:inherit;font-weight:400;line-height:normal;padding:0;text-shadow:none}.rickshaw_legend .action:hover{opacity:.6}.rickshaw_legend .action{margin-right:.2em;opacity:.2;cursor:pointer;font-size:14px}.rickshaw_legend .line.disabled{opacity:.4}.rickshaw_legend ul{list-style-type:none;padding:0;margin:2px;cursor:pointer}.rickshaw_legend li{padding:0 0 0 2px;min-width:80px;white-space:nowrap}.rickshaw_legend li:hover{background:rgba(255,255,255,.08);border-radius:3px}.rickshaw_legend li:active{background:rgba(255,255,255,.2);border-radius:3px}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
!function(e){var t,n,r,l,o;return o=["object","array","number","string","boolean","null"],r=function(){function t(e){null==e&&(e={}),this.options=e}return t.prototype.htmlEncode=function(e){return null!==e?e.toString().replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;"):""},t.prototype.jsString=function(e){return e=JSON.stringify(e).slice(1,-1),this.htmlEncode(e)},t.prototype.decorateWithSpan=function(e,t){return'<span class="'+t+'">'+this.htmlEncode(e)+"</span>"},t.prototype.valueToHTML=function(t,n){var r;if(null==n&&(n=0),r=Object.prototype.toString.call(t).match(/\s(.+)]/)[1].toLowerCase(),this.options.strict&&!e.inArray(r,o))throw new Error(""+r+" is not a valid JSON value type");return this[""+r+"ToHTML"].call(this,t,n)},t.prototype.nullToHTML=function(e){return this.decorateWithSpan("null","null")},t.prototype.undefinedToHTML=function(){return this.decorateWithSpan("undefined","undefined")},t.prototype.numberToHTML=function(e){return this.decorateWithSpan(e,"num")},t.prototype.stringToHTML=function(e){var t,n;return/^(http|https|file):\/\/[^\s]+$/i.test(e)?'<a href="'+this.htmlEncode(e)+'"><span class="q">"</span>'+this.jsString(e)+'<span class="q">"</span></a>':(t="",e=this.jsString(e),this.options.nl2br&&(n=/([^>\\r\\n]?)(\\r\\n|\\n\\r|\\r|\\n)/g,n.test(e)&&(t=" multiline",e=(e+"").replace(n,"$1<br />"))),'<span class="string'+t+'">"'+e+'"</span>')},t.prototype.booleanToHTML=function(e){return this.decorateWithSpan(e,"bool")},t.prototype.arrayToHTML=function(e,t){var n,r,l,o,i,s,a,p;for(null==t&&(t=0),r=!1,i="",o=e.length,l=a=0,p=e.length;p>a;l=++a)s=e[l],r=!0,i+="<li>"+this.valueToHTML(s,t+1),o>1&&(i+=","),i+="</li>",o--;return r?(n=0===t?"":" collapsible",'[<ul class="array level'+t+n+'">'+i+"</ul>]"):"[ ]"},t.prototype.objectToHTML=function(e,t){var n,r,l,o,i,s,a;null==t&&(t=0),r=!1,i="",o=0;for(s in e)o++;for(s in e)a=e[s],r=!0,l=this.options.escape?this.jsString(s):s,i+='<li><a class="prop" href="javascript:;"><span class="q">"</span>'+l+'<span class="q">"</span></a>: '+this.valueToHTML(a,t+1),o>1&&(i+=","),i+="</li>",o--;return r?(n=0===t?"":" collapsible",'{<ul class="obj level'+t+n+'">'+i+"</ul>}"):"{ }"},t.prototype.jsonToHTML=function(e){return'<div class="jsonview">'+this.valueToHTML(e)+"</div>"},t}(),"undefined"!=typeof module&&null!==module&&(module.exports=r),n=function(){function e(){}return e.bindEvent=function(e,t){var n;return e.firstChild.addEventListener("click",function(e){return function(n){return e.toggle(n.target.parentNode.firstChild,t)}}(this)),n=document.createElement("div"),n.className="collapser",n.innerHTML=t.collapsed?"+":"-",n.addEventListener("click",function(e){return function(n){return e.toggle(n.target,t)}}(this)),e.insertBefore(n,e.firstChild),t.collapsed?this.collapse(n):void 0},e.expand=function(e){var t,n;return n=this.collapseTarget(e),""!==n.style.display?(t=n.parentNode.getElementsByClassName("ellipsis")[0],n.parentNode.removeChild(t),n.style.display="",e.innerHTML="-"):void 0},e.collapse=function(e){var t,n;return n=this.collapseTarget(e),"none"!==n.style.display?(n.style.display="none",t=document.createElement("span"),t.className="ellipsis",t.innerHTML=" &hellip; ",n.parentNode.insertBefore(t,n),e.innerHTML="+"):void 0},e.toggle=function(e,t){var n,r,l,o,i,s;if(null==t&&(t={}),l=this.collapseTarget(e),n="none"===l.style.display?"expand":"collapse",t.recursive_collapser){for(r=e.parentNode.getElementsByClassName("collapser"),s=[],o=0,i=r.length;i>o;o++)e=r[o],s.push(this[n](e));return s}return this[n](e)},e.collapseTarget=function(e){var t,n;return n=e.parentNode.getElementsByClassName("collapsible"),n.length?t=n[0]:void 0},e}(),t=e,l={collapse:function(e){return"-"===e.innerHTML?n.collapse(e):void 0},expand:function(e){return"+"===e.innerHTML?n.expand(e):void 0},toggle:function(e){return n.toggle(e)}},t.fn.JSONView=function(){var e,o,i,s,a,p,c;return e=arguments,null!=l[e[0]]?(a=e[0],this.each(function(){var n,r;return n=t(this),null!=e[1]?(r=e[1],n.find(".jsonview .collapsible.level"+r).siblings(".collapser").each(function(){return l[a](this)})):n.find(".jsonview > ul > li .collapsible").siblings(".collapser").each(function(){return l[a](this)})})):(s=e[0],p=e[1]||{},o={collapsed:!1,nl2br:!1,recursive_collapser:!1,escape:!0,strict:!1},p=t.extend(o,p),i=new r(p),"[object String]"===Object.prototype.toString.call(s)&&(s=JSON.parse(s)),c=i.jsonToHTML(s),this.each(function(){var e,r,l,o,i,s;for(e=t(this),e.html(c),l=e[0].getElementsByClassName("collapsible"),s=[],o=0,i=l.length;i>o;o++)r=l[o],"LI"===r.parentNode.nodeName?s.push(n.bindEvent(r.parentNode,p)):s.push(void 0);return s}))}}(jQuery);
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
using System;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
namespace DotNetCore.CAP.Dashboard
{
public abstract class DashboardContext
{
protected DashboardContext(IStorage storage, DashboardOptions options)
{
if (storage == null) throw new ArgumentNullException(nameof(storage));
if (options == null) throw new ArgumentNullException(nameof(options));
Storage = storage;
Options = options;
}
public IStorage Storage { get; }
public DashboardOptions Options { get; }
public Match UriMatch { get; set; }
public DashboardRequest Request { get; protected set; }
public DashboardResponse Response { get; protected set; }
public IServiceProvider RequestServices { get; protected set; }
}
public sealed class CapDashboardContext : DashboardContext
{
public CapDashboardContext(
IStorage storage,
DashboardOptions options,
HttpContext httpContext)
: base(storage, options)
{
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
HttpContext = httpContext;
Request = new CapDashboardRequest(httpContext);
Response = new CapDashboardResponse(httpContext);
RequestServices = httpContext.RequestServices;
}
public HttpContext HttpContext { get; }
}
}
\ No newline at end of file
using System;
namespace DotNetCore.CAP.Dashboard
{
public class DashboardMetric
{
public DashboardMetric(string name, Func<RazorPage, Metric> func)
: this(name, name, func)
{
}
public DashboardMetric(string name, string title, Func<RazorPage, Metric> func)
{
Name = name;
Title = title;
Func = func;
}
public string Name { get; }
public Func<RazorPage, Metric> Func { get; }
public string Title { get; set; }
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using DotNetCore.CAP.Dashboard.Resources;
using DotNetCore.CAP.Internal;
using Microsoft.Extensions.DependencyInjection;
namespace DotNetCore.CAP.Dashboard
{
public static class DashboardMetrics
{
private static readonly Dictionary<string, DashboardMetric> Metrics = new Dictionary<string, DashboardMetric>();
static DashboardMetrics()
{
AddMetric(ServerCount);
AddMetric(SubscriberCount);
AddMetric(PublishedFailedCountOrNull);
AddMetric(ReceivedFailedCountOrNull);
AddMetric(PublishedProcessingCount);
AddMetric(ReceivedProcessingCount);
AddMetric(PublishedSucceededCount);
AddMetric(ReceivedSucceededCount);
AddMetric(PublishedFailedCount);
AddMetric(ReceivedFailedCount);
}
public static void AddMetric(DashboardMetric metric)
{
if (metric == null) throw new ArgumentNullException(nameof(metric));
lock (Metrics)
{
Metrics[metric.Name] = metric;
}
}
public static IEnumerable<DashboardMetric> GetMetrics()
{
lock (Metrics)
{
return Metrics.Values.ToList();
}
}
public static readonly DashboardMetric ServerCount = new DashboardMetric(
"servers:count",
"Metrics_Servers",
page => new Metric(page.Statistics.Servers.ToString("N0"))
{
Style = page.Statistics.Servers == 0 ? MetricStyle.Warning : MetricStyle.Default,
Highlighted = page.Statistics.Servers == 0,
Title = page.Statistics.Servers == 0
? "No active servers found. Jobs will not be processed."
: null
});
public static readonly DashboardMetric SubscriberCount = new DashboardMetric(
"retries:count",
"Metrics_Retries",
page =>
{
long retryCount;
var methodCache = page.RequestServices.GetService<MethodMatcherCache>();
retryCount = methodCache.GetCandidatesMethodsOfGroupNameGrouped().Sum(x => x.Value.Count);
return new Metric(retryCount.ToString("N0"))
{
Style = retryCount > 0 ? MetricStyle.Default : MetricStyle.Warning
};
});
//----------------------------------------------------
public static readonly DashboardMetric PublishedFailedCountOrNull = new DashboardMetric(
"published_failed:count-or-null",
"Metrics_FailedJobs",
page => page.Statistics.PublishedFailed > 0
? new Metric(page.Statistics.PublishedFailed.ToString("N0"))
{
Style = MetricStyle.Danger,
Highlighted = true,
Title = string.Format(Strings.Metrics_FailedCountOrNull, page.Statistics.PublishedFailed)
}
: null);
public static readonly DashboardMetric ReceivedFailedCountOrNull = new DashboardMetric(
"received_failed:count-or-null",
"Metrics_FailedJobs",
page => page.Statistics.ReceivedFailed > 0
? new Metric(page.Statistics.ReceivedFailed.ToString("N0"))
{
Style = MetricStyle.Danger,
Highlighted = true,
Title = string.Format(Strings.Metrics_FailedCountOrNull, page.Statistics.ReceivedFailed)
}
: null);
//----------------------------------------------------
public static readonly DashboardMetric PublishedProcessingCount = new DashboardMetric(
"published_processing:count",
"Metrics_ProcessingJobs",
page => new Metric(page.Statistics.PublishedProcessing.ToString("N0"))
{
Style = page.Statistics.PublishedProcessing > 0 ? MetricStyle.Warning : MetricStyle.Default
});
public static readonly DashboardMetric ReceivedProcessingCount = new DashboardMetric(
"received_processing:count",
"Metrics_ProcessingJobs",
page => new Metric(page.Statistics.ReceivedProcessing.ToString("N0"))
{
Style = page.Statistics.ReceivedProcessing > 0 ? MetricStyle.Warning : MetricStyle.Default
});
//----------------------------------------------------
public static readonly DashboardMetric PublishedSucceededCount = new DashboardMetric(
"published_succeeded:count",
"Metrics_SucceededJobs",
page => new Metric(page.Statistics.PublishedSucceeded.ToString("N0"))
{
IntValue = page.Statistics.PublishedSucceeded
});
public static readonly DashboardMetric ReceivedSucceededCount = new DashboardMetric(
"received_succeeded:count",
"Metrics_SucceededJobs",
page => new Metric(page.Statistics.ReceivedSucceeded.ToString("N0"))
{
IntValue = page.Statistics.ReceivedSucceeded
});
//----------------------------------------------------
public static readonly DashboardMetric PublishedFailedCount = new DashboardMetric(
"published_failed:count",
"Metrics_FailedJobs",
page => new Metric(page.Statistics.PublishedFailed.ToString("N0"))
{
IntValue = page.Statistics.PublishedFailed,
Style = page.Statistics.PublishedFailed > 0 ? MetricStyle.Danger : MetricStyle.Default,
Highlighted = page.Statistics.PublishedFailed > 0
});
public static readonly DashboardMetric ReceivedFailedCount = new DashboardMetric(
"received_failed:count",
"Metrics_FailedJobs",
page => new Metric(page.Statistics.ReceivedFailed.ToString("N0"))
{
IntValue = page.Statistics.ReceivedFailed,
Style = page.Statistics.ReceivedFailed > 0 ? MetricStyle.Danger : MetricStyle.Default,
Highlighted = page.Statistics.ReceivedFailed > 0
});
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace DotNetCore.CAP.Dashboard
{
public abstract class DashboardRequest
{
public abstract string Method { get; }
public abstract string Path { get; }
public abstract string PathBase { get; }
public abstract string LocalIpAddress { get; }
public abstract string RemoteIpAddress { get; }
public abstract string GetQuery(string key);
public abstract Task<IList<string>> GetFormValuesAsync(string key);
}
internal sealed class CapDashboardRequest : DashboardRequest
{
private readonly HttpContext _context;
public CapDashboardRequest(HttpContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
_context = context;
}
public override string Method => _context.Request.Method;
public override string Path => _context.Request.Path.Value;
public override string PathBase => _context.Request.PathBase.Value;
public override string LocalIpAddress => _context.Connection.LocalIpAddress.ToString();
public override string RemoteIpAddress => _context.Connection.RemoteIpAddress.ToString();
public override string GetQuery(string key) => _context.Request.Query[key];
public override async Task<IList<string>> GetFormValuesAsync(string key)
{
var form = await _context.Request.ReadFormAsync();
return form[key];
}
}
}
\ No newline at end of file
using System;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace DotNetCore.CAP.Dashboard
{
public abstract class DashboardResponse
{
public abstract string ContentType { get; set; }
public abstract int StatusCode { get; set; }
public abstract Stream Body { get; }
public abstract void SetExpire(DateTimeOffset? value);
public abstract Task WriteAsync(string text);
}
internal sealed class CapDashboardResponse : DashboardResponse
{
private readonly HttpContext _context;
public CapDashboardResponse(HttpContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
_context = context;
}
public override string ContentType
{
get { return _context.Response.ContentType; }
set { _context.Response.ContentType = value; }
}
public override int StatusCode
{
get { return _context.Response.StatusCode; }
set { _context.Response.StatusCode = value; }
}
public override Stream Body => _context.Response.Body;
public override Task WriteAsync(string text)
{
return _context.Response.WriteAsync(text);
}
public override void SetExpire(DateTimeOffset? value)
{
_context.Response.Headers["Expires"] = value?.ToString("r", CultureInfo.InvariantCulture);
}
}
}
\ No newline at end of file
using System.Reflection;
using DotNetCore.CAP.Dashboard.Pages;
using DotNetCore.CAP.Processor.States;
namespace DotNetCore.CAP.Dashboard
{
public static class DashboardRoutes
{
private static readonly string[] Javascripts =
{
"jquery-2.1.4.min.js",
"bootstrap.min.js",
"moment.min.js",
"moment-with-locales.min.js",
"d3.min.js",
"d3.layout.min.js",
"rickshaw.min.js",
"jsonview.min.js",
"cap.js"
};
private static readonly string[] Stylesheets =
{
"bootstrap.min.css",
"rickshaw.min.css",
"jsonview.min.css",
"cap.css"
};
static DashboardRoutes()
{
Routes = new RouteCollection();
Routes.AddRazorPage("/", x => new HomePage());
Routes.Add("/stats", new JsonStats());
#region Embedded static content
Routes.Add("/js[0-9]+", new CombinedResourceDispatcher(
"application/javascript",
GetExecutingAssembly(),
GetContentFolderNamespace("js"),
Javascripts));
Routes.Add("/css[0-9]+", new CombinedResourceDispatcher(
"text/css",
GetExecutingAssembly(),
GetContentFolderNamespace("css"),
Stylesheets));
Routes.Add("/fonts/glyphicons-halflings-regular/eot", new EmbeddedResourceDispatcher(
"application/vnd.ms-fontobject",
GetExecutingAssembly(),
GetContentResourceName("fonts", "glyphicons-halflings-regular.eot")));
Routes.Add("/fonts/glyphicons-halflings-regular/svg", new EmbeddedResourceDispatcher(
"image/svg+xml",
GetExecutingAssembly(),
GetContentResourceName("fonts", "glyphicons-halflings-regular.svg")));
Routes.Add("/fonts/glyphicons-halflings-regular/ttf", new EmbeddedResourceDispatcher(
"application/octet-stream",
GetExecutingAssembly(),
GetContentResourceName("fonts", "glyphicons-halflings-regular.ttf")));
Routes.Add("/fonts/glyphicons-halflings-regular/woff", new EmbeddedResourceDispatcher(
"font/woff",
GetExecutingAssembly(),
GetContentResourceName("fonts", "glyphicons-halflings-regular.woff")));
Routes.Add("/fonts/glyphicons-halflings-regular/woff2", new EmbeddedResourceDispatcher(
"font/woff2",
GetExecutingAssembly(),
GetContentResourceName("fonts", "glyphicons-halflings-regular.woff2")));
#endregion Embedded static content
#region Razor pages and commands
Routes.AddJsonResult("/published/message/(?<Id>.+)", x =>
{
var id = int.Parse(x.UriMatch.Groups["Id"].Value);
var message = x.Storage.GetConnection().GetPublishedMessageAsync(id).GetAwaiter().GetResult();
return message.Content;
});
Routes.AddJsonResult("/received/message/(?<Id>.+)", x =>
{
var id = int.Parse(x.UriMatch.Groups["Id"].Value);
var message = x.Storage.GetConnection().GetReceivedMessageAsync(id).GetAwaiter().GetResult();
return message.Content;
});
Routes.AddPublishBatchCommand(
"/published/requeue",
(client, messageId) => client.Storage.GetConnection().ChangePublishedState(messageId, new ScheduledState()));
Routes.AddPublishBatchCommand(
"/received/requeue",
(client, messageId) => client.Storage.GetConnection().ChangeReceivedState(messageId, new ScheduledState()));
Routes.AddRazorPage(
"/published/(?<StatusName>.+)",
x => new PublishedPage(x.Groups["StatusName"].Value));
Routes.AddRazorPage(
"/received/(?<StatusName>.+)",
x => new ReceivedPage(x.Groups["StatusName"].Value));
Routes.AddRazorPage("/subscribers", x => new SubscriberPage());
//Routes.AddRazorPage("/servers", x => new ServersPage());
//Routes.AddRazorPage("/retries", x => new RetriesPage());
#endregion Razor pages and commands
}
public static RouteCollection Routes { get; }
internal static string GetContentFolderNamespace(string contentFolder)
{
return $"{typeof(DashboardRoutes).Namespace}.Content.{contentFolder}";
}
internal static string GetContentResourceName(string contentFolder, string resourceName)
{
return $"{GetContentFolderNamespace(contentFolder)}.{resourceName}";
}
private static EnqueuedState CreateEnqueuedState()
{
return new EnqueuedState();
}
private static Assembly GetExecutingAssembly()
{
return typeof(DashboardRoutes).GetTypeInfo().Assembly;
}
}
}
\ No newline at end of file
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
internal class EmbeddedResourceDispatcher : IDashboardDispatcher
{
private readonly Assembly _assembly;
private readonly string _resourceName;
private readonly string _contentType;
public EmbeddedResourceDispatcher(
string contentType,
Assembly assembly,
string resourceName)
{
if (contentType == null) throw new ArgumentNullException(nameof(contentType));
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
_assembly = assembly;
_resourceName = resourceName;
_contentType = contentType;
}
public Task Dispatch(DashboardContext context)
{
context.Response.ContentType = _contentType;
context.Response.SetExpire(DateTimeOffset.Now.AddYears(1));
WriteResponse(context.Response);
return Task.FromResult(true);
}
protected virtual void WriteResponse(DashboardResponse response)
{
WriteResource(response, _assembly, _resourceName);
}
protected void WriteResource(DashboardResponse response, Assembly assembly, string resourceName)
{
using (var inputStream = assembly.GetManifestResourceStream(resourceName))
{
if (inputStream == null)
{
throw new ArgumentException($@"Resource with name {resourceName} not found in assembly {assembly}.");
}
inputStream.CopyTo(response.Body);
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using DotNetCore.CAP.Dashboard.Pages;
using DotNetCore.CAP.Dashboard.Resources;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Models;
using Microsoft.Extensions.Internal;
namespace DotNetCore.CAP.Dashboard
{
public class HtmlHelper
{
private readonly RazorPage _page;
public HtmlHelper(RazorPage page)
{
if (page == null) throw new ArgumentNullException(nameof(page));
_page = page;
}
public NonEscapedString Breadcrumbs(string title, IDictionary<string, string> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
return RenderPartial(new Breadcrumbs(title, items));
}
public NonEscapedString MessagesSidebar(MessageType type)
{
if (type == MessageType.Publish)
{
return SidebarMenu(MessagesSidebarMenu.PublishedItems);
}
else
{
return SidebarMenu(MessagesSidebarMenu.ReceivedItems);
}
}
public NonEscapedString SidebarMenu(IEnumerable<Func<RazorPage, MenuItem>> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
return RenderPartial(new SidebarMenu(items));
}
public NonEscapedString BlockMetric(DashboardMetric metric)
{
if (metric == null) throw new ArgumentNullException(nameof(metric));
return RenderPartial(new BlockMetric(metric));
}
public NonEscapedString InlineMetric(DashboardMetric metric)
{
if (metric == null) throw new ArgumentNullException(nameof(metric));
return RenderPartial(new InlineMetric(metric));
}
public NonEscapedString Paginator(Pager pager)
{
if (pager == null) throw new ArgumentNullException(nameof(pager));
return RenderPartial(new Paginator(pager));
}
public NonEscapedString PerPageSelector(Pager pager)
{
if (pager == null) throw new ArgumentNullException(nameof(pager));
return RenderPartial(new PerPageSelector(pager));
}
public NonEscapedString RenderPartial(RazorPage partialPage)
{
partialPage.Assign(_page);
return new NonEscapedString(partialPage.ToString());
}
public NonEscapedString Raw(string value)
{
return new NonEscapedString(value);
}
public NonEscapedString StateLabel(string stateName)
{
if (String.IsNullOrWhiteSpace(stateName))
{
return Raw($"<em>{Strings.Common_NoState}</em>");
}
return Raw($"<span class=\"label label-default\" style=\"background-color: {JobHistoryRenderer.GetForegroundStateColor(stateName)};\">{stateName}</span>");
}
public NonEscapedString RelativeTime(DateTime value)
{
return Raw($"<span data-moment=\"{Helper.ToTimestamp(value)}\">{value}</span>");
}
public NonEscapedString MomentTitle(DateTime time, string value)
{
return Raw($"<span data-moment-title=\"{Helper.ToTimestamp(time)}\">{value}</span>");
}
public NonEscapedString LocalTime(DateTime value)
{
return Raw($"<span data-moment-local=\"{Helper.ToTimestamp(value)}\">{value}</span>");
}
public string ToHumanDuration(TimeSpan? duration, bool displaySign = true)
{
if (duration == null) return null;
var builder = new StringBuilder();
if (displaySign)
{
builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+");
}
duration = duration.Value.Duration();
if (duration.Value.Days > 0)
{
builder.Append($"{duration.Value.Days}d ");
}
if (duration.Value.Hours > 0)
{
builder.Append($"{duration.Value.Hours}h ");
}
if (duration.Value.Minutes > 0)
{
builder.Append($"{duration.Value.Minutes}m ");
}
if (duration.Value.TotalHours < 1)
{
if (duration.Value.Seconds > 0)
{
builder.Append(duration.Value.Seconds);
if (duration.Value.Milliseconds > 0)
{
builder.Append($".{duration.Value.Milliseconds.ToString().PadLeft(3, '0')}");
}
builder.Append("s ");
}
else
{
if (duration.Value.Milliseconds > 0)
{
builder.Append($"{duration.Value.Milliseconds}ms ");
}
}
}
if (builder.Length <= 1)
{
builder.Append(" <1ms ");
}
builder.Remove(builder.Length - 1, 1);
return builder.ToString();
}
public string FormatProperties(IDictionary<string, string> properties)
{
return String.Join(", ", properties.Select(x => $"{x.Key}: \"{x.Value}\""));
}
public NonEscapedString QueueLabel(string queue)
{
var label = queue != null
? $"<a class=\"text-uppercase\" href=\"{_page.Url.Queue(queue)}\">{queue}</a>"
: $"<span class=\"label label-danger\"><i>{Strings.Common_Unknown}</i></span>";
return new NonEscapedString(label);
}
public NonEscapedString ServerId(string serverId)
{
var parts = serverId.Split(':');
var shortenedId = parts.Length > 1
? String.Join(":", parts.Take(parts.Length - 1))
: serverId;
return new NonEscapedString(
$"<span class=\"labe label-defult text-uppercase\" title=\"{serverId}\">{shortenedId}</span>");
}
public NonEscapedString MethodEscaped(MethodInfo method)
{
var outputString = string.Empty;
var @public = WrapKeyword("public");
var @async = string.Empty;
var @return = string.Empty;
var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(method.ReturnType, out var coercedAwaitableInfo);
if (isAwaitable)
{
@async = WrapKeyword("async");
var asyncResultType = coercedAwaitableInfo.AwaitableInfo.ResultType;
@return = WrapType("Task") + WrapIdentifier("<") + WrapType(asyncResultType) + WrapIdentifier(">");
}
else
{
@return = WrapType(method.ReturnType);
}
var @name = method.Name;
string paramType = null;
string paramName = null;
string paramString = string.Empty;
var @params = method.GetParameters();
if (@params.Length == 1)
{
var firstParam = @params[0];
var firstParamType = firstParam.ParameterType;
paramType = WrapType(firstParamType);
paramName = firstParam.Name;
}
if (paramType == null)
{
paramString = "();";
}
else
{
paramString = $"({paramType} {paramName});";
}
outputString = @public + " " + (string.IsNullOrEmpty(@async) ? "" : @async + " ") + @return + " " + @name + paramString;
return new NonEscapedString(outputString);
}
private string WrapType(Type type)
{
if (type == null)
{
return string.Empty;
}
if (type.Name == "Void")
{
return WrapKeyword(type.Name.ToLower());
}
if (Helper.IsComplexType(type))
{
return WrapType(type.Name);
}
if (type.IsPrimitive || type.Equals(typeof(string)) || type.Equals(typeof(decimal)))
{
return WrapKeyword(type.Name.ToLower());
}
else
{
return WrapType(type.Name);
}
}
private string WrapIdentifier(string value)
{
return value;
}
private string WrapKeyword(string value)
{
return Span("keyword", value);
}
private string WrapType(string value)
{
return Span("type", value);
}
private string WrapString(string value)
{
return Span("string", value);
}
private string Span(string @class, string value)
{
return $"<span class=\"{@class}\">{value}</span>";
}
public NonEscapedString StackTrace(string stackTrace)
{
try
{
//return new NonEscapedString(StackTraceFormatter.FormatHtml(stackTrace, StackTraceHtmlFragments));
return new NonEscapedString(stackTrace);
}
catch (RegexMatchTimeoutException)
{
return new NonEscapedString(HtmlEncode(stackTrace));
}
}
public string HtmlEncode(string text)
{
return WebUtility.HtmlEncode(text);
}
}
}
\ No newline at end of file
namespace DotNetCore.CAP.Dashboard
{
public interface IDashboardAuthorizationFilter
{
bool Authorize(DashboardContext context);
}
}
\ No newline at end of file
using System.Threading.Tasks;
namespace DotNetCore.CAP.Dashboard
{
public interface IDashboardDispatcher
{
Task Dispatch(DashboardContext context);
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using DotNetCore.CAP.Dashboard.Monitoring;
using DotNetCore.CAP.Models;
namespace DotNetCore.CAP.Dashboard
{
public interface IMonitoringApi
{
StatisticsDto GetStatistics();
IList<MessageDto> Messages(MessageQueryDto queryDto);
int PublishedFailedCount();
int PublishedProcessingCount();
int PublishedSucceededCount();
int ReceivedFailedCount();
int ReceivedProcessingCount();
int ReceivedSucceededCount();
IDictionary<DateTime, int> HourlySucceededJobs(MessageType type);
IDictionary<DateTime, int> HourlyFailedJobs(MessageType type);
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
using DotNetCore.CAP.Infrastructure;
using DotNetCore.CAP.Processor.States;
using Newtonsoft.Json;
namespace DotNetCore.CAP.Dashboard
{
public static class JobHistoryRenderer
{
private static readonly IDictionary<string, Func<HtmlHelper, IDictionary<string, string>, NonEscapedString>>
Renderers = new Dictionary<string, Func<HtmlHelper, IDictionary<string, string>, NonEscapedString>>();
private static readonly IDictionary<string, string> BackgroundStateColors
= new Dictionary<string, string>();
private static readonly IDictionary<string, string> ForegroundStateColors
= new Dictionary<string, string>();
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static JobHistoryRenderer()
{
Register(SucceededState.StateName, SucceededRenderer);
Register(FailedState.StateName, FailedRenderer);
Register(ProcessingState.StateName, ProcessingRenderer);
BackgroundStateColors.Add(EnqueuedState.StateName, "#F5F5F5");
BackgroundStateColors.Add(SucceededState.StateName, "#EDF7ED");
BackgroundStateColors.Add(FailedState.StateName, "#FAEBEA");
BackgroundStateColors.Add(ProcessingState.StateName, "#FCEFDC");
BackgroundStateColors.Add(ScheduledState.StateName, "#E0F3F8");
ForegroundStateColors.Add(EnqueuedState.StateName, "#999");
ForegroundStateColors.Add(SucceededState.StateName, "#5cb85c");
ForegroundStateColors.Add(FailedState.StateName, "#d9534f");
ForegroundStateColors.Add(ProcessingState.StateName, "#f0ad4e");
ForegroundStateColors.Add(ScheduledState.StateName, "#5bc0de");
}
public static void AddBackgroundStateColor(string stateName, string color)
{
BackgroundStateColors.Add(stateName, color);
}
public static string GetBackgroundStateColor(string stateName)
{
if (stateName == null || !BackgroundStateColors.ContainsKey(stateName))
{
return "inherit";
}
return BackgroundStateColors[stateName];
}
public static void AddForegroundStateColor(string stateName, string color)
{
ForegroundStateColors.Add(stateName, color);
}
public static string GetForegroundStateColor(string stateName)
{
if (stateName == null || !ForegroundStateColors.ContainsKey(stateName))
{
return "inherit";
}
return ForegroundStateColors[stateName];
}
public static void Register(string state, Func<HtmlHelper, IDictionary<string, string>, NonEscapedString> renderer)
{
if (!Renderers.ContainsKey(state))
{
Renderers.Add(state, renderer);
}
else
{
Renderers[state] = renderer;
}
}
public static bool Exists(string state)
{
return Renderers.ContainsKey(state);
}
public static NonEscapedString RenderHistory(
this HtmlHelper helper,
string state, IDictionary<string, string> properties)
{
var renderer = Renderers.ContainsKey(state)
? Renderers[state]
: DefaultRenderer;
return renderer?.Invoke(helper, properties);
}
public static NonEscapedString NullRenderer(HtmlHelper helper, IDictionary<string, string> properties)
{
return null;
}
public static NonEscapedString DefaultRenderer(HtmlHelper helper, IDictionary<string, string> stateData)
{
if (stateData == null || stateData.Count == 0) return null;
var builder = new StringBuilder();
builder.Append("<dl class=\"dl-horizontal\">");
foreach (var item in stateData)
{
builder.Append($"<dt>{item.Key}</dt>");
builder.Append($"<dd>{item.Value}</dd>");
}
builder.Append("</dl>");
return new NonEscapedString(builder.ToString());
}
public static NonEscapedString SucceededRenderer(HtmlHelper html, IDictionary<string, string> stateData)
{
var builder = new StringBuilder();
builder.Append("<dl class=\"dl-horizontal\">");
var itemsAdded = false;
if (stateData.ContainsKey("Latency"))
{
var latency = TimeSpan.FromMilliseconds(long.Parse(stateData["Latency"]));
builder.Append($"<dt>Latency:</dt><dd>{html.ToHumanDuration(latency, false)}</dd>");
itemsAdded = true;
}
if (stateData.ContainsKey("PerformanceDuration"))
{
var duration = TimeSpan.FromMilliseconds(long.Parse(stateData["PerformanceDuration"]));
builder.Append($"<dt>Duration:</dt><dd>{html.ToHumanDuration(duration, false)}</dd>");
itemsAdded = true;
}
if (stateData.ContainsKey("Result") && !String.IsNullOrWhiteSpace(stateData["Result"]))
{
var result = stateData["Result"];
builder.Append($"<dt>Result:</dt><dd>{System.Net.WebUtility.HtmlEncode(result)}</dd>");
itemsAdded = true;
}
builder.Append("</dl>");
if (!itemsAdded) return null;
return new NonEscapedString(builder.ToString());
}
private static NonEscapedString FailedRenderer(HtmlHelper html, IDictionary<string, string> stateData)
{
var stackTrace = html.StackTrace(stateData["ExceptionDetails"]).ToString();
return new NonEscapedString(
$"<h4 class=\"exception-type\">{stateData["ExceptionType"]}</h4><p class=\"text-muted\">{stateData["ExceptionMessage"]}</p>{"<pre class=\"stack-trace\">" + stackTrace + "</pre>"}");
}
private static NonEscapedString ProcessingRenderer(HtmlHelper helper, IDictionary<string, string> stateData)
{
var builder = new StringBuilder();
builder.Append("<dl class=\"dl-horizontal\">");
string serverId = null;
if (stateData.ContainsKey("ServerId"))
{
serverId = stateData["ServerId"];
}
else if (stateData.ContainsKey("ServerName"))
{
serverId = stateData["ServerName"];
}
if (serverId != null)
{
builder.Append("<dt>Server:</dt>");
builder.Append($"<dd>{helper.ServerId(serverId)}</dd>");
}
if (stateData.ContainsKey("WorkerId"))
{
builder.Append("<dt>Worker:</dt>");
builder.Append($"<dd>{stateData["WorkerId"].Substring(0, 8)}</dd>");
}
else if (stateData.ContainsKey("WorkerNumber"))
{
builder.Append("<dt>Worker:</dt>");
builder.Append($"<dd>#{stateData["WorkerNumber"]}</dd>");
}
builder.Append("</dl>");
return new NonEscapedString(builder.ToString());
}
}
}
\ No newline at end of file
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
namespace DotNetCore.CAP.Dashboard
{
internal class JsonDispatcher : IDashboardDispatcher
{
private readonly Func<DashboardContext, object> _command;
private readonly Func<DashboardContext, string> _jsonCommand;
public JsonDispatcher(Func<DashboardContext, object> command)
{
_command = command;
}
public JsonDispatcher(Func<DashboardContext, string> command)
{
_jsonCommand = command;
}
public async Task Dispatch(DashboardContext context)
{
var request = context.Request;
var response = context.Response;
string serialized = null;
if (_command != null)
{
object result = _command(context);
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new JsonConverter[] { new StringEnumConverter { CamelCaseText = true } }
};
serialized = JsonConvert.SerializeObject(result, settings);
}
if (_jsonCommand != null)
{
serialized = _jsonCommand(context);
}
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(serialized);
}
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment