@@ -20,7 +20,7 @@ namespace Sample.Kafka.SqlServer | |||
x.UseDiscovery(d => | |||
{ | |||
d.DiscoveryServerHostName = "localhost"; | |||
d.DiscoveryServerProt = 8500; | |||
d.DiscoveryServerPort = 8500; | |||
d.CurrentNodeHostName = "localhost"; | |||
d.CurrentNodePort = 5820; | |||
d.NodeName = "CAP 2号节点"; | |||
@@ -40,8 +40,6 @@ namespace Sample.Kafka.SqlServer | |||
app.UseMvc(); | |||
app.UseCap(); | |||
app.UseCapDashboard(); | |||
} | |||
} | |||
} |
@@ -27,7 +27,7 @@ namespace Sample.RabbitMQ.MySql | |||
x.UseDiscovery(d => | |||
{ | |||
d.DiscoveryServerHostName = "localhost"; | |||
d.DiscoveryServerProt = 8500; | |||
d.DiscoveryServerPort = 8500; | |||
d.CurrentNodeHostName = "localhost"; | |||
d.CurrentNodePort = 5800; | |||
d.NodeName = "CAP 1号节点"; | |||
@@ -45,7 +45,6 @@ namespace Sample.RabbitMQ.MySql | |||
app.UseMvc(); | |||
app.UseCap(); | |||
app.UseCapDashboard(); | |||
} | |||
} | |||
} |
@@ -26,7 +26,7 @@ namespace Sample.RabbitMQ.PostgreSql | |||
x.UseDiscovery(d => | |||
{ | |||
d.DiscoveryServerHostName = "localhost"; | |||
d.DiscoveryServerProt = 8500; | |||
d.DiscoveryServerPort = 8500; | |||
d.CurrentNodeHostName = "localhost"; | |||
d.CurrentNodePort = 5800; | |||
d.NodeName = "CAP一号节点"; | |||
@@ -29,7 +29,7 @@ namespace Sample.RabbitMQ.SqlServer | |||
x.UseDiscovery(d => | |||
{ | |||
d.DiscoveryServerHostName = "localhost"; | |||
d.DiscoveryServerProt = 8500; | |||
d.DiscoveryServerPort = 8500; | |||
d.CurrentNodeHostName = "localhost"; | |||
d.CurrentNodePort = 5800; | |||
d.NodeName = "CAP一号节点"; | |||
@@ -10,14 +10,6 @@ namespace DotNetCore.CAP | |||
/// </summary> | |||
public class KafkaOptions | |||
{ | |||
private IEnumerable<KeyValuePair<string, object>> _kafkaConfig; | |||
public KafkaOptions() | |||
{ | |||
MainConfig = new Dictionary<string, object>(); | |||
} | |||
/// <summary> | |||
/// librdkafka configuration parameters (refer to https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md). | |||
/// <para> | |||
@@ -26,22 +18,28 @@ namespace DotNetCore.CAP | |||
/// </summary> | |||
public readonly IDictionary<string, object> MainConfig; | |||
private IEnumerable<KeyValuePair<string, object>> _kafkaConfig; | |||
public KafkaOptions() | |||
{ | |||
MainConfig = new Dictionary<string, object>(); | |||
} | |||
/// <summary> | |||
/// The `bootstrap.servers` item config of <see cref="MainConfig"/>. | |||
/// The `bootstrap.servers` item config of <see cref="MainConfig" />. | |||
/// <para> | |||
/// Initial list of brokers as a CSV list of broker host or host:port. | |||
/// </para> | |||
/// </summary> | |||
public string Servers { get; set; } | |||
internal IEnumerable<KeyValuePair<string, object>> AskafkaConfig() | |||
internal IEnumerable<KeyValuePair<string, object>> AsKafkaConfig() | |||
{ | |||
if (_kafkaConfig == null) | |||
{ | |||
if (string.IsNullOrWhiteSpace(Servers)) | |||
{ | |||
throw new ArgumentNullException(nameof(Servers)); | |||
} | |||
MainConfig["bootstrap.servers"] = Servers; | |||
MainConfig["queue.buffering.max.ms"] = "10"; | |||
@@ -9,18 +9,17 @@ namespace Microsoft.Extensions.DependencyInjection | |||
/// <summary> | |||
/// Configuration to use kafka in CAP. | |||
/// </summary> | |||
/// <param name="options">CAP configuration options</param> | |||
/// <param name="bootstrapServers">Kafka bootstrap server urls.</param> | |||
public static CapOptions UseKafka(this CapOptions options, string bootstrapServers) | |||
{ | |||
return options.UseKafka(opt => | |||
{ | |||
opt.Servers = bootstrapServers; | |||
}); | |||
return options.UseKafka(opt => { opt.Servers = bootstrapServers; }); | |||
} | |||
/// <summary> | |||
/// Configuration to use kafka in CAP. | |||
/// </summary> | |||
/// <param name="options">CAP configuration options</param> | |||
/// <param name="configure">Provides programmatic configuration for the kafka .</param> | |||
/// <returns></returns> | |||
public static CapOptions UseKafka(this CapOptions options, Action<KafkaOptions> configure) | |||
@@ -13,12 +13,6 @@ namespace DotNetCore.CAP.Kafka | |||
private readonly KafkaOptions _kafkaOptions; | |||
private Consumer<Null, string> _consumerClient; | |||
public event EventHandler<MessageContext> OnMessageReceieved; | |||
public event EventHandler<string> OnError; | |||
public IDeserializer<string> StringDeserializer { get; set; } | |||
public KafkaConsumerClient(string groupId, KafkaOptions options) | |||
{ | |||
_groupId = groupId; | |||
@@ -26,15 +20,19 @@ namespace DotNetCore.CAP.Kafka | |||
StringDeserializer = new StringDeserializer(Encoding.UTF8); | |||
} | |||
public IDeserializer<string> StringDeserializer { get; set; } | |||
public event EventHandler<MessageContext> OnMessageReceived; | |||
public event EventHandler<string> OnError; | |||
public void Subscribe(IEnumerable<string> topics) | |||
{ | |||
if (topics == null) | |||
throw new ArgumentNullException(nameof(topics)); | |||
if (_consumerClient == null) | |||
{ | |||
InitKafkaClient(); | |||
} | |||
//_consumerClient.Assign(topics.Select(x=> new TopicPartition(x, 0))); | |||
_consumerClient.Subscribe(topics); | |||
@@ -65,7 +63,7 @@ namespace DotNetCore.CAP.Kafka | |||
{ | |||
_kafkaOptions.MainConfig["group.id"] = _groupId; | |||
var config = _kafkaOptions.AskafkaConfig(); | |||
var config = _kafkaOptions.AsKafkaConfig(); | |||
_consumerClient = new Consumer<Null, string>(config, null, StringDeserializer); | |||
_consumerClient.OnMessage += ConsumerClient_OnMessage; | |||
@@ -81,7 +79,7 @@ namespace DotNetCore.CAP.Kafka | |||
Content = e.Value | |||
}; | |||
OnMessageReceieved?.Invoke(sender, message); | |||
OnMessageReceived?.Invoke(sender, message); | |||
} | |||
private void ConsumerClient_OnError(object sender, Error e) | |||
@@ -9,8 +9,8 @@ namespace DotNetCore.CAP.Kafka | |||
{ | |||
internal class PublishQueueExecutor : BasePublishQueueExecutor | |||
{ | |||
private readonly ILogger _logger; | |||
private readonly KafkaOptions _kafkaOptions; | |||
private readonly ILogger _logger; | |||
public PublishQueueExecutor( | |||
CapOptions options, | |||
@@ -27,7 +27,7 @@ namespace DotNetCore.CAP.Kafka | |||
{ | |||
try | |||
{ | |||
var config = _kafkaOptions.AskafkaConfig(); | |||
var config = _kafkaOptions.AsKafkaConfig(); | |||
var contentBytes = Encoding.UTF8.GetBytes(content); | |||
using (var producer = new Producer(config)) | |||
{ | |||
@@ -39,19 +39,17 @@ namespace DotNetCore.CAP.Kafka | |||
return Task.FromResult(OperateResult.Success); | |||
} | |||
else | |||
return Task.FromResult(OperateResult.Failed(new OperateError | |||
{ | |||
return Task.FromResult(OperateResult.Failed(new OperateError | |||
{ | |||
Code = message.Error.Code.ToString(), | |||
Description = message.Error.Reason | |||
})); | |||
} | |||
Code = message.Error.Code.ToString(), | |||
Description = message.Error.Reason | |||
})); | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError($"kafka topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}"); | |||
_logger.LogError( | |||
$"kafka topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}"); | |||
return Task.FromResult(OperateResult.Failed(ex)); | |||
} | |||
@@ -6,7 +6,7 @@ namespace DotNetCore.CAP | |||
public class EFOptions | |||
{ | |||
/// <summary> | |||
/// EF dbcontext type. | |||
/// EF db context type. | |||
/// </summary> | |||
internal Type DbContextType { get; set; } | |||
} |
@@ -29,22 +29,18 @@ namespace DotNetCore.CAP | |||
_configure(mysqlOptions); | |||
if (mysqlOptions.DbContextType != null) | |||
{ | |||
services.AddSingleton(x => | |||
{ | |||
using (var scope = x.CreateScope()) | |||
{ | |||
var provider = scope.ServiceProvider; | |||
var dbContext = (DbContext)provider.GetService(mysqlOptions.DbContextType); | |||
var dbContext = (DbContext) provider.GetService(mysqlOptions.DbContextType); | |||
mysqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; | |||
return mysqlOptions; | |||
} | |||
}); | |||
} | |||
else | |||
{ | |||
services.AddSingleton(mysqlOptions); | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
// ReSharper disable once CheckNamespace | |||
namespace DotNetCore.CAP | |||
{ | |||
public class MySqlOptions : EFOptions | |||
@@ -9,10 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
public static CapOptions UseMySql(this CapOptions options, string connectionString) | |||
{ | |||
return options.UseMySql(opt => | |||
{ | |||
opt.ConnectionString = connectionString; | |||
}); | |||
return options.UseMySql(opt => { opt.ConnectionString = connectionString; }); | |||
} | |||
public static CapOptions UseMySql(this CapOptions options, Action<MySqlOptions> configure) | |||
@@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
public static CapOptions UseEntityFramework<TContext>(this CapOptions options) | |||
where TContext : DbContext | |||
{ | |||
return options.UseEntityFramework<TContext>(opt => | |||
{ | |||
opt.DbContextType = typeof(TContext); | |||
}); | |||
return options.UseEntityFramework<TContext>(opt => { opt.DbContextType = typeof(TContext); }); | |||
} | |||
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure) | |||
@@ -38,7 +32,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
if (configure == null) throw new ArgumentNullException(nameof(configure)); | |||
var efOptions = new EFOptions { DbContextType = typeof(TContext) }; | |||
var efOptions = new EFOptions {DbContextType = typeof(TContext)}; | |||
configure(efOptions); | |||
options.RegisterExtension(new MySqlCapOptionsExtension(configure)); | |||
@@ -13,9 +13,9 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
public class CapPublisher : CapPublisherBase, ICallbackPublisher | |||
{ | |||
private readonly DbContext _dbContext; | |||
private readonly ILogger _logger; | |||
private readonly MySqlOptions _options; | |||
private readonly DbContext _dbContext; | |||
public CapPublisher(IServiceProvider provider, | |||
ILogger<CapPublisher> logger, | |||
@@ -28,7 +28,15 @@ namespace DotNetCore.CAP.MySql | |||
if (_options.DbContextType != null) | |||
{ | |||
IsUsingEF = true; | |||
_dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); | |||
_dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType); | |||
} | |||
} | |||
public async Task PublishAsync(CapPublishedMessage message) | |||
{ | |||
using (var conn = new MySqlConnection(_options.ConnectionString)) | |||
{ | |||
await conn.ExecuteAsync(PrepareSql(), message); | |||
} | |||
} | |||
@@ -45,36 +53,31 @@ namespace DotNetCore.CAP.MySql | |||
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); | |||
dbTrans = dbContextTransaction.GetDbTransaction(); | |||
} | |||
DbTranasaction = dbTrans; | |||
DbTransaction = dbTrans; | |||
} | |||
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) | |||
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, | |||
CapPublishedMessage message) | |||
{ | |||
dbConnection.Execute(PrepareSql(), message, dbTransaction); | |||
_logger.LogInformation("Published Message has been persisted in the database. name:" + message); | |||
} | |||
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) | |||
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, | |||
CapPublishedMessage message) | |||
{ | |||
await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); | |||
_logger.LogInformation("Published Message has been persisted in the database. name:" + message); | |||
} | |||
public async Task PublishAsync(CapPublishedMessage message) | |||
{ | |||
using (var conn = new MySqlConnection(_options.ConnectionString)) | |||
{ | |||
await conn.ExecuteAsync(PrepareSql(), message); | |||
} | |||
} | |||
#region private methods | |||
private string PrepareSql() | |||
{ | |||
return $"INSERT INTO `{_options.TableNamePrefix}.published` (`Name`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; | |||
return | |||
$"INSERT INTO `{_options.TableNamePrefix}.published` (`Name`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`)VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; | |||
} | |||
#endregion private methods | |||
@@ -9,11 +9,10 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
internal class DefaultAdditionalProcessor : IAdditionalProcessor | |||
{ | |||
private readonly ILogger _logger; | |||
private readonly MySqlOptions _options; | |||
private const int MaxBatch = 1000; | |||
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); | |||
private readonly ILogger _logger; | |||
private readonly MySqlOptions _options; | |||
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); | |||
public DefaultAdditionalProcessor(ILogger<DefaultAdditionalProcessor> logger, | |||
@@ -27,7 +26,8 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
_logger.LogDebug("Collecting expired entities."); | |||
var tables = new[]{ | |||
var tables = new[] | |||
{ | |||
$"{_options.TableNamePrefix}.published", | |||
$"{_options.TableNamePrefix}.received" | |||
}; | |||
@@ -39,8 +39,9 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
using (var connection = new MySqlConnection(_options.ConnectionString)) | |||
{ | |||
removedCount = await connection.ExecuteAsync($@"DELETE FROM `{table}` WHERE ExpiresAt < @now limit @count;", | |||
new { now = DateTime.Now, count = MaxBatch }); | |||
removedCount = await connection.ExecuteAsync( | |||
$@"DELETE FROM `{table}` WHERE ExpiresAt < @now limit @count;", | |||
new {now = DateTime.Now, count = MaxBatch}); | |||
} | |||
if (removedCount != 0) | |||
@@ -8,11 +8,11 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
public class MySqlFetchedMessage : IFetchedMessage | |||
{ | |||
private readonly IDbConnection _connection; | |||
private readonly IDbTransaction _transaction; | |||
private readonly Timer _timer; | |||
private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1); | |||
private readonly IDbConnection _connection; | |||
private readonly object _lockObject = new object(); | |||
private readonly Timer _timer; | |||
private readonly IDbTransaction _transaction; | |||
public MySqlFetchedMessage(int messageId, | |||
MessageType type, | |||
@@ -11,9 +11,9 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
public class MySqlStorage : IStorage | |||
{ | |||
private readonly MySqlOptions _options; | |||
private readonly ILogger _logger; | |||
private readonly IDbConnection _existingConnection = null; | |||
private readonly ILogger _logger; | |||
private readonly MySqlOptions _options; | |||
public MySqlStorage(ILogger<MySqlStorage> logger, MySqlOptions options) | |||
{ | |||
@@ -46,7 +46,7 @@ namespace DotNetCore.CAP.MySql | |||
protected virtual string CreateDbTablesScript(string prefix) | |||
{ | |||
var batchSql = | |||
$@" | |||
$@" | |||
CREATE TABLE IF NOT EXISTS `{prefix}.queue` ( | |||
`MessageId` int(11) NOT NULL, | |||
`MessageType` tinyint(4) NOT NULL | |||
@@ -97,9 +97,7 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` ( | |||
var connection = _existingConnection ?? new MySqlConnection(_options.ConnectionString); | |||
if (connection.State == ConnectionState.Closed) | |||
{ | |||
connection.Open(); | |||
} | |||
return connection; | |||
} | |||
@@ -112,9 +110,7 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` ( | |||
internal void ReleaseConnection(IDbConnection connection) | |||
{ | |||
if (connection != null && !IsExistingConnection(connection)) | |||
{ | |||
connection.Dispose(); | |||
} | |||
} | |||
} | |||
} |
@@ -5,23 +5,21 @@ 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 | |||
{ | |||
public class MySqlStorageConnection : IStorageConnection | |||
{ | |||
private readonly MySqlOptions _options; | |||
private readonly string _prefix; | |||
public MySqlStorageConnection(MySqlOptions options) | |||
{ | |||
_options = options; | |||
_prefix = _options.TableNamePrefix; | |||
Options = options; | |||
_prefix = Options.TableNamePrefix; | |||
} | |||
public MySqlOptions Options => _options; | |||
public MySqlOptions Options { get; } | |||
public IStorageTransaction CreateTransaction() | |||
{ | |||
@@ -32,7 +30,7 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
var sql = $@"SELECT * FROM `{_prefix}.published` WHERE `Id`={id};"; | |||
using (var connection = new MySqlConnection(_options.ConnectionString)) | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql); | |||
} | |||
@@ -59,7 +57,7 @@ DELETE FROM `{_prefix}.queue` LIMIT 1;"; | |||
{ | |||
var sql = $"SELECT * FROM `{_prefix}.published` WHERE `StatusName` = '{StatusName.Scheduled}' LIMIT 1;"; | |||
using (var connection = new MySqlConnection(_options.ConnectionString)) | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
return await connection.QueryFirstOrDefaultAsync<CapPublishedMessage>(sql); | |||
} | |||
@@ -69,14 +67,12 @@ DELETE FROM `{_prefix}.queue` LIMIT 1;"; | |||
{ | |||
var sql = $"SELECT * FROM `{_prefix}.published` WHERE `StatusName` = '{StatusName.Failed}';"; | |||
using (var connection = new MySqlConnection(_options.ConnectionString)) | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
return await connection.QueryAsync<CapPublishedMessage>(sql); | |||
} | |||
} | |||
// CapReceviedMessage | |||
public async Task StoreReceivedMessageAsync(CapReceivedMessage message) | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
@@ -85,7 +81,7 @@ DELETE FROM `{_prefix}.queue` LIMIT 1;"; | |||
INSERT INTO `{_prefix}.received`(`Name`,`Group`,`Content`,`Retries`,`Added`,`ExpiresAt`,`StatusName`) | |||
VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; | |||
using (var connection = new MySqlConnection(_options.ConnectionString)) | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
await connection.ExecuteAsync(sql, message); | |||
} | |||
@@ -94,25 +90,25 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; | |||
public async Task<CapReceivedMessage> GetReceivedMessageAsync(int id) | |||
{ | |||
var sql = $@"SELECT * FROM `{_prefix}.received` WHERE Id={id};"; | |||
using (var connection = new MySqlConnection(_options.ConnectionString)) | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
return await connection.QueryFirstOrDefaultAsync<CapReceivedMessage>(sql); | |||
} | |||
} | |||
public async Task<CapReceivedMessage> GetNextReceviedMessageToBeEnqueuedAsync() | |||
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync() | |||
{ | |||
var sql = $"SELECT * FROM `{_prefix}.received` WHERE `StatusName` = '{StatusName.Scheduled}' LIMIT 1;"; | |||
using (var connection = new MySqlConnection(_options.ConnectionString)) | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
return await connection.QueryFirstOrDefaultAsync<CapReceivedMessage>(sql); | |||
} | |||
} | |||
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages() | |||
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages() | |||
{ | |||
var sql = $"SELECT * FROM `{_prefix}.received` WHERE `StatusName` = '{StatusName.Failed}';"; | |||
using (var connection = new MySqlConnection(_options.ConnectionString)) | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
return await connection.QueryAsync<CapReceivedMessage>(sql); | |||
} | |||
@@ -123,10 +119,32 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; | |||
{ | |||
} | |||
public bool ChangePublishedState(int messageId, string state) | |||
{ | |||
var sql = | |||
$"UPDATE `{_prefix}.published` SET `Retries`=`Retries`+1,`StatusName` = '{state}' WHERE `Id`={messageId}"; | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
return connection.Execute(sql) > 0; | |||
} | |||
} | |||
public bool ChangeReceivedState(int messageId, string state) | |||
{ | |||
var sql = | |||
$"UPDATE `{_prefix}.received` SET `Retries`=`Retries`+1,`StatusName` = '{state}' WHERE `Id`={messageId}"; | |||
using (var connection = new MySqlConnection(Options.ConnectionString)) | |||
{ | |||
return connection.Execute(sql) > 0; | |||
} | |||
} | |||
private async Task<IFetchedMessage> FetchNextMessageCoreAsync(string sql, object args = null) | |||
{ | |||
//here don't use `using` to dispose | |||
var connection = new MySqlConnection(_options.ConnectionString); | |||
var connection = new MySqlConnection(Options.ConnectionString); | |||
await connection.OpenAsync(); | |||
var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); | |||
FetchedMessage fetchedMessage; | |||
@@ -148,17 +166,8 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; | |||
return null; | |||
} | |||
return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction); | |||
} | |||
public bool ChangePublishedState(int messageId, string state) | |||
{ | |||
throw new NotImplementedException(); | |||
} | |||
public bool ChangeReceivedState(int messageId, string state) | |||
{ | |||
throw new NotImplementedException(); | |||
return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, | |||
transaction); | |||
} | |||
} | |||
} |
@@ -9,10 +9,10 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
public class MySqlStorageTransaction : IStorageTransaction | |||
{ | |||
private readonly string _prefix; | |||
private readonly IDbConnection _dbConnection; | |||
private readonly IDbTransaction _dbTransaction; | |||
private readonly IDbConnection _dbConnection; | |||
private readonly string _prefix; | |||
public MySqlStorageTransaction(MySqlStorageConnection connection) | |||
{ | |||
@@ -28,7 +28,8 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $"UPDATE `{_prefix}.published` SET `Retries` = @Retries,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; | |||
var sql = | |||
$"UPDATE `{_prefix}.published` SET `Retries` = @Retries,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; | |||
_dbConnection.Execute(sql, message, _dbTransaction); | |||
} | |||
@@ -36,7 +37,8 @@ namespace DotNetCore.CAP.MySql | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $"UPDATE `{_prefix}.received` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; | |||
var sql = | |||
$"UPDATE `{_prefix}.received` SET `Retries` = @Retries,`Content`= @Content,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; | |||
_dbConnection.Execute(sql, message, _dbTransaction); | |||
} | |||
@@ -45,7 +47,8 @@ namespace DotNetCore.CAP.MySql | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $"INSERT INTO `{_prefix}.queue` values(@MessageId,@MessageType);"; | |||
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Publish }, _dbTransaction); | |||
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish}, | |||
_dbTransaction); | |||
} | |||
public void EnqueueMessage(CapReceivedMessage message) | |||
@@ -53,7 +56,8 @@ namespace DotNetCore.CAP.MySql | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $"INSERT INTO `{_prefix}.queue` values(@MessageId,@MessageType);"; | |||
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Subscribe }, _dbTransaction); | |||
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe}, | |||
_dbTransaction); | |||
} | |||
public Task CommitAsync() | |||
@@ -9,7 +9,7 @@ namespace DotNetCore.CAP | |||
/// <summary> | |||
/// Gets or sets the schema to use when creating database objects. | |||
/// Default is <see cref="DefaultSchema"/>. | |||
/// Default is <see cref="DefaultSchema" />. | |||
/// </summary> | |||
public string Schema { get; set; } = DefaultSchema; | |||
@@ -9,10 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
public static CapOptions UsePostgreSql(this CapOptions options, string connectionString) | |||
{ | |||
return options.UsePostgreSql(opt => | |||
{ | |||
opt.ConnectionString = connectionString; | |||
}); | |||
return options.UsePostgreSql(opt => { opt.ConnectionString = connectionString; }); | |||
} | |||
public static CapOptions UsePostgreSql(this CapOptions options, Action<PostgreSqlOptions> configure) | |||
@@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
public static CapOptions UseEntityFramework<TContext>(this CapOptions options) | |||
where TContext : DbContext | |||
{ | |||
return options.UseEntityFramework<TContext>(opt => | |||
{ | |||
opt.DbContextType = typeof(TContext); | |||
}); | |||
return options.UseEntityFramework<TContext>(opt => { opt.DbContextType = typeof(TContext); }); | |||
} | |||
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure) | |||
@@ -38,7 +32,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
if (configure == null) throw new ArgumentNullException(nameof(configure)); | |||
var efOptions = new EFOptions { DbContextType = typeof(TContext) }; | |||
var efOptions = new EFOptions {DbContextType = typeof(TContext)}; | |||
configure(efOptions); | |||
options.RegisterExtension(new PostgreSqlCapOptionsExtension(configure)); | |||
@@ -29,22 +29,18 @@ namespace DotNetCore.CAP | |||
_configure(postgreSqlOptions); | |||
if (postgreSqlOptions.DbContextType != null) | |||
{ | |||
services.AddSingleton(x => | |||
{ | |||
using (var scope = x.CreateScope()) | |||
{ | |||
var provider = scope.ServiceProvider; | |||
var dbContext = (DbContext)provider.GetService(postgreSqlOptions.DbContextType); | |||
var dbContext = (DbContext) provider.GetService(postgreSqlOptions.DbContextType); | |||
postgreSqlOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; | |||
return postgreSqlOptions; | |||
} | |||
}); | |||
} | |||
else | |||
{ | |||
services.AddSingleton(postgreSqlOptions); | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
// ReSharper disable once CheckNamespace | |||
namespace DotNetCore.CAP | |||
{ | |||
public class PostgreSqlOptions : EFOptions | |||
@@ -13,9 +13,9 @@ namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
public class CapPublisher : CapPublisherBase, ICallbackPublisher | |||
{ | |||
private readonly DbContext _dbContext; | |||
private readonly ILogger _logger; | |||
private readonly PostgreSqlOptions _options; | |||
private readonly DbContext _dbContext; | |||
public CapPublisher(IServiceProvider provider, | |||
ILogger<CapPublisher> logger, | |||
@@ -28,7 +28,15 @@ namespace DotNetCore.CAP.PostgreSql | |||
if (_options.DbContextType != null) | |||
{ | |||
IsUsingEF = true; | |||
_dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); | |||
_dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType); | |||
} | |||
} | |||
public async Task PublishAsync(CapPublishedMessage message) | |||
{ | |||
using (var conn = new NpgsqlConnection(_options.ConnectionString)) | |||
{ | |||
await conn.ExecuteAsync(PrepareSql(), message); | |||
} | |||
} | |||
@@ -45,36 +53,31 @@ namespace DotNetCore.CAP.PostgreSql | |||
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); | |||
dbTrans = dbContextTransaction.GetDbTransaction(); | |||
} | |||
DbTranasaction = dbTrans; | |||
DbTransaction = dbTrans; | |||
} | |||
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) | |||
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, | |||
CapPublishedMessage message) | |||
{ | |||
dbConnection.Execute(PrepareSql(), message, dbTransaction); | |||
_logger.LogInformation("Published Message has been persisted in the database. name:" + message); | |||
} | |||
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) | |||
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, | |||
CapPublishedMessage message) | |||
{ | |||
await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); | |||
_logger.LogInformation("Published Message has been persisted in the database. name:" + message); | |||
} | |||
public async Task PublishAsync(CapPublishedMessage message) | |||
{ | |||
using (var conn = new NpgsqlConnection(_options.ConnectionString)) | |||
{ | |||
await conn.ExecuteAsync(PrepareSql(), message); | |||
} | |||
} | |||
#region private methods | |||
private string PrepareSql() | |||
{ | |||
return $"INSERT INTO \"{_options.Schema}\".\"published\" (\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; | |||
return | |||
$"INSERT INTO \"{_options.Schema}\".\"published\" (\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; | |||
} | |||
#endregion private methods | |||
@@ -9,26 +9,22 @@ namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
internal class DefaultAdditionalProcessor : IAdditionalProcessor | |||
{ | |||
private readonly IServiceProvider _provider; | |||
private readonly ILogger _logger; | |||
private readonly PostgreSqlOptions _options; | |||
private const int MaxBatch = 1000; | |||
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); | |||
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); | |||
private static readonly string[] Tables = | |||
{ | |||
"published","received" | |||
"published", "received" | |||
}; | |||
public DefaultAdditionalProcessor( | |||
IServiceProvider provider, | |||
ILogger<DefaultAdditionalProcessor> logger, | |||
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); | |||
private readonly ILogger _logger; | |||
private readonly PostgreSqlOptions _options; | |||
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); | |||
public DefaultAdditionalProcessor(ILogger<DefaultAdditionalProcessor> logger, | |||
PostgreSqlOptions sqlServerOptions) | |||
{ | |||
_logger = logger; | |||
_provider = provider; | |||
_options = sqlServerOptions; | |||
} | |||
@@ -43,8 +39,9 @@ namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
using (var connection = new NpgsqlConnection(_options.ConnectionString)) | |||
{ | |||
removedCount = await connection.ExecuteAsync($"DELETE FROM \"{_options.Schema}\".\"{table}\" WHERE \"ExpiresAt\" < @now AND \"Id\" IN (SELECT \"Id\" FROM \"{_options.Schema}\".\"{table}\" LIMIT @count);", | |||
new { now = DateTime.Now, count = MaxBatch }); | |||
removedCount = await connection.ExecuteAsync( | |||
$"DELETE FROM \"{_options.Schema}\".\"{table}\" WHERE \"ExpiresAt\" < @now AND \"Id\" IN (SELECT \"Id\" FROM \"{_options.Schema}\".\"{table}\" LIMIT @count);", | |||
new {now = DateTime.Now, count = MaxBatch}); | |||
} | |||
if (removedCount != 0) | |||
@@ -8,11 +8,11 @@ namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
public class PostgreSqlFetchedMessage : IFetchedMessage | |||
{ | |||
private readonly IDbConnection _connection; | |||
private readonly IDbTransaction _transaction; | |||
private readonly Timer _timer; | |||
private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1); | |||
private readonly IDbConnection _connection; | |||
private readonly object _lockObject = new object(); | |||
private readonly Timer _timer; | |||
private readonly IDbTransaction _transaction; | |||
public PostgreSqlFetchedMessage(int messageId, | |||
MessageType type, | |||
@@ -10,10 +10,10 @@ using DotNetCore.CAP.Models; | |||
namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
public class PostgreSqlMonitoringApi: IMonitoringApi | |||
public class PostgreSqlMonitoringApi : IMonitoringApi | |||
{ | |||
private readonly PostgreSqlStorage _storage; | |||
private readonly PostgreSqlOptions _options; | |||
private readonly PostgreSqlStorage _storage; | |||
public PostgreSqlMonitoringApi(IStorage storage, PostgreSqlOptions options) | |||
{ | |||
@@ -23,7 +23,7 @@ namespace DotNetCore.CAP.PostgreSql | |||
public StatisticsDto GetStatistics() | |||
{ | |||
string sql = String.Format(@" | |||
var sql = string.Format(@" | |||
select count(Id) from ""{0}"".""published"" where ""StatusName"" = N'Succeeded'; | |||
select count(Id) from ""{0}"".""received"" where ""StatusName"" = N'Succeeded'; | |||
select count(Id) from ""{0}"".""published"" where ""StatusName"" = N'Failed'; | |||
@@ -55,32 +55,22 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin | |||
{ | |||
var tableName = queryDto.MessageType == MessageType.Publish ? "published" : "received"; | |||
var where = string.Empty; | |||
if (!string.IsNullOrEmpty(queryDto.StatusName)) | |||
{ | |||
if (string.Equals(queryDto.StatusName, StatusName.Processing, StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
if (string.Equals(queryDto.StatusName, StatusName.Processing, | |||
StringComparison.CurrentCultureIgnoreCase)) | |||
where += " and \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')"; | |||
} | |||
else | |||
{ | |||
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 limit @Limit"; | |||
var sqlQuery = | |||
$"select * from \"{_options.Schema}\".\"{tableName}\" where 1=1 {where} order by \"Added\" desc offset @Offset limit @Limit"; | |||
return UseConnection(conn => conn.Query<MessageDto>(sqlQuery, new | |||
{ | |||
@@ -89,7 +79,7 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin | |||
queryDto.Name, | |||
queryDto.Content, | |||
Offset = queryDto.CurrentPage * queryDto.PageSize, | |||
Limit = queryDto.PageSize, | |||
Limit = queryDto.PageSize | |||
}).ToList()); | |||
} | |||
@@ -143,7 +133,7 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin | |||
? $"select count(Id) from \"{_options.Schema}\".\"{tableName}\" where \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')" | |||
: $"select count(Id) from \"{_options.Schema}\".\"{tableName}\" where \"StatusName\" = @state"; | |||
var count = connection.ExecuteScalar<int>(sqlQuery, new { state = statusName }); | |||
var count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName}); | |||
return count; | |||
} | |||
@@ -152,7 +142,8 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin | |||
return _storage.UseConnection(action); | |||
} | |||
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName, string statusName) | |||
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName, | |||
string statusName) | |||
{ | |||
var endDate = DateTime.Now; | |||
var dates = new List<DateTime>(); | |||
@@ -174,7 +165,7 @@ select count(Id) from ""{0}"".""received"" where ""StatusName"" in (N'Processin | |||
IDictionary<string, DateTime> keyMaps) | |||
{ | |||
//SQL Server 2012+ | |||
string sqlQuery = | |||
var sqlQuery = | |||
$@" | |||
with aggr as ( | |||
select to_char(""Added"",'yyyy-MM-dd-HH') as ""Key"", | |||
@@ -187,13 +178,11 @@ select ""Key"",""Count"" from aggr where ""Key"" in @keys;"; | |||
var valuesMap = connection.Query( | |||
sqlQuery, | |||
new { keys = keyMaps.Keys, statusName }) | |||
.ToDictionary(x => (string)x.Key, x => (int)x.Count); | |||
new {keys = keyMaps.Keys, 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++) | |||
@@ -11,9 +11,9 @@ namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
public class PostgreSqlStorage : IStorage | |||
{ | |||
private readonly PostgreSqlOptions _options; | |||
private readonly ILogger _logger; | |||
private readonly IDbConnection _existingConnection = null; | |||
private readonly ILogger _logger; | |||
private readonly PostgreSqlOptions _options; | |||
public PostgreSqlStorage(ILogger<PostgreSqlStorage> logger, PostgreSqlOptions options) | |||
{ | |||
@@ -64,9 +64,7 @@ namespace DotNetCore.CAP.PostgreSql | |||
var connection = _existingConnection ?? new NpgsqlConnection(_options.ConnectionString); | |||
if (connection.State == ConnectionState.Closed) | |||
{ | |||
connection.Open(); | |||
} | |||
return connection; | |||
} | |||
@@ -79,9 +77,7 @@ namespace DotNetCore.CAP.PostgreSql | |||
internal void ReleaseConnection(IDbConnection connection) | |||
{ | |||
if (connection != null && !IsExistingConnection(connection)) | |||
{ | |||
connection.Dispose(); | |||
} | |||
} | |||
protected virtual string CreateDbTablesScript(string schema) | |||
@@ -63,8 +63,6 @@ namespace DotNetCore.CAP.PostgreSql | |||
} | |||
} | |||
// CapReceviedMessage | |||
public async Task StoreReceivedMessageAsync(CapReceivedMessage message) | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
@@ -87,7 +85,7 @@ namespace DotNetCore.CAP.PostgreSql | |||
} | |||
} | |||
public async Task<CapReceivedMessage> GetNextReceviedMessageToBeEnqueuedAsync() | |||
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync() | |||
{ | |||
var sql = | |||
$"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"StatusName\" = '{StatusName.Scheduled}' FOR UPDATE SKIP LOCKED LIMIT 1;"; | |||
@@ -97,7 +95,7 @@ namespace DotNetCore.CAP.PostgreSql | |||
} | |||
} | |||
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages() | |||
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages() | |||
{ | |||
var sql = | |||
$"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"StatusName\"='{StatusName.Failed}' LIMIT 1000;"; | |||
@@ -125,7 +123,7 @@ namespace DotNetCore.CAP.PostgreSql | |||
public bool ChangeReceivedState(int messageId, string state) | |||
{ | |||
var sql = | |||
$"UPDATE \"{Options.Schema}\".\"received\" SET \"Retries\"=\"Retries\"+1,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}"; | |||
$"UPDATE \"{Options.Schema}\".\"received\" SET \"Retries\"=\"Retries\"+1,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}"; | |||
using (var connection = new NpgsqlConnection(Options.ConnectionString)) | |||
{ | |||
@@ -9,10 +9,10 @@ namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
public class PostgreSqlStorageTransaction : IStorageTransaction | |||
{ | |||
private readonly string _schema; | |||
private readonly IDbConnection _dbConnection; | |||
private readonly IDbTransaction _dbTransaction; | |||
private readonly IDbConnection _dbConnection; | |||
private readonly string _schema; | |||
public PostgreSqlStorageTransaction(PostgreSqlStorageConnection connection) | |||
{ | |||
@@ -28,7 +28,10 @@ namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $@"UPDATE ""{_schema}"".""published"" SET ""Retries""=@Retries,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;"; | |||
var sql = | |||
$@"UPDATE ""{ | |||
_schema | |||
}"".""published"" SET ""Retries""=@Retries,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;"; | |||
_dbConnection.Execute(sql, message, _dbTransaction); | |||
} | |||
@@ -36,7 +39,10 @@ namespace DotNetCore.CAP.PostgreSql | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $@"UPDATE ""{_schema}"".""received"" SET ""Retries""=@Retries,""Content""= @Content,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;"; | |||
var sql = | |||
$@"UPDATE ""{ | |||
_schema | |||
}"".""received"" SET ""Retries""=@Retries,""Content""= @Content,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;"; | |||
_dbConnection.Execute(sql, message, _dbTransaction); | |||
} | |||
@@ -45,7 +51,8 @@ namespace DotNetCore.CAP.PostgreSql | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);"; | |||
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Publish }, _dbTransaction); | |||
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish}, | |||
_dbTransaction); | |||
} | |||
public void EnqueueMessage(CapReceivedMessage message) | |||
@@ -53,7 +60,8 @@ namespace DotNetCore.CAP.PostgreSql | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);"; | |||
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Subscribe }, _dbTransaction); | |||
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe}, | |||
_dbTransaction); | |||
} | |||
public Task CommitAsync() | |||
@@ -8,10 +8,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
public static CapOptions UseRabbitMQ(this CapOptions options, string hostName) | |||
{ | |||
return options.UseRabbitMQ(opt => | |||
{ | |||
opt.HostName = hostName; | |||
}); | |||
return options.UseRabbitMQ(opt => { opt.HostName = hostName; }); | |||
} | |||
public static CapOptions UseRabbitMQ(this CapOptions options, Action<RabbitMQOptions> configure) | |||
@@ -1,4 +1,5 @@ | |||
// ReSharper disable once CheckNamespace | |||
namespace DotNetCore.CAP | |||
{ | |||
public class RabbitMQOptions | |||
@@ -10,13 +10,13 @@ namespace DotNetCore.CAP.RabbitMQ | |||
{ | |||
private const int DefaultPoolSize = 15; | |||
private readonly ConcurrentQueue<IConnection> _pool = new ConcurrentQueue<IConnection>(); | |||
private readonly Func<IConnection> _activator; | |||
private int _maxSize; | |||
private readonly ConcurrentQueue<IConnection> _pool = new ConcurrentQueue<IConnection>(); | |||
private int _count; | |||
private int _maxSize; | |||
public ConnectionPool(RabbitMQOptions options) | |||
{ | |||
_maxSize = DefaultPoolSize; | |||
@@ -24,9 +24,28 @@ namespace DotNetCore.CAP.RabbitMQ | |||
_activator = CreateActivator(options); | |||
} | |||
IConnection IConnectionPool.Rent() | |||
{ | |||
return Rent(); | |||
} | |||
bool IConnectionPool.Return(IConnection connection) | |||
{ | |||
return Return(connection); | |||
} | |||
public void Dispose() | |||
{ | |||
_maxSize = 0; | |||
IConnection context; | |||
while (_pool.TryDequeue(out context)) | |||
context.Dispose(); | |||
} | |||
private static Func<IConnection> CreateActivator(RabbitMQOptions options) | |||
{ | |||
var factory = new ConnectionFactory() | |||
var factory = new ConnectionFactory | |||
{ | |||
HostName = options.HostName, | |||
UserName = options.UserName, | |||
@@ -43,7 +62,7 @@ namespace DotNetCore.CAP.RabbitMQ | |||
public virtual IConnection Rent() | |||
{ | |||
if (_pool.TryDequeue(out IConnection connection)) | |||
if (_pool.TryDequeue(out var connection)) | |||
{ | |||
Interlocked.Decrement(ref _count); | |||
@@ -72,20 +91,5 @@ namespace DotNetCore.CAP.RabbitMQ | |||
return false; | |||
} | |||
IConnection IConnectionPool.Rent() => Rent(); | |||
bool IConnectionPool.Return(IConnection connection) => Return(connection); | |||
public void Dispose() | |||
{ | |||
_maxSize = 0; | |||
IConnection context; | |||
while (_pool.TryDequeue(out context)) | |||
{ | |||
context.Dispose(); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -8,4 +8,4 @@ namespace DotNetCore.CAP.RabbitMQ | |||
bool Return(IConnection context); | |||
} | |||
} | |||
} |
@@ -9,8 +9,8 @@ namespace DotNetCore.CAP.RabbitMQ | |||
{ | |||
internal sealed class PublishQueueExecutor : BasePublishQueueExecutor | |||
{ | |||
private readonly ILogger _logger; | |||
private readonly ConnectionPool _connectionPool; | |||
private readonly ILogger _logger; | |||
private readonly RabbitMQOptions _rabbitMQOptions; | |||
public PublishQueueExecutor( | |||
@@ -36,11 +36,11 @@ namespace DotNetCore.CAP.RabbitMQ | |||
{ | |||
var body = Encoding.UTF8.GetBytes(content); | |||
channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, durable: true); | |||
channel.BasicPublish(exchange: _rabbitMQOptions.TopicExchangeName, | |||
routingKey: keyName, | |||
basicProperties: null, | |||
body: body); | |||
channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, true); | |||
channel.BasicPublish(_rabbitMQOptions.TopicExchangeName, | |||
keyName, | |||
null, | |||
body); | |||
_logger.LogDebug($"rabbitmq topic message [{keyName}] has been published."); | |||
} | |||
@@ -48,10 +48,11 @@ namespace DotNetCore.CAP.RabbitMQ | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError($"rabbitmq topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}"); | |||
_logger.LogError( | |||
$"rabbitmq topic message [{keyName}] has benn raised an exception of sending. the exception is: {ex.Message}"); | |||
return Task.FromResult(OperateResult.Failed(ex, | |||
new OperateError() | |||
new OperateError | |||
{ | |||
Code = ex.HResult.ToString(), | |||
Description = ex.Message | |||
@@ -10,21 +10,17 @@ namespace DotNetCore.CAP.RabbitMQ | |||
{ | |||
internal sealed class RabbitMQConsumerClient : IConsumerClient | |||
{ | |||
private readonly ConnectionPool _connectionPool; | |||
private readonly string _exchageName; | |||
private readonly string _queueName; | |||
private readonly RabbitMQOptions _rabbitMQOptions; | |||
private readonly ConnectionPool _connectionPool; | |||
private IModel _channel; | |||
private ulong _deliveryTag; | |||
public event EventHandler<MessageContext> OnMessageReceieved; | |||
public event EventHandler<string> OnError; | |||
public RabbitMQConsumerClient(string queueName, | |||
ConnectionPool connectionPool, | |||
RabbitMQOptions options) | |||
ConnectionPool connectionPool, | |||
RabbitMQOptions options) | |||
{ | |||
_queueName = queueName; | |||
_connectionPool = connectionPool; | |||
@@ -34,35 +30,16 @@ namespace DotNetCore.CAP.RabbitMQ | |||
InitClient(); | |||
} | |||
private void InitClient() | |||
{ | |||
var connection = _connectionPool.Rent(); | |||
_channel = connection.CreateModel(); | |||
_channel.ExchangeDeclare( | |||
exchange: _exchageName, | |||
type: RabbitMQOptions.ExchangeType, | |||
durable: true); | |||
var arguments = new Dictionary<string, object> { { "x-message-ttl", _rabbitMQOptions.QueueMessageExpires } }; | |||
_channel.QueueDeclare(_queueName, | |||
durable: true, | |||
exclusive: false, | |||
autoDelete: false, | |||
arguments: arguments); | |||
public event EventHandler<MessageContext> OnMessageReceived; | |||
_connectionPool.Return(connection); | |||
} | |||
public event EventHandler<string> OnError; | |||
public void Subscribe(IEnumerable<string> topics) | |||
{ | |||
if (topics == null) throw new ArgumentNullException(nameof(topics)); | |||
foreach (var topic in topics) | |||
{ | |||
_channel.QueueBind(_queueName, _exchageName, topic); | |||
} | |||
} | |||
public void Listening(TimeSpan timeout, CancellationToken cancellationToken) | |||
@@ -72,9 +49,7 @@ namespace DotNetCore.CAP.RabbitMQ | |||
consumer.Shutdown += OnConsumerShutdown; | |||
_channel.BasicConsume(_queueName, false, consumer); | |||
while (true) | |||
{ | |||
Task.Delay(timeout, cancellationToken).GetAwaiter().GetResult(); | |||
} | |||
} | |||
public void Commit() | |||
@@ -87,6 +62,27 @@ namespace DotNetCore.CAP.RabbitMQ | |||
_channel.Dispose(); | |||
} | |||
private void InitClient() | |||
{ | |||
var connection = _connectionPool.Rent(); | |||
_channel = connection.CreateModel(); | |||
_channel.ExchangeDeclare( | |||
_exchageName, | |||
RabbitMQOptions.ExchangeType, | |||
true); | |||
var arguments = new Dictionary<string, object> {{"x-message-ttl", _rabbitMQOptions.QueueMessageExpires}}; | |||
_channel.QueueDeclare(_queueName, | |||
true, | |||
false, | |||
false, | |||
arguments); | |||
_connectionPool.Return(connection); | |||
} | |||
private void OnConsumerReceived(object sender, BasicDeliverEventArgs e) | |||
{ | |||
_deliveryTag = e.DeliveryTag; | |||
@@ -96,7 +92,7 @@ namespace DotNetCore.CAP.RabbitMQ | |||
Name = e.RoutingKey, | |||
Content = Encoding.UTF8.GetString(e.Body) | |||
}; | |||
OnMessageReceieved?.Invoke(sender, message); | |||
OnMessageReceived?.Invoke(sender, message); | |||
} | |||
private void OnConsumerShutdown(object sender, ShutdownEventArgs e) | |||
@@ -2,8 +2,8 @@ | |||
{ | |||
internal sealed class RabbitMQConsumerClientFactory : IConsumerClientFactory | |||
{ | |||
private readonly RabbitMQOptions _rabbitMQOptions; | |||
private readonly ConnectionPool _connectionPool; | |||
private readonly RabbitMQOptions _rabbitMQOptions; | |||
public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, ConnectionPool pool) | |||
@@ -9,7 +9,7 @@ namespace DotNetCore.CAP | |||
/// <summary> | |||
/// Gets or sets the schema to use when creating database objects. | |||
/// Default is <see cref="DefaultSchema"/>. | |||
/// Default is <see cref="DefaultSchema" />. | |||
/// </summary> | |||
public string Schema { get; set; } = DefaultSchema; | |||
@@ -9,10 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
public static CapOptions UseSqlServer(this CapOptions options, string connectionString) | |||
{ | |||
return options.UseSqlServer(opt => | |||
{ | |||
opt.ConnectionString = connectionString; | |||
}); | |||
return options.UseSqlServer(opt => { opt.ConnectionString = connectionString; }); | |||
} | |||
public static CapOptions UseSqlServer(this CapOptions options, Action<SqlServerOptions> configure) | |||
@@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
public static CapOptions UseEntityFramework<TContext>(this CapOptions options) | |||
where TContext : DbContext | |||
{ | |||
return options.UseEntityFramework<TContext>(opt => | |||
{ | |||
opt.DbContextType = typeof(TContext); | |||
}); | |||
return options.UseEntityFramework<TContext>(opt => { opt.DbContextType = typeof(TContext); }); | |||
} | |||
public static CapOptions UseEntityFramework<TContext>(this CapOptions options, Action<EFOptions> configure) | |||
@@ -38,7 +32,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
if (configure == null) throw new ArgumentNullException(nameof(configure)); | |||
var efOptions = new EFOptions { DbContextType = typeof(TContext) }; | |||
var efOptions = new EFOptions {DbContextType = typeof(TContext)}; | |||
configure(efOptions); | |||
options.RegisterExtension(new SqlServerCapOptionsExtension(configure)); | |||
@@ -34,22 +34,18 @@ namespace DotNetCore.CAP | |||
_configure(sqlServerOptions); | |||
if (sqlServerOptions.DbContextType != null) | |||
{ | |||
services.AddSingleton(x => | |||
{ | |||
using (var scope = x.CreateScope()) | |||
{ | |||
var provider = scope.ServiceProvider; | |||
var dbContext = (DbContext)provider.GetService(sqlServerOptions.DbContextType); | |||
var dbContext = (DbContext) provider.GetService(sqlServerOptions.DbContextType); | |||
sqlServerOptions.ConnectionString = dbContext.Database.GetDbConnection().ConnectionString; | |||
return sqlServerOptions; | |||
} | |||
}); | |||
} | |||
else | |||
{ | |||
services.AddSingleton(sqlServerOptions); | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
// ReSharper disable once CheckNamespace | |||
namespace DotNetCore.CAP | |||
{ | |||
public class SqlServerOptions : EFOptions | |||
@@ -13,9 +13,9 @@ namespace DotNetCore.CAP.SqlServer | |||
{ | |||
public class CapPublisher : CapPublisherBase, ICallbackPublisher | |||
{ | |||
private readonly DbContext _dbContext; | |||
private readonly ILogger _logger; | |||
private readonly SqlServerOptions _options; | |||
private readonly DbContext _dbContext; | |||
public CapPublisher(IServiceProvider provider, | |||
ILogger<CapPublisher> logger, | |||
@@ -28,7 +28,15 @@ namespace DotNetCore.CAP.SqlServer | |||
if (_options.DbContextType != null) | |||
{ | |||
IsUsingEF = true; | |||
_dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); | |||
_dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType); | |||
} | |||
} | |||
public async Task PublishAsync(CapPublishedMessage message) | |||
{ | |||
using (var conn = new SqlConnection(_options.ConnectionString)) | |||
{ | |||
await conn.ExecuteAsync(PrepareSql(), message); | |||
} | |||
} | |||
@@ -45,36 +53,31 @@ namespace DotNetCore.CAP.SqlServer | |||
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted); | |||
dbTrans = dbContextTransaction.GetDbTransaction(); | |||
} | |||
DbTranasaction = dbTrans; | |||
DbTransaction = dbTrans; | |||
} | |||
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) | |||
protected override void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, | |||
CapPublishedMessage message) | |||
{ | |||
dbConnection.Execute(PrepareSql(), message, dbTransaction); | |||
_logger.LogInformation("Published Message has been persisted in the database. name:" + message); | |||
} | |||
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message) | |||
protected override async Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, | |||
CapPublishedMessage message) | |||
{ | |||
await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); | |||
_logger.LogInformation("Published Message has been persisted in the database. name:" + message); | |||
} | |||
public async Task PublishAsync(CapPublishedMessage message) | |||
{ | |||
using (var conn = new SqlConnection(_options.ConnectionString)) | |||
{ | |||
await conn.ExecuteAsync(PrepareSql(), message); | |||
} | |||
} | |||
#region private methods | |||
private string PrepareSql() | |||
{ | |||
return $"INSERT INTO {_options.Schema}.[Published] ([Name],[Content],[Retries],[Added],[ExpiresAt],[StatusName])VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; | |||
return | |||
$"INSERT INTO {_options.Schema}.[Published] ([Name],[Content],[Retries],[Added],[ExpiresAt],[StatusName])VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName)"; | |||
} | |||
#endregion private methods | |||
@@ -9,18 +9,18 @@ namespace DotNetCore.CAP.SqlServer | |||
{ | |||
public class DefaultAdditionalProcessor : IAdditionalProcessor | |||
{ | |||
private readonly ILogger _logger; | |||
private readonly SqlServerOptions _options; | |||
private const int MaxBatch = 1000; | |||
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); | |||
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); | |||
private static readonly string[] Tables = | |||
{ | |||
"Published","Received" | |||
"Published", "Received" | |||
}; | |||
private readonly TimeSpan _delay = TimeSpan.FromSeconds(1); | |||
private readonly ILogger _logger; | |||
private readonly SqlServerOptions _options; | |||
private readonly TimeSpan _waitingInterval = TimeSpan.FromMinutes(5); | |||
public DefaultAdditionalProcessor(ILogger<DefaultAdditionalProcessor> logger, | |||
SqlServerOptions sqlServerOptions) | |||
{ | |||
@@ -42,7 +42,7 @@ namespace DotNetCore.CAP.SqlServer | |||
removedCount = await connection.ExecuteAsync($@" | |||
DELETE TOP (@count) | |||
FROM [{_options.Schema}].[{table}] WITH (readpast) | |||
WHERE ExpiresAt < @now;", new { now = DateTime.Now, count = MaxBatch }); | |||
WHERE ExpiresAt < @now;", new {now = DateTime.Now, count = MaxBatch}); | |||
} | |||
if (removedCount != 0) | |||
@@ -8,11 +8,11 @@ namespace DotNetCore.CAP.SqlServer | |||
{ | |||
public class SqlServerFetchedMessage : IFetchedMessage | |||
{ | |||
private readonly IDbConnection _connection; | |||
private readonly IDbTransaction _transaction; | |||
private readonly Timer _timer; | |||
private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1); | |||
private readonly IDbConnection _connection; | |||
private readonly object _lockObject = new object(); | |||
private readonly Timer _timer; | |||
private readonly IDbTransaction _transaction; | |||
public SqlServerFetchedMessage(int messageId, | |||
MessageType type, | |||
@@ -12,8 +12,8 @@ namespace DotNetCore.CAP.SqlServer | |||
{ | |||
internal class SqlServerMonitoringApi : IMonitoringApi | |||
{ | |||
private readonly SqlServerStorage _storage; | |||
private readonly SqlServerOptions _options; | |||
private readonly SqlServerStorage _storage; | |||
public SqlServerMonitoringApi(IStorage storage, SqlServerOptions options) | |||
{ | |||
@@ -23,7 +23,7 @@ namespace DotNetCore.CAP.SqlServer | |||
public StatisticsDto GetStatistics() | |||
{ | |||
string sql = String.Format(@" | |||
var 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'; | |||
@@ -31,7 +31,7 @@ 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 in (N'Processing',N'Scheduled',N'Enqueued'); | |||
select count(Id) from [{0}].Received with (nolock) where StatusName in (N'Processing',N'Scheduled',N'Enqueued');", | |||
_options.Schema); | |||
_options.Schema); | |||
var statistics = UseConnection(connection => | |||
{ | |||
@@ -63,7 +63,7 @@ _options.Schema); | |||
{ | |||
var tableName = type == MessageType.Publish ? "Published" : "Received"; | |||
return UseConnection(connection => | |||
GetHourlyTimelineStats(connection, tableName, StatusName.Succeeded)); | |||
GetHourlyTimelineStats(connection, tableName, StatusName.Succeeded)); | |||
} | |||
public IList<MessageDto> Messages(MessageQueryDto queryDto) | |||
@@ -71,30 +71,20 @@ _options.Schema); | |||
var tableName = queryDto.MessageType == MessageType.Publish ? "Published" : "Received"; | |||
var where = string.Empty; | |||
if (!string.IsNullOrEmpty(queryDto.StatusName)) | |||
{ | |||
if (string.Equals(queryDto.StatusName, StatusName.Processing, StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
if (string.Equals(queryDto.StatusName, StatusName.Processing, | |||
StringComparison.CurrentCultureIgnoreCase)) | |||
where += " and statusname in (N'Processing',N'Scheduled',N'Enqueued')"; | |||
} | |||
else | |||
{ | |||
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"; | |||
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 => conn.Query<MessageDto>(sqlQuery, new | |||
{ | |||
@@ -103,7 +93,7 @@ _options.Schema); | |||
queryDto.Name, | |||
queryDto.Content, | |||
Offset = queryDto.CurrentPage * queryDto.PageSize, | |||
Limit = queryDto.PageSize, | |||
Limit = queryDto.PageSize | |||
}).ToList()); | |||
} | |||
@@ -143,7 +133,7 @@ _options.Schema); | |||
? $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName in (N'Processing',N'Scheduled',N'Enqueued')" | |||
: $"select count(Id) from [{_options.Schema}].{tableName} with (nolock) where StatusName = @state"; | |||
var count = connection.ExecuteScalar<int>(sqlQuery, new { state = statusName }); | |||
var count = connection.ExecuteScalar<int>(sqlQuery, new {state = statusName}); | |||
return count; | |||
} | |||
@@ -152,7 +142,8 @@ _options.Schema); | |||
return _storage.UseConnection(action); | |||
} | |||
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName, string statusName) | |||
private Dictionary<DateTime, int> GetHourlyTimelineStats(IDbConnection connection, string tableName, | |||
string statusName) | |||
{ | |||
var endDate = DateTime.Now; | |||
var dates = new List<DateTime>(); | |||
@@ -168,14 +159,14 @@ _options.Schema); | |||
} | |||
private Dictionary<DateTime, int> GetTimelineStats( | |||
IDbConnection connection, | |||
string tableName, | |||
string statusName, | |||
IDictionary<string, DateTime> keyMaps) | |||
IDbConnection connection, | |||
string tableName, | |||
string statusName, | |||
IDictionary<string, DateTime> keyMaps) | |||
{ | |||
//SQL Server 2012+ | |||
string sqlQuery = | |||
$@" | |||
var sqlQuery = | |||
$@" | |||
with aggr as ( | |||
select FORMAT(Added,'yyyy-MM-dd-HH') as [Key], | |||
count(id) [Count] | |||
@@ -186,14 +177,12 @@ with aggr as ( | |||
select [Key], [Count] from aggr with (nolock) where [Key] in @keys;"; | |||
var valuesMap = connection.Query( | |||
sqlQuery, | |||
new { keys = keyMaps.Keys, statusName }) | |||
.ToDictionary(x => (string)x.Key, x => (int)x.Count); | |||
sqlQuery, | |||
new {keys = keyMaps.Keys, 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++) | |||
@@ -11,9 +11,9 @@ namespace DotNetCore.CAP.SqlServer | |||
{ | |||
public class SqlServerStorage : IStorage | |||
{ | |||
private readonly SqlServerOptions _options; | |||
private readonly ILogger _logger; | |||
private readonly IDbConnection _existingConnection = null; | |||
private readonly ILogger _logger; | |||
private readonly SqlServerOptions _options; | |||
public SqlServerStorage(ILogger<SqlServerStorage> logger, SqlServerOptions options) | |||
{ | |||
@@ -47,7 +47,7 @@ namespace DotNetCore.CAP.SqlServer | |||
protected virtual string CreateDbTablesScript(string schema) | |||
{ | |||
var batchSql = | |||
$@" | |||
$@" | |||
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{schema}') | |||
BEGIN | |||
EXEC('CREATE SCHEMA {schema}') | |||
@@ -118,9 +118,7 @@ END;"; | |||
var connection = _existingConnection ?? new SqlConnection(_options.ConnectionString); | |||
if (connection.State == ConnectionState.Closed) | |||
{ | |||
connection.Open(); | |||
} | |||
return connection; | |||
} | |||
@@ -133,10 +131,7 @@ END;"; | |||
internal void ReleaseConnection(IDbConnection connection) | |||
{ | |||
if (connection != null && !IsExistingConnection(connection)) | |||
{ | |||
connection.Dispose(); | |||
} | |||
} | |||
} | |||
} |
@@ -101,7 +101,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; | |||
} | |||
} | |||
public async Task<CapReceivedMessage> GetNextReceviedMessageToBeEnqueuedAsync() | |||
public async Task<CapReceivedMessage> GetNextReceivedMessageToBeEnqueuedAsync() | |||
{ | |||
var sql = | |||
$"SELECT TOP (1) * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Scheduled}'"; | |||
@@ -111,7 +111,7 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; | |||
} | |||
} | |||
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceviedMessages() | |||
public async Task<IEnumerable<CapReceivedMessage>> GetFailedReceivedMessages() | |||
{ | |||
var sql = | |||
$"SELECT * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Failed}'"; | |||
@@ -9,10 +9,10 @@ namespace DotNetCore.CAP.SqlServer | |||
{ | |||
public class SqlServerStorageTransaction : IStorageTransaction | |||
{ | |||
private readonly string _schema; | |||
private readonly IDbConnection _dbConnection; | |||
private readonly IDbTransaction _dbTransaction; | |||
private readonly IDbConnection _dbConnection; | |||
private readonly string _schema; | |||
public SqlServerStorageTransaction(SqlServerStorageConnection connection) | |||
{ | |||
@@ -28,7 +28,8 @@ namespace DotNetCore.CAP.SqlServer | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $"UPDATE [{_schema}].[Published] SET [Retries] = @Retries,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;"; | |||
var sql = | |||
$"UPDATE [{_schema}].[Published] SET [Retries] = @Retries,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;"; | |||
_dbConnection.Execute(sql, message, _dbTransaction); | |||
} | |||
@@ -36,7 +37,8 @@ namespace DotNetCore.CAP.SqlServer | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $"UPDATE [{_schema}].[Received] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;"; | |||
var sql = | |||
$"UPDATE [{_schema}].[Received] SET [Retries] = @Retries,[Content] = @Content,[ExpiresAt] = @ExpiresAt,[StatusName]=@StatusName WHERE Id=@Id;"; | |||
_dbConnection.Execute(sql, message, _dbTransaction); | |||
} | |||
@@ -45,7 +47,8 @@ namespace DotNetCore.CAP.SqlServer | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $"INSERT INTO [{_schema}].[Queue] values(@MessageId,@MessageType);"; | |||
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Publish }, _dbTransaction); | |||
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish}, | |||
_dbTransaction); | |||
} | |||
public void EnqueueMessage(CapReceivedMessage message) | |||
@@ -53,7 +56,8 @@ namespace DotNetCore.CAP.SqlServer | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
var sql = $"INSERT INTO [{_schema}].[Queue] values(@MessageId,@MessageType);"; | |||
_dbConnection.Execute(sql, new CapQueue { MessageId = message.Id, MessageType = MessageType.Subscribe }, _dbTransaction); | |||
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe}, | |||
_dbTransaction); | |||
} | |||
public Task CommitAsync() | |||
@@ -10,7 +10,7 @@ namespace DotNetCore.CAP.Abstractions | |||
public abstract class CapPublisherBase : ICapPublisher, IDisposable | |||
{ | |||
protected IDbConnection DbConnection { get; set; } | |||
protected IDbTransaction DbTranasaction { get; set; } | |||
protected IDbTransaction DbTransaction { get; set; } | |||
protected bool IsCapOpenedTrans { get; set; } | |||
protected bool IsCapOpenedConn { get; set; } | |||
protected bool IsUsingEF { get; set; } | |||
@@ -60,13 +60,15 @@ namespace DotNetCore.CAP.Abstractions | |||
protected abstract void PrepareConnectionForEF(); | |||
protected abstract void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message); | |||
protected abstract void Execute(IDbConnection dbConnection, IDbTransaction dbTransaction, | |||
CapPublishedMessage message); | |||
protected abstract Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, CapPublishedMessage message); | |||
protected abstract Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, | |||
CapPublishedMessage message); | |||
protected virtual string Serialize<T>(T obj, string callbackName = null) | |||
{ | |||
var serializer = (IContentSerializer)ServiceProvider.GetService(typeof(IContentSerializer)); | |||
var serializer = (IContentSerializer) ServiceProvider.GetService(typeof(IContentSerializer)); | |||
var message = new CapMessageDto(obj) | |||
{ | |||
@@ -85,11 +87,11 @@ namespace DotNetCore.CAP.Abstractions | |||
IsCapOpenedConn = true; | |||
DbConnection.Open(); | |||
} | |||
DbTranasaction = dbTransaction; | |||
if (DbTranasaction == null) | |||
DbTransaction = dbTransaction; | |||
if (DbTransaction == null) | |||
{ | |||
IsCapOpenedTrans = true; | |||
DbTranasaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted); | |||
DbTransaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted); | |||
} | |||
} | |||
@@ -97,15 +99,17 @@ namespace DotNetCore.CAP.Abstractions | |||
{ | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (!IsUsingEF) | |||
throw new InvalidOperationException("If you are using the EntityFramework, you need to configure the DbContextType first." + | |||
" otherwise you need to use overloaded method with IDbConnection and IDbTransaction."); | |||
throw new InvalidOperationException( | |||
"If you are using the EntityFramework, you need to configure the DbContextType first." + | |||
" otherwise you need to use overloaded method with IDbConnection and IDbTransaction."); | |||
} | |||
private void CheckIsAdoNet(string name) | |||
{ | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (IsUsingEF) | |||
throw new InvalidOperationException("If you are using the EntityFramework, you do not need to use this overloaded."); | |||
throw new InvalidOperationException( | |||
"If you are using the EntityFramework, you do not need to use this overloaded."); | |||
} | |||
private async Task PublishWithTransAsync(string name, string content) | |||
@@ -117,7 +121,7 @@ namespace DotNetCore.CAP.Abstractions | |||
StatusName = StatusName.Scheduled | |||
}; | |||
await ExecuteAsync(DbConnection, DbTranasaction, message); | |||
await ExecuteAsync(DbConnection, DbTransaction, message); | |||
ClosedCap(); | |||
@@ -133,7 +137,7 @@ namespace DotNetCore.CAP.Abstractions | |||
StatusName = StatusName.Scheduled | |||
}; | |||
Execute(DbConnection, DbTranasaction, message); | |||
Execute(DbConnection, DbTransaction, message); | |||
ClosedCap(); | |||
@@ -144,18 +148,16 @@ namespace DotNetCore.CAP.Abstractions | |||
{ | |||
if (IsCapOpenedTrans) | |||
{ | |||
DbTranasaction.Commit(); | |||
DbTranasaction.Dispose(); | |||
DbTransaction.Commit(); | |||
DbTransaction.Dispose(); | |||
} | |||
if (IsCapOpenedConn) | |||
{ | |||
DbConnection.Dispose(); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
DbTranasaction?.Dispose(); | |||
DbTransaction?.Dispose(); | |||
DbConnection?.Dispose(); | |||
} | |||
@@ -3,15 +3,15 @@ | |||
namespace DotNetCore.CAP.Abstractions | |||
{ | |||
/// <summary> | |||
/// A context for consumers, it used to be provider wapper of method description and received message. | |||
/// A context for consumers, it used to be provider wrapper of method description and received message. | |||
/// </summary> | |||
public class ConsumerContext | |||
{ | |||
/// <summary> | |||
/// create a new instance of <see cref="ConsumerContext"/> . | |||
/// create a new instance of <see cref="ConsumerContext" /> . | |||
/// </summary> | |||
/// <param name="descriptor">consumer method descriptor. </param> | |||
/// <param name="message"> reveied message.</param> | |||
/// <param name="message"> received message.</param> | |||
public ConsumerContext(ConsumerExecutorDescriptor descriptor, MessageContext message) | |||
{ | |||
ConsumerDescriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor)); | |||
@@ -24,7 +24,7 @@ namespace DotNetCore.CAP.Abstractions | |||
public ConsumerExecutorDescriptor ConsumerDescriptor { get; set; } | |||
/// <summary> | |||
/// consumer reveived message. | |||
/// consumer received message. | |||
/// </summary> | |||
public MessageContext DeliverMessage { get; set; } | |||
} |
@@ -3,22 +3,22 @@ | |||
namespace DotNetCore.CAP.Abstractions | |||
{ | |||
/// <summary> | |||
/// Defines an interface for selecting an cosumer service method to invoke for the current message. | |||
/// Defines an interface for selecting an consumer service method to invoke for the current message. | |||
/// </summary> | |||
public interface IConsumerServiceSelector | |||
{ | |||
/// <summary> | |||
/// Selects a set of <see cref="ConsumerExecutorDescriptor"/> candidates for the current message associated with | |||
/// </summary> | |||
/// <returns>A set of <see cref="ConsumerExecutorDescriptor"/> candidates or <c>null</c>.</returns> | |||
/// Selects a set of <see cref="ConsumerExecutorDescriptor" /> candidates for the current message associated with | |||
/// </summary> | |||
/// <returns>A set of <see cref="ConsumerExecutorDescriptor" /> candidates or <c>null</c>.</returns> | |||
IReadOnlyList<ConsumerExecutorDescriptor> SelectCandidates(); | |||
/// <summary> | |||
/// Selects the best <see cref="ConsumerExecutorDescriptor"/> candidate from <paramref name="candidates"/> for the | |||
/// Selects the best <see cref="ConsumerExecutorDescriptor" /> candidate from <paramref name="candidates" /> for the | |||
/// current message associated. | |||
/// </summary> | |||
/// <param name="key">topic or exchange router key.</param> | |||
/// <param name="candidates">the set of <see cref="ConsumerExecutorDescriptor"/> candidates.</param> | |||
/// <param name="candidates">the set of <see cref="ConsumerExecutorDescriptor" /> candidates.</param> | |||
/// <returns></returns> | |||
ConsumerExecutorDescriptor | |||
SelectBestCandidate(string key, IReadOnlyList<ConsumerExecutorDescriptor> candidates); | |||
@@ -8,4 +8,4 @@ namespace DotNetCore.CAP.Abstractions | |||
T DeSerialize<T>(string content) where T : CapMessageDto, new(); | |||
} | |||
} | |||
} |
@@ -8,22 +8,22 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding | |||
public struct ModelBindingResult | |||
{ | |||
/// <summary> | |||
/// Creates a <see cref="ModelBindingResult"/> representing a failed model binding operation. | |||
/// Creates a <see cref="ModelBindingResult" /> representing a failed model binding operation. | |||
/// </summary> | |||
/// <returns>A <see cref="ModelBindingResult"/> representing a failed model binding operation.</returns> | |||
/// <returns>A <see cref="ModelBindingResult" /> representing a failed model binding operation.</returns> | |||
public static ModelBindingResult Failed() | |||
{ | |||
return new ModelBindingResult(model: null, isSuccess: false); | |||
return new ModelBindingResult(null, false); | |||
} | |||
/// <summary> | |||
/// Creates a <see cref="ModelBindingResult"/> representing a successful model binding operation. | |||
/// Creates a <see cref="ModelBindingResult" /> representing a successful model binding operation. | |||
/// </summary> | |||
/// <param name="model">The model value. May be <c>null.</c></param> | |||
/// <returns>A <see cref="ModelBindingResult"/> representing a successful model bind.</returns> | |||
/// <returns>A <see cref="ModelBindingResult" /> representing a successful model bind.</returns> | |||
public static ModelBindingResult Success(object model) | |||
{ | |||
return new ModelBindingResult(model, isSuccess: true); | |||
return new ModelBindingResult(model, true); | |||
} | |||
private ModelBindingResult(object model, bool isSuccess) | |||
@@ -42,26 +42,16 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding | |||
public override string ToString() | |||
{ | |||
if (IsSuccess) | |||
{ | |||
return $"Success '{Model}'"; | |||
} | |||
else | |||
{ | |||
return $"Failed"; | |||
} | |||
return $"Failed"; | |||
} | |||
public override bool Equals(object obj) | |||
{ | |||
var other = obj as ModelBindingResult?; | |||
if (other == null) | |||
{ | |||
return false; | |||
} | |||
else | |||
{ | |||
return Equals(other.Value); | |||
} | |||
return Equals(other.Value); | |||
} | |||
public override int GetHashCode() | |||
@@ -81,10 +71,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding | |||
} | |||
/// <summary> | |||
/// Compares <see cref="ModelBindingResult"/> objects for equality. | |||
/// Compares <see cref="ModelBindingResult" /> objects for equality. | |||
/// </summary> | |||
/// <param name="x">A <see cref="ModelBindingResult"/>.</param> | |||
/// <param name="y">A <see cref="ModelBindingResult"/>.</param> | |||
/// <param name="x">A <see cref="ModelBindingResult" />.</param> | |||
/// <param name="y">A <see cref="ModelBindingResult" />.</param> | |||
/// <returns><c>true</c> if the objects are equal, otherwise <c>false</c>.</returns> | |||
public static bool operator ==(ModelBindingResult x, ModelBindingResult y) | |||
{ | |||
@@ -92,10 +82,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding | |||
} | |||
/// <summary> | |||
/// Compares <see cref="ModelBindingResult"/> objects for inequality. | |||
/// Compares <see cref="ModelBindingResult" /> objects for inequality. | |||
/// </summary> | |||
/// <param name="x">A <see cref="ModelBindingResult"/>.</param> | |||
/// <param name="y">A <see cref="ModelBindingResult"/>.</param> | |||
/// <param name="x">A <see cref="ModelBindingResult" />.</param> | |||
/// <param name="y">A <see cref="ModelBindingResult" />.</param> | |||
/// <returns><c>true</c> if the objects are not equal, otherwise <c>false</c>.</returns> | |||
public static bool operator !=(ModelBindingResult x, ModelBindingResult y) | |||
{ | |||
@@ -4,7 +4,7 @@ namespace DotNetCore.CAP.Abstractions | |||
{ | |||
/// <inheritdoc /> | |||
/// <summary> | |||
/// An abstract attribute that for kafka attribute or rabbitmq attribute | |||
/// An abstract attribute that for kafka attribute or rabbit mq attribute | |||
/// </summary> | |||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] | |||
public abstract class TopicAttribute : Attribute | |||
@@ -20,8 +20,8 @@ namespace DotNetCore.CAP.Abstractions | |||
public string Name { get; } | |||
/// <summary> | |||
/// kafak --> groups.id | |||
/// rabbitmq --> queue.name | |||
/// kafka --> groups.id | |||
/// rabbit MQ --> queue.name | |||
/// </summary> | |||
public string Group { get; set; } = "cap.default.group"; | |||
} |
@@ -7,21 +7,19 @@ using Microsoft.Extensions.DependencyInjection; | |||
namespace Microsoft.AspNetCore.Builder | |||
{ | |||
/// <summary> | |||
/// app extensions for <see cref="IApplicationBuilder"/> | |||
/// app extensions for <see cref="IApplicationBuilder" /> | |||
/// </summary> | |||
public static class AppBuilderExtensions | |||
{ | |||
///<summary> | |||
/// <summary> | |||
/// Enables cap for the current application | |||
/// </summary> | |||
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param> | |||
/// <returns>The <see cref="IApplicationBuilder"/> instance this method extends.</returns> | |||
/// <param name="app">The <see cref="IApplicationBuilder" /> instance this method extends.</param> | |||
/// <returns>The <see cref="IApplicationBuilder" /> instance this method extends.</returns> | |||
public static IApplicationBuilder UseCap(this IApplicationBuilder app) | |||
{ | |||
if (app == null) | |||
{ | |||
throw new ArgumentNullException(nameof(app)); | |||
} | |||
CheckRequirement(app); | |||
@@ -33,9 +31,7 @@ namespace Microsoft.AspNetCore.Builder | |||
if (provider.GetService<DashboardOptions>() != null) | |||
{ | |||
if (provider.GetService<DiscoveryOptions>() != null) | |||
{ | |||
app.UseMiddleware<GatewayProxyMiddleware>(); | |||
} | |||
app.UseMiddleware<DashboardMiddleware>(); | |||
} | |||
@@ -46,21 +42,18 @@ namespace Microsoft.AspNetCore.Builder | |||
{ | |||
var marker = app.ApplicationServices.GetService<CapMarkerService>(); | |||
if (marker == null) | |||
{ | |||
throw new InvalidOperationException("AddCap() must be called on the service collection. eg: services.AddCap(...)"); | |||
} | |||
throw new InvalidOperationException( | |||
"AddCap() must be called on the service collection. eg: services.AddCap(...)"); | |||
var messageQueuemarker = app.ApplicationServices.GetService<CapMessageQueueMakerService>(); | |||
if (messageQueuemarker == null) | |||
{ | |||
throw new InvalidOperationException("You must be config used message queue provider at AddCap() options! eg: services.AddCap(options=>{ options.UseKafka(...) })"); | |||
} | |||
var messageQueueMarker = app.ApplicationServices.GetService<CapMessageQueueMakerService>(); | |||
if (messageQueueMarker == null) | |||
throw new InvalidOperationException( | |||
"You must be config used message queue provider at AddCap() options! eg: services.AddCap(options=>{ options.UseKafka(...) })"); | |||
var databaseMarker = app.ApplicationServices.GetService<CapDatabaseStorageMarkerService>(); | |||
if (databaseMarker == null) | |||
{ | |||
throw new InvalidOperationException("You must be config used database provider at AddCap() options! eg: services.AddCap(options=>{ options.UseSqlServer(...) })"); | |||
} | |||
throw new InvalidOperationException( | |||
"You must be config used database provider at AddCap() options! eg: services.AddCap(options=>{ options.UseSqlServer(...) })"); | |||
} | |||
} | |||
} |
@@ -15,7 +15,6 @@ namespace DotNetCore.CAP | |||
/// </summary> | |||
public class CapDatabaseStorageMarkerService | |||
{ | |||
} | |||
/// <summary> | |||
@@ -23,7 +22,6 @@ namespace DotNetCore.CAP | |||
/// </summary> | |||
public class CapMessageQueueMakerService | |||
{ | |||
} | |||
/// <summary> | |||
@@ -37,7 +35,7 @@ namespace DotNetCore.CAP | |||
} | |||
/// <summary> | |||
/// Gets the <see cref="IServiceCollection"/> where MVC services are configured. | |||
/// Gets the <see cref="IServiceCollection" /> where MVC services are configured. | |||
/// </summary> | |||
public IServiceCollection Services { get; } | |||
@@ -51,7 +49,7 @@ namespace DotNetCore.CAP | |||
} | |||
/// <summary> | |||
/// Add an <see cref="ICapPublisher"/>. | |||
/// Add an <see cref="ICapPublisher" />. | |||
/// </summary> | |||
/// <typeparam name="T">The type of the service.</typeparam> | |||
public virtual CapBuilder AddProducerService<T>() | |||
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using DotNetCore.CAP.Models; | |||
namespace DotNetCore.CAP | |||
{ | |||
@@ -8,8 +9,6 @@ namespace DotNetCore.CAP | |||
/// </summary> | |||
public class CapOptions | |||
{ | |||
internal IList<ICapOptionsExtension> Extensions { get; } | |||
/// <summary> | |||
/// Default value for polling delay timeout, in seconds. | |||
/// </summary> | |||
@@ -21,7 +20,7 @@ namespace DotNetCore.CAP | |||
public const int DefaultQueueProcessorCount = 2; | |||
/// <summary> | |||
/// Default succeeded message expriation timespan, in seconds. | |||
/// Default succeeded message expiration time span, in seconds. | |||
/// </summary> | |||
public const int DefaultSucceedMessageExpirationAfter = 24 * 3600; | |||
@@ -39,8 +38,10 @@ namespace DotNetCore.CAP | |||
Extensions = new List<ICapOptionsExtension>(); | |||
} | |||
internal IList<ICapOptionsExtension> Extensions { get; } | |||
/// <summary> | |||
/// Productor job polling delay time. | |||
/// Producer job polling delay time. | |||
/// Default is 15 sec. | |||
/// </summary> | |||
public int PollingDelay { get; set; } | |||
@@ -52,8 +53,8 @@ namespace DotNetCore.CAP | |||
public int QueueProcessorCount { get; set; } | |||
/// <summary> | |||
/// Sent or received succeed message after timespan of due, then the message will be deleted at due time. | |||
/// Dafault is 24*3600 seconds. | |||
/// Sent or received succeed message after timespan of due, then the message will be deleted at due time. | |||
/// Default is 24*3600 seconds. | |||
/// </summary> | |||
public int SucceedMessageExpiredAfter { get; set; } | |||
@@ -66,7 +67,7 @@ namespace DotNetCore.CAP | |||
/// <summary> | |||
/// We’ll invoke this call-back with message type,name,content when requeue failed message. | |||
/// </summary> | |||
public Action<Models.MessageType, string, string> FailedCallback { get; set; } | |||
public Action<MessageType, string, string> FailedCallback { get; set; } | |||
/// <summary> | |||
/// Registers an extension that will be executed when building services. | |||
@@ -11,16 +11,16 @@ using Microsoft.Extensions.DependencyInjection.Extensions; | |||
namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
/// <summary> | |||
/// Contains extension methods to <see cref="IServiceCollection"/> for configuring consistence services. | |||
/// Contains extension methods to <see cref="IServiceCollection" /> for configuring consistence services. | |||
/// </summary> | |||
public static class ServiceCollectionExtensions | |||
{ | |||
/// <summary> | |||
/// Adds and configures the consistence services for the consitence. | |||
/// Adds and configures the consistence services for the consistency. | |||
/// </summary> | |||
/// <param name="services">The services available in the application.</param> | |||
/// <param name="setupAction">An action to configure the <see cref="CapOptions"/>.</param> | |||
/// <returns>An <see cref="CapBuilder"/> for application services.</returns> | |||
/// <param name="setupAction">An action to configure the <see cref="CapOptions" />.</param> | |||
/// <returns>An <see cref="CapBuilder" /> for application services.</returns> | |||
public static CapBuilder AddCap( | |||
this IServiceCollection services, | |||
Action<CapOptions> setupAction) | |||
@@ -42,7 +42,7 @@ namespace Microsoft.Extensions.DependencyInjection | |||
services.AddSingleton<IProcessingServer, CapProcessingServer>(); | |||
services.AddSingleton<IBootstrapper, DefaultBootstrapper>(); | |||
services.AddSingleton<IStateChanger, StateChanger>(); | |||
//Processors | |||
services.AddTransient<PublishQueuer>(); | |||
services.AddTransient<SubscribeQueuer>(); | |||
@@ -51,15 +51,13 @@ namespace Microsoft.Extensions.DependencyInjection | |||
//Executors | |||
services.AddSingleton<IQueueExecutorFactory, QueueExecutorFactory>(); | |||
services.AddSingleton<IQueueExecutor, SubscibeQueueExecutor>(); | |||
services.AddSingleton<IQueueExecutor, SubscribeQueueExecutor>(); | |||
//Options and extension service | |||
var options = new CapOptions(); | |||
setupAction(options); | |||
foreach (var serviceExtension in options.Extensions) | |||
{ | |||
serviceExtension.AddServices(services); | |||
} | |||
services.AddSingleton(options); | |||
return new CapBuilder(services); | |||
@@ -69,19 +67,13 @@ namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
var consumerListenerServices = new List<KeyValuePair<Type, Type>>(); | |||
foreach (var rejectedServices in services) | |||
{ | |||
if (rejectedServices.ImplementationType != null | |||
&& typeof(ICapSubscribe).IsAssignableFrom(rejectedServices.ImplementationType)) | |||
{ | |||
consumerListenerServices.Add(new KeyValuePair<Type, Type>(typeof(ICapSubscribe), | |||
rejectedServices.ImplementationType)); | |||
} | |||
} | |||
foreach (var service in consumerListenerServices) | |||
{ | |||
services.AddTransient(service.Key, service.Value); | |||
} | |||
} | |||
} | |||
} |
@@ -6,26 +6,26 @@ using System.Threading; | |||
namespace DotNetCore.CAP | |||
{ | |||
#region Cache<T> class | |||
/// <summary> | |||
/// This is a generic cache subsystem based on key/value pairs, where key is generic, too. Key must be unique. | |||
/// Every cache entry has its own timeout. | |||
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on <see cref="ThreadPool"/> threads). | |||
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on | |||
/// <see cref="ThreadPool" /> threads). | |||
/// </summary> | |||
public class Cache<K, T> : IDisposable | |||
{ | |||
#region Constructor and class members | |||
/// <summary> | |||
/// Initializes a new instance of the <see cref="Cache{K,T}"/> class. | |||
/// </summary> | |||
public Cache() { } | |||
private Dictionary<K, T> _cache = new Dictionary<K, T>(); | |||
private Dictionary<K, Timer> _timers = new Dictionary<K, Timer>(); | |||
private ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); | |||
private readonly Dictionary<K, T> _cache = new Dictionary<K, T>(); | |||
private readonly Dictionary<K, Timer> _timers = new Dictionary<K, Timer>(); | |||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); | |||
#endregion | |||
#region IDisposable implementation & Clear | |||
private bool disposed = false; | |||
private bool disposed; | |||
/// <summary> | |||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. | |||
@@ -40,7 +40,8 @@ namespace DotNetCore.CAP | |||
/// Releases unmanaged and - optionally - managed resources. | |||
/// </summary> | |||
/// <param name="disposing"> | |||
/// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> | |||
/// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources. | |||
/// </param> | |||
protected virtual void Dispose(bool disposing) | |||
{ | |||
if (!disposed) | |||
@@ -67,20 +68,26 @@ namespace DotNetCore.CAP | |||
{ | |||
try | |||
{ | |||
foreach (Timer t in _timers.Values) | |||
foreach (var t in _timers.Values) | |||
t.Dispose(); | |||
} | |||
catch | |||
{ } | |||
{ | |||
} | |||
_timers.Clear(); | |||
_cache.Clear(); | |||
} | |||
finally { _locker.ExitWriteLock(); } | |||
finally | |||
{ | |||
_locker.ExitWriteLock(); | |||
} | |||
} | |||
#endregion | |||
#region CheckTimer | |||
// Checks whether a specific timer already exists and adds a new one, if not | |||
private void CheckTimer(K key, TimeSpan? cacheTimeout, bool restartTimerIfExists) | |||
{ | |||
@@ -89,41 +96,48 @@ namespace DotNetCore.CAP | |||
if (_timers.TryGetValue(key, out timer)) | |||
{ | |||
if (restartTimerIfExists) | |||
{ | |||
timer.Change( | |||
cacheTimeout ?? Timeout.InfiniteTimeSpan, | |||
Timeout.InfiniteTimeSpan); | |||
} | |||
} | |||
else | |||
{ | |||
_timers.Add( | |||
key, | |||
new Timer( | |||
new TimerCallback(RemoveByTimer), | |||
RemoveByTimer, | |||
key, | |||
cacheTimeout ?? Timeout.InfiniteTimeSpan, | |||
Timeout.InfiniteTimeSpan)); | |||
} | |||
} | |||
private void RemoveByTimer(object state) | |||
{ | |||
Remove((K)state); | |||
Remove((K) state); | |||
} | |||
#endregion | |||
#region AddOrUpdate, Get, Remove, Exists, Clear | |||
/// <summary> | |||
/// Adds or updates the specified cache-key with the specified cacheObject and applies a specified timeout (in seconds) to this key. | |||
/// Adds or updates the specified cache-key with the specified cacheObject and applies a specified timeout (in seconds) | |||
/// to this key. | |||
/// </summary> | |||
/// <param name="key">The cache-key to add or update.</param> | |||
/// <param name="cacheObject">The cache object to store.</param> | |||
/// <param name="cacheTimeout">The cache timeout (lifespan) of this object. Must be 1 or greater. | |||
/// Specify Timeout.Infinite to keep the entry forever.</param> | |||
/// <param name="restartTimerIfExists">(Optional). If set to <c>true</c>, the timer for this cacheObject will be reset if the object already | |||
/// exists in the cache. (Default = false).</param> | |||
/// <param name="cacheTimeout"> | |||
/// The cache timeout (lifespan) of this object. Must be 1 or greater. | |||
/// Specify Timeout.Infinite to keep the entry forever. | |||
/// </param> | |||
/// <param name="restartTimerIfExists"> | |||
/// (Optional). If set to <c>true</c>, the timer for this cacheObject will be reset if the object already | |||
/// exists in the cache. (Default = false). | |||
/// </param> | |||
public void AddOrUpdate(K key, T cacheObject, TimeSpan? cacheTimeout, bool restartTimerIfExists = false) | |||
{ | |||
if (disposed) return; | |||
if (disposed) return; | |||
_locker.EnterWriteLock(); | |||
try | |||
@@ -135,11 +149,15 @@ namespace DotNetCore.CAP | |||
else | |||
_cache[key] = cacheObject; | |||
} | |||
finally { _locker.ExitWriteLock(); } | |||
finally | |||
{ | |||
_locker.ExitWriteLock(); | |||
} | |||
} | |||
/// <summary> | |||
/// Adds or updates the specified cache-key with the specified cacheObject and applies <c>Timeout.Infinite</c> to this key. | |||
/// Adds or updates the specified cache-key with the specified cacheObject and applies <c>Timeout.Infinite</c> to this | |||
/// key. | |||
/// </summary> | |||
/// <param name="key">The cache-key to add or update.</param> | |||
/// <param name="cacheObject">The cache object to store.</param> | |||
@@ -168,9 +186,12 @@ namespace DotNetCore.CAP | |||
try | |||
{ | |||
T rv; | |||
return (_cache.TryGetValue(key, out rv) ? rv : default(T)); | |||
return _cache.TryGetValue(key, out rv) ? rv : default(T); | |||
} | |||
finally | |||
{ | |||
_locker.ExitReadLock(); | |||
} | |||
finally { _locker.ExitReadLock(); } | |||
} | |||
/// <summary> | |||
@@ -192,7 +213,10 @@ namespace DotNetCore.CAP | |||
{ | |||
return _cache.TryGetValue(key, out value); | |||
} | |||
finally { _locker.ExitReadLock(); } | |||
finally | |||
{ | |||
_locker.ExitReadLock(); | |||
} | |||
} | |||
/// <summary> | |||
@@ -207,18 +231,26 @@ namespace DotNetCore.CAP | |||
try | |||
{ | |||
var removers = (from k in _cache.Keys.Cast<K>() | |||
where keyPattern(k) | |||
select k).ToList(); | |||
where keyPattern(k) | |||
select k).ToList(); | |||
foreach (K workKey in removers) | |||
foreach (var workKey in removers) | |||
{ | |||
try { _timers[workKey].Dispose(); } | |||
catch { } | |||
try | |||
{ | |||
_timers[workKey].Dispose(); | |||
} | |||
catch | |||
{ | |||
} | |||
_timers.Remove(workKey); | |||
_cache.Remove(workKey); | |||
} | |||
} | |||
finally { _locker.ExitWriteLock(); } | |||
finally | |||
{ | |||
_locker.ExitWriteLock(); | |||
} | |||
} | |||
/// <summary> | |||
@@ -235,13 +267,21 @@ namespace DotNetCore.CAP | |||
{ | |||
if (_cache.ContainsKey(key)) | |||
{ | |||
try { _timers[key].Dispose(); } | |||
catch { } | |||
try | |||
{ | |||
_timers[key].Dispose(); | |||
} | |||
catch | |||
{ | |||
} | |||
_timers.Remove(key); | |||
_cache.Remove(key); | |||
} | |||
} | |||
finally { _locker.ExitWriteLock(); } | |||
finally | |||
{ | |||
_locker.ExitWriteLock(); | |||
} | |||
} | |||
/// <summary> | |||
@@ -258,33 +298,40 @@ namespace DotNetCore.CAP | |||
{ | |||
return _cache.ContainsKey(key); | |||
} | |||
finally { _locker.ExitReadLock(); } | |||
finally | |||
{ | |||
_locker.ExitReadLock(); | |||
} | |||
} | |||
#endregion | |||
} | |||
#endregion | |||
#region Other Cache classes (derived) | |||
/// <summary> | |||
/// This is a generic cache subsystem based on key/value pairs, where key is a string. | |||
/// You can add any item to this cache as long as the key is unique, so treat keys as something like namespaces and build them with a | |||
/// You can add any item to this cache as long as the key is unique, so treat keys as something like namespaces and | |||
/// build them with a | |||
/// specific system/syntax in your application. | |||
/// Every cache entry has its own timeout. | |||
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on <see cref="ThreadPool"/> threads). | |||
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on | |||
/// <see cref="ThreadPool" /> threads). | |||
/// </summary> | |||
//public class Cache<T> : Cache<string, T> | |||
//{ | |||
//} | |||
/// <summary> | |||
/// The non-generic Cache class instanciates a Cache{object} that can be used with any type of (mixed) contents. | |||
/// It also publishes a static <c>.Global</c> member, so a cache can be used even without creating a dedicated instance. | |||
/// It also publishes a static <c>.Global</c> member, so a cache can be used even without creating a dedicated | |||
/// instance. | |||
/// The <c>.Global</c> member is lazy instanciated. | |||
/// </summary> | |||
public class CapCache : Cache<string, object> | |||
{ | |||
#region Static Global Cache instance | |||
private static Lazy<CapCache> global = new Lazy<CapCache>(); | |||
private static readonly Lazy<CapCache> global = new Lazy<CapCache>(); | |||
/// <summary> | |||
/// Gets the global shared cache instance valid for the entire process. | |||
/// </summary> | |||
@@ -292,8 +339,9 @@ namespace DotNetCore.CAP | |||
/// The global shared cache instance. | |||
/// </value> | |||
public static CapCache Global => global.Value; | |||
#endregion | |||
} | |||
#endregion | |||
} | |||
#endregion | |||
} |
@@ -28,7 +28,7 @@ namespace DotNetCore.CAP.Dashboard | |||
_command(context, id); | |||
} | |||
context.Response.StatusCode = (int)HttpStatusCode.NoContent; | |||
context.Response.StatusCode = (int) HttpStatusCode.NoContent; | |||
} | |||
} | |||
} |
@@ -10,12 +10,13 @@ namespace DotNetCore.CAP | |||
{ | |||
public class DashboardMiddleware | |||
{ | |||
private readonly DashboardOptions _options; | |||
private readonly RequestDelegate _next; | |||
private readonly IStorage _storage; | |||
private readonly DashboardOptions _options; | |||
private readonly RouteCollection _routes; | |||
private readonly IStorage _storage; | |||
public DashboardMiddleware(RequestDelegate next, DashboardOptions options, IStorage storage, 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)); | |||
@@ -40,17 +41,15 @@ namespace DotNetCore.CAP | |||
var findResult = _routes.FindDispatcher(context.Request.Path.Value); | |||
if (findResult == null) | |||
{ | |||
return _next.Invoke(context); | |||
} | |||
if (_options.Authorization.Any(filter => !filter.Authorize(dashboardContext))) | |||
{ | |||
var isAuthenticated = context.User?.Identity?.IsAuthenticated; | |||
context.Response.StatusCode = isAuthenticated == true | |||
? (int)HttpStatusCode.Forbidden | |||
: (int)HttpStatusCode.Unauthorized; | |||
? (int) HttpStatusCode.Forbidden | |||
: (int) HttpStatusCode.Unauthorized; | |||
return Task.CompletedTask; | |||
} | |||
@@ -66,4 +65,4 @@ namespace DotNetCore.CAP | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -10,7 +10,7 @@ namespace DotNetCore.CAP | |||
{ | |||
AppPath = "/"; | |||
PathMatch = "/cap"; | |||
Authorization = new[] { new LocalRequestsOnlyAuthorizationFilter() }; | |||
Authorization = new[] {new LocalRequestsOnlyAuthorizationFilter()}; | |||
StatsPollingInterval = 2000; | |||
} | |||
@@ -28,5 +28,4 @@ namespace DotNetCore.CAP | |||
/// </summary> | |||
public int StatsPollingInterval { get; set; } | |||
} | |||
} | |||
} |
@@ -1,12 +1,12 @@ | |||
using System; | |||
using DotNetCore.CAP; | |||
using DotNetCore.CAP.Dashboard; | |||
using DotNetCore.CAP.Dashboard.GatewayProxy; | |||
using DotNetCore.CAP.Dashboard.GatewayProxy.Requester; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace DotNetCore.CAP | |||
{ | |||
using Dashboard; | |||
using Dashboard.GatewayProxy; | |||
using Dashboard.GatewayProxy.Requester; | |||
using Microsoft.Extensions.DependencyInjection; | |||
internal sealed class DashboardOptionsExtension : ICapOptionsExtension | |||
{ | |||
private readonly Action<DashboardOptions> _options; | |||
@@ -20,8 +20,8 @@ namespace DotNetCore.CAP | |||
{ | |||
var dashboardOptions = new DashboardOptions(); | |||
_options?.Invoke(dashboardOptions); | |||
services.AddSingleton(dashboardOptions); | |||
services.AddSingleton(DashboardRoutes.Routes); | |||
services.AddSingleton(dashboardOptions); | |||
services.AddSingleton(DashboardRoutes.Routes); | |||
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>(); | |||
services.AddSingleton<IHttpClientCache, MemoryHttpClientCache>(); | |||
services.AddSingleton<IRequestMapper, RequestMapper>(); | |||
@@ -31,13 +31,11 @@ namespace DotNetCore.CAP | |||
namespace Microsoft.Extensions.DependencyInjection | |||
{ | |||
using DotNetCore.CAP; | |||
public static class CapOptionsExtensions | |||
{ | |||
public static CapOptions UseDashboard(this CapOptions capOptions) | |||
{ | |||
return capOptions.UseDashboard(opt => {}); | |||
return capOptions.UseDashboard(opt => { }); | |||
} | |||
public static CapOptions UseDashboard(this CapOptions capOptions, Action<DashboardOptions> options) | |||
@@ -22,12 +22,10 @@ namespace DotNetCore.CAP.Dashboard | |||
protected override void WriteResponse(DashboardResponse response) | |||
{ | |||
foreach (var resourceName in _resourceNames) | |||
{ | |||
WriteResource( | |||
response, | |||
_assembly, | |||
$"{_baseNamespace}.{resourceName}"); | |||
} | |||
} | |||
} | |||
} |
@@ -20,18 +20,14 @@ namespace DotNetCore.CAP.Dashboard | |||
if (!"POST".Equals(request.Method, StringComparison.OrdinalIgnoreCase)) | |||
{ | |||
response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; | |||
response.StatusCode = (int) HttpStatusCode.MethodNotAllowed; | |||
return Task.FromResult(false); | |||
} | |||
if (_command(context)) | |||
{ | |||
response.StatusCode = (int)HttpStatusCode.NoContent; | |||
} | |||
response.StatusCode = (int) HttpStatusCode.NoContent; | |||
else | |||
{ | |||
response.StatusCode = 422; | |||
} | |||
return Task.FromResult(true); | |||
} | |||
@@ -12,34 +12,32 @@ body { | |||
} | |||
/* Wrapper for page content to push down footer */ | |||
#wrap { | |||
min-height: 100%; | |||
height: auto !important; | |||
height: 100%; | |||
/* Negative indent footer by its height */ | |||
margin: 0 auto -60px; | |||
min-height: 100%; | |||
/* Pad bottom by footer height */ | |||
padding: 0 0 60px; | |||
} | |||
/* Set the fixed height of the footer here */ | |||
#footer { | |||
background-color: #f5f5f5; | |||
} | |||
#footer { background-color: #f5f5f5; } | |||
/* Custom page CSS | |||
-------------------------------------------------- */ | |||
.container .credit { | |||
margin: 20px 0; | |||
} | |||
.container .credit { margin: 20px 0; } | |||
.page-header { | |||
margin-top: 0; | |||
overflow: hidden; | |||
white-space: nowrap; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.btn-death { | |||
@@ -48,37 +46,33 @@ body { | |||
color: #fff; | |||
} | |||
.btn-death:hover { | |||
background-color: #666; | |||
border-color: #555; | |||
color: #fff; | |||
} | |||
.list-group .list-group-item .glyphicon { | |||
margin-right: 3px; | |||
.btn-death:hover { | |||
background-color: #666; | |||
border-color: #555; | |||
color: #fff; | |||
} | |||
.list-group .list-group-item .glyphicon { margin-right: 3px; } | |||
.breadcrumb { | |||
margin-bottom: 10px; | |||
background-color: inherit; | |||
margin-bottom: 10px; | |||
padding: 0; | |||
} | |||
.btn-toolbar-label { | |||
padding: 7px 0; | |||
vertical-align: middle; | |||
display: inline-block; | |||
margin-left: 5px; | |||
padding: 7px 0; | |||
vertical-align: middle; | |||
} | |||
.btn-toolbar-label-sm { | |||
padding: 5px 0; | |||
} | |||
.btn-toolbar-label-sm { padding: 5px 0; } | |||
.btn-toolbar-spacer { | |||
width: 5px; | |||
display: inline-block; | |||
height: 1px; | |||
width: 5px; | |||
} | |||
a:hover .label-hover { | |||
@@ -86,31 +80,23 @@ a:hover .label-hover { | |||
color: #fff !important; | |||
} | |||
.expander { | |||
cursor: pointer; | |||
} | |||
.expander { cursor: pointer; } | |||
.expandable { | |||
display: none; | |||
} | |||
.expandable { display: none; } | |||
.table-inner { | |||
margin-bottom: 7px; | |||
font-size: 90%; | |||
margin-bottom: 7px; | |||
} | |||
.min-width { | |||
width: 1%; | |||
white-space: nowrap; | |||
width: 1%; | |||
} | |||
.align-right { | |||
text-align: right; | |||
} | |||
.align-right { text-align: right; } | |||
.table > tbody > tr.hover:hover > td, .table > tbody > tr.hover:hover > th { | |||
background-color: #f9f9f9; | |||
} | |||
.table > tbody > tr.hover:hover > td, .table > tbody > tr.hover:hover > th { background-color: #f9f9f9; } | |||
.table > tbody > tr.highlight > td, .table > tbody > tr.highlight > th { | |||
background-color: #fcf8e3; | |||
@@ -122,246 +108,196 @@ a:hover .label-hover { | |||
border-color: #f5e8ce; | |||
} | |||
.word-break { | |||
word-break: break-all; | |||
} | |||
.word-break { word-break: break-all; } | |||
/* Statistics widget | |||
-------------------------------------------------- */ | |||
#stats .list-group-item { | |||
border-color: #e7e7e7; | |||
background-color: #f8f8f8; | |||
border-color: #e7e7e7; | |||
} | |||
#stats a.list-group-item { | |||
color: #777; | |||
} | |||
#stats a.list-group-item { color: #777; } | |||
#stats a.list-group-item:hover, | |||
#stats a.list-group-item:focus { | |||
color: #333; | |||
} | |||
#stats a.list-group-item:hover, | |||
#stats a.list-group-item:focus { color: #333; } | |||
#stats .list-group-item.active, | |||
#stats .list-group-item.active:hover, | |||
#stats .list-group-item.active:focus { | |||
color: #555; | |||
background-color: #e7e7e7; | |||
border-color: #e7e7e7; | |||
color: #555; | |||
} | |||
.table td.failed-job-details { | |||
padding-top: 0; | |||
padding-bottom: 0; | |||
border-top: none; | |||
background-color: #f5f5f5; | |||
border-top: none; | |||
padding-bottom: 0; | |||
padding-top: 0; | |||
} | |||
.obsolete-data, .obsolete-data a, .obsolete-data pre, .obsolete-data .label { | |||
color: #999; | |||
} | |||
.obsolete-data, .obsolete-data a, .obsolete-data pre, .obsolete-data .label { color: #999; } | |||
.obsolete-data pre, .obsolete-data .label { | |||
background-color: #f5f5f5; | |||
} | |||
.obsolete-data pre, .obsolete-data .label { background-color: #f5f5f5; } | |||
.obsolete-data .glyphicon-question-sign { | |||
font-size: 80%; | |||
color: #999; | |||
} | |||
.obsolete-data .glyphicon-question-sign { | |||
color: #999; | |||
font-size: 80%; | |||
} | |||
.stack-trace { | |||
padding: 10px; | |||
border: none; | |||
padding: 10px; | |||
} | |||
.st-type { | |||
font-weight: bold; | |||
} | |||
.st-type { font-weight: bold; } | |||
.st-param-name { | |||
color: #666; | |||
} | |||
.st-param-name { color: #666; } | |||
.st-file { | |||
color: #999; | |||
} | |||
.st-file { color: #999; } | |||
.st-method { | |||
color: #00008B; | |||
font-weight: bold; | |||
} | |||
.st-line { | |||
color: #8B008B; | |||
} | |||
.st-line { color: #8B008B; } | |||
.width-200 { | |||
width: 200px; | |||
} | |||
.width-200 { width: 200px; } | |||
.btn-toolbar-top { | |||
margin-bottom: 10px; | |||
} | |||
.btn-toolbar-top { margin-bottom: 10px; } | |||
.paginator .btn { | |||
color: #428bca; | |||
} | |||
.paginator .btn { color: #428bca; } | |||
.paginator .btn.active { | |||
color: #333; | |||
} | |||
.paginator .btn.active { color: #333; } | |||
/* Job Snippet styles */ | |||
.job-snippet { | |||
-ms-border-radius: 4px; | |||
background-color: #f5f5f5; | |||
border-radius: 4px; | |||
display: table; | |||
margin-bottom: 20px; | |||
padding: 15px; | |||
display: table; | |||
width: 100%; | |||
-ms-border-radius: 4px; | |||
border-radius: 4px; | |||
background-color: #f5f5f5; | |||
} | |||
.job-snippet > * { | |||
display: table-cell; | |||
vertical-align: top; | |||
} | |||
.job-snippet-code { | |||
.job-snippet > * { | |||
display: table-cell; | |||
vertical-align: top; | |||
} | |||
.job-snippet-code pre { | |||
border: none; | |||
margin: 0; | |||
background: inherit; | |||
padding: 0; | |||
-ms-border-radius: 0; | |||
border-radius: 0; | |||
font-size: 14px; | |||
} | |||
.job-snippet-code { vertical-align: top; } | |||
.job-snippet-code code { | |||
display: block; | |||
color: black; | |||
background-color: #f5f5f5; | |||
} | |||
.job-snippet-code pre { | |||
-ms-border-radius: 0; | |||
background: inherit; | |||
border: none; | |||
border-radius: 0; | |||
font-size: 14px; | |||
margin: 0; | |||
padding: 0; | |||
} | |||
.job-snippet-code pre .comment { | |||
color: rgb(0, 128, 0); | |||
} | |||
.job-snippet-code code { | |||
background-color: #f5f5f5; | |||
color: black; | |||
display: block; | |||
} | |||
.job-snippet-code pre .keyword { | |||
color: rgb(0, 0, 255); | |||
} | |||
.job-snippet-code pre .comment { color: rgb(0, 128, 0); } | |||
.job-snippet-code pre .string { | |||
color: rgb(163, 21, 21); | |||
} | |||
.job-snippet-code pre .keyword { color: rgb(0, 0, 255); } | |||
.job-snippet-code pre .type { | |||
color: rgb(43, 145, 175); | |||
} | |||
.job-snippet-code pre .string { color: rgb(163, 21, 21); } | |||
.job-snippet-code pre .xmldoc { | |||
color: rgb(128, 128, 128); | |||
} | |||
.job-snippet-code pre .type { color: rgb(43, 145, 175); } | |||
.job-snippet-code pre .xmldoc { color: rgb(128, 128, 128); } | |||
.job-snippet-properties { | |||
max-width: 200px; | |||
padding-left: 5px; | |||
} | |||
.job-snippet-properties dl { | |||
margin: 0; | |||
} | |||
.job-snippet-properties dl { margin: 0; } | |||
.job-snippet-properties dl dt { | |||
color: #999; | |||
text-shadow: 0 1px white; | |||
font-weight: normal; | |||
} | |||
.job-snippet-properties dl dd { | |||
margin-left: 0; | |||
margin-bottom: 5px; | |||
} | |||
.job-snippet-properties pre { | |||
background-color: white; | |||
-webkit-box-shadow: none; | |||
-ms-box-shadow: none; | |||
padding: 2px 4px; | |||
border: none; | |||
margin: 0; | |||
} | |||
.job-snippet-properties dl dt { | |||
color: #999; | |||
font-weight: normal; | |||
text-shadow: 0 1px white; | |||
} | |||
.job-snippet-properties code { | |||
color: black; | |||
} | |||
.job-snippet-properties dl dd { | |||
margin-bottom: 5px; | |||
margin-left: 0; | |||
} | |||
.job-snippet-properties pre { | |||
-ms-box-shadow: none; | |||
-webkit-box-shadow: none; | |||
background-color: white; | |||
border: none; | |||
margin: 0; | |||
padding: 2px 4px; | |||
} | |||
.job-snippet-properties code { color: black; } | |||
.state-card { | |||
position: relative; | |||
display: block; | |||
margin-bottom: 7px; | |||
padding: 12px; | |||
-ms-border-radius: 3px; | |||
background-color: #fff; | |||
border: 1px solid #e5e5e5; | |||
-ms-border-radius: 3px; | |||
border-radius: 3px; | |||
display: block; | |||
margin-bottom: 7px; | |||
padding: 12px; | |||
position: relative; | |||
} | |||
.state-card-title { | |||
margin-bottom: 0; | |||
} | |||
.state-card-title { margin-bottom: 0; } | |||
.state-card-title .pull-right { | |||
margin-top: 3px; | |||
} | |||
.state-card-title .pull-right { margin-top: 3px; } | |||
.state-card-text { | |||
margin-top: 5px; | |||
margin-bottom: 0; | |||
margin-top: 5px; | |||
} | |||
.state-card h4 { | |||
margin-top: 0; | |||
} | |||
.state-card h4 { margin-top: 0; } | |||
.state-card-body { | |||
padding: 10px; | |||
margin: 10px -12px -12px -12px; | |||
-ms-border-bottom-left-radius: 3px; | |||
border-bottom-left-radius: 3px; | |||
-ms-border-bottom-right-radius: 3px; | |||
border-bottom-right-radius: 3px; | |||
background-color: #f5f5f5; | |||
border-bottom-left-radius: 3px; | |||
border-bottom-right-radius: 3px; | |||
margin: 10px -12px -12px -12px; | |||
padding: 10px; | |||
} | |||
.state-card-body dl { | |||
margin-top: 5px; | |||
margin-bottom: 0; | |||
} | |||
margin-bottom: 0; | |||
margin-top: 5px; | |||
} | |||
.state-card-body pre { | |||
white-space: pre-wrap; /* CSS 3 */ | |||
word-wrap: break-word; /* Internet Explorer 5.5+ */ | |||
background: transparent; | |||
padding: 0; | |||
} | |||
.state-card-body pre { | |||
background: transparent; | |||
padding: 0; | |||
white-space: pre-wrap; /* CSS 3 */ | |||
word-wrap: break-word; /* Internet Explorer 5.5+ */ | |||
} | |||
.state-card-body .stack-trace { | |||
background-color: transparent; | |||
padding: 0 20px; | |||
margin-bottom: 0; | |||
} | |||
.state-card-body .stack-trace { | |||
background-color: transparent; | |||
margin-bottom: 0; | |||
padding: 0 20px; | |||
} | |||
.state-card-body .exception-type { | |||
margin-top: 0; | |||
} | |||
.state-card-body .exception-type { margin-top: 0; } | |||
/* Job History styles */ | |||
@@ -370,17 +306,15 @@ a:hover .label-hover { | |||
opacity: 0.8; | |||
} | |||
.job-history.job-history-current { | |||
opacity: 1.0; | |||
} | |||
.job-history.job-history-current { opacity: 1.0; } | |||
.job-history-heading { | |||
padding: 5px 10px; | |||
color: #666; | |||
-ms-border-top-left-radius: 4px; | |||
border-top-left-radius: 4px; | |||
-ms-border-top-right-radius: 4px; | |||
border-top-left-radius: 4px; | |||
border-top-right-radius: 4px; | |||
color: #666; | |||
padding: 5px 10px; | |||
} | |||
.job-history-body { | |||
@@ -389,140 +323,118 @@ a:hover .label-hover { | |||
} | |||
.job-history-title { | |||
margin-top: 0; | |||
margin-bottom: 2px; | |||
margin-top: 0; | |||
} | |||
.job-history dl { | |||
margin-top: 5px; | |||
margin-bottom: 5px; | |||
margin-top: 5px; | |||
} | |||
.job-history .stack-trace { | |||
background-color: transparent; | |||
padding: 0 20px; | |||
margin-bottom: 5px; | |||
padding: 0 20px; | |||
} | |||
.job-history .exception-type { | |||
margin-top: 0; | |||
} | |||
.job-history .exception-type { margin-top: 0; } | |||
.job-history-current .job-history-heading, | |||
.job-history-current small { | |||
color: white; | |||
} | |||
.job-history-current small { color: white; } | |||
a.job-method { | |||
color: inherit; | |||
} | |||
a.job-method { color: inherit; } | |||
.list-group .glyphicon { | |||
top: 2px; | |||
} | |||
.list-group .glyphicon { top: 2px; } | |||
span.metric { | |||
-moz-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
-ms-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
-o-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
-webkit-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
background-color: transparent; | |||
border: solid 1px; | |||
border-radius: 10px; | |||
display: inline-block; | |||
min-width: 10px; | |||
padding: 2px 6px; | |||
font-size: 12px; | |||
line-height: 1; | |||
min-width: 10px; | |||
padding: 2px 6px; | |||
text-align: center; | |||
white-space: nowrap; | |||
vertical-align: baseline; | |||
background-color: transparent; | |||
border-radius: 10px; | |||
border: solid 1px; | |||
-webkit-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
-moz-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
-ms-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
-o-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
vertical-align: baseline; | |||
white-space: nowrap; | |||
} | |||
span.metric.highlighted { | |||
font-weight: bold; | |||
color: #fff !important; | |||
} | |||
span.metric.highlighted { | |||
color: #fff !important; | |||
font-weight: bold; | |||
} | |||
span.metric-default { | |||
color: #777; | |||
border-color: #777; | |||
color: #777; | |||
} | |||
span.metric-default.highlighted { | |||
background-color: #777; | |||
} | |||
span.metric-default.highlighted { background-color: #777; } | |||
div.metric { | |||
border: solid 1px transparent; | |||
-ms-border-radius: 4px; | |||
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); | |||
border: solid 1px transparent; | |||
border-radius: 4px; | |||
-webkit-box-shadow: 0 1px 1px rgba(0,0,0,.05); | |||
box-shadow: 0 1px 1px rgba(0,0,0,.05); | |||
box-shadow: 0 1px 1px rgba(0, 0, 0, .05); | |||
margin-bottom: 20px; | |||
transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; | |||
} | |||
div.metric .metric-body { | |||
padding: 15px 15px 0; | |||
font-size: 26px; | |||
text-align: center; | |||
} | |||
font-size: 26px; | |||
padding: 15px 15px 0; | |||
text-align: center; | |||
} | |||
div.metric .metric-description { | |||
padding: 0 15px 15px; | |||
text-align: center; | |||
} | |||
div.metric .metric-description { | |||
padding: 0 15px 15px; | |||
text-align: center; | |||
} | |||
div.metric.metric-default { | |||
border-color: #ddd; | |||
} | |||
div.metric.metric-default { border-color: #ddd; } | |||
div.metric-info, | |||
span.metric-info { | |||
color: #5bc0de; | |||
border-color: #5bc0de; | |||
color: #5bc0de; | |||
} | |||
span.metric-info.highlighted { | |||
background-color: #5bc0de; | |||
} | |||
span.metric-info.highlighted { background-color: #5bc0de; } | |||
div.metric-warning, | |||
span.metric-warning { | |||
color: #f0ad4e; | |||
border-color: #f0ad4e; | |||
color: #f0ad4e; | |||
} | |||
span.metric-warning.highlighted { | |||
background-color: #f0ad4e; | |||
} | |||
span.metric-warning.highlighted { background-color: #f0ad4e; } | |||
div.metric-success, | |||
span.metric-success { | |||
color: #5cb85c; | |||
border-color: #5cb85c; | |||
color: #5cb85c; | |||
} | |||
span.metric-success.highlighted { | |||
background-color: #5cb85c; | |||
} | |||
span.metric-success.highlighted { background-color: #5cb85c; } | |||
div.metric-danger, | |||
span.metric-danger { | |||
color: #d9534f; | |||
border-color: #d9534f; | |||
color: #d9534f; | |||
} | |||
span.metric-danger.highlighted { | |||
background-color: #d9534f; | |||
} | |||
span.metric-danger.highlighted { background-color: #d9534f; } | |||
span.metric-null, | |||
div.metric-null { | |||
display: none; | |||
} | |||
div.metric-null { display: none; } | |||
@media (min-width: 992px) { | |||
#stats { | |||
@@ -532,23 +444,17 @@ div.metric-null { | |||
} | |||
@media (min-width: 1200px) { | |||
#stats { | |||
width: 262.5px; | |||
} | |||
#stats { width: 262.5px; } | |||
} | |||
.subscribe-table td { | |||
vertical-align: middle !important; | |||
} | |||
.subscribe-table td { vertical-align: middle !important; } | |||
.subscribe-table td[rowspan] { | |||
font-weight: bold; | |||
} | |||
.subscribe-table td[rowspan] { font-weight: bold; } | |||
#legend { | |||
background: rgba(173, 169, 169, 0.13); | |||
color: #000; | |||
position: absolute; | |||
top: 110px; | |||
right: 20px; | |||
color: #000; | |||
} | |||
top: 110px; | |||
} |
@@ -1,16 +1,16 @@ | |||
(function (cap) { | |||
(function(cap) { | |||
cap.config = { | |||
pollInterval: $("#capConfig").data("pollinterval"), | |||
pollUrl: $("#capConfig").data("pollurl"), | |||
locale: document.documentElement.lang | |||
}; | |||
cap.Metrics = (function () { | |||
cap.Metrics = (function() { | |||
function Metrics() { | |||
this._metrics = {}; | |||
} | |||
Metrics.prototype.addElement = function (name, element) { | |||
Metrics.prototype.addElement = function(name, element) { | |||
if (!(name in this._metrics)) { | |||
this._metrics[name] = []; | |||
} | |||
@@ -18,7 +18,7 @@ | |||
this._metrics[name].push(element); | |||
}; | |||
Metrics.prototype.getElements = function (name) { | |||
Metrics.prototype.getElements = function(name) { | |||
if (!(name in this._metrics)) { | |||
return []; | |||
} | |||
@@ -26,11 +26,11 @@ | |||
return this._metrics[name]; | |||
}; | |||
Metrics.prototype.getNames = function () { | |||
var result = []; | |||
var metrics = this._metrics; | |||
Metrics.prototype.getNames = function() { | |||
const result = []; | |||
const metrics = this._metrics; | |||
for (var name in metrics) { | |||
for (let name in metrics) { | |||
if (metrics.hasOwnProperty(name)) { | |||
result.push(name); | |||
} | |||
@@ -42,14 +42,14 @@ | |||
return Metrics; | |||
})(); | |||
var BaseGraph = function () { | |||
var BaseGraph = function() { | |||
this.height = 200; | |||
}; | |||
BaseGraph.prototype.update = function () { | |||
var graph = this._graph; | |||
BaseGraph.prototype.update = function() { | |||
const graph = this._graph; | |||
var width = $(graph.element).innerWidth(); | |||
const width = $(graph.element).innerWidth(); | |||
if (width !== graph.width) { | |||
graph.configure({ | |||
width: width, | |||
@@ -60,48 +60,57 @@ | |||
graph.update(); | |||
}; | |||
BaseGraph.prototype._initGraph = function (element, settings, xSettings, ySettings) { | |||
BaseGraph.prototype._initGraph = function(element, settings, xSettings, ySettings) { | |||
var graph = this._graph = new Rickshaw.Graph($.extend({ | |||
element: element, | |||
width: $(element).innerWidth(), | |||
height: this.height, | |||
interpolation: 'linear', | |||
stroke: true | |||
}, settings)); | |||
const graph = this._graph = new Rickshaw.Graph($.extend({ | |||
element: element, | |||
width: $(element).innerWidth(), | |||
height: this.height, | |||
interpolation: "linear", | |||
stroke: true | |||
}, | |||
settings)); | |||
this._hoverDetail = new Rickshaw.Graph.HoverDetail({ | |||
graph: graph, | |||
yFormatter: function (y) { return Math.floor(y); }, | |||
xFormatter: function (x) { return moment(new Date(x * 1000)).format("LLLL"); } | |||
yFormatter: function(y) { return Math.floor(y); }, | |||
xFormatter: function(x) { return moment(new Date(x * 1000)).format("LLLL"); } | |||
}); | |||
if (xSettings) { | |||
this._xAxis = new Rickshaw.Graph.Axis.Time($.extend({ | |||
graph: graph, | |||
timeFixture: new Rickshaw.Fixtures.Time.Local() | |||
}, xSettings)); | |||
graph: graph, | |||
timeFixture: new Rickshaw.Fixtures.Time.Local() | |||
}, | |||
xSettings)); | |||
var legend = new Rickshaw.Graph.Legend({ | |||
element: document.querySelector('#legend'), | |||
const legend = new Rickshaw.Graph.Legend({ | |||
element: document.querySelector("#legend"), | |||
graph: graph | |||
}); | |||
} | |||
if (ySettings) { | |||
this._yAxis = new Rickshaw.Graph.Axis.Y($.extend({ | |||
graph: graph, | |||
tickFormat: Rickshaw.Fixtures.Number.formatKMBT | |||
}, ySettings)); | |||
graph: graph, | |||
tickFormat: Rickshaw.Fixtures.Number.formatKMBT | |||
}, | |||
ySettings)); | |||
} | |||
graph.render(); | |||
} | |||
}; | |||
cap.RealtimeGraph = (function () { | |||
cap.RealtimeGraph = (function() { | |||
function RealtimeGraph(element, | |||
pubSucceeded, pubFailed, pubSucceededStr, pubFailedStr, | |||
recSucceeded, recFailed, recSucceededStr, recFailedStr | |||
pubSucceeded, | |||
pubFailed, | |||
pubSucceededStr, | |||
pubFailedStr, | |||
recSucceeded, | |||
recFailed, | |||
recSucceededStr, | |||
recFailedStr | |||
) { | |||
this._pubSucceeded = pubSucceeded; | |||
this._pubSucceededStr = pubSucceededStr; | |||
@@ -115,48 +124,53 @@ | |||
this._recFailed = recFailed; | |||
this._recFailedStr = recFailedStr; | |||
this._initGraph(element, { | |||
renderer: 'bar', | |||
series: new Rickshaw.Series.FixedDuration([ | |||
{ | |||
name: pubSucceededStr, | |||
color: '#33cc33' | |||
},{ | |||
name: recSucceededStr, | |||
color: '#3333cc' | |||
},{ | |||
name: pubFailedStr, | |||
color: '#ff3300' | |||
},{ | |||
name: recFailedStr, | |||
color: '#ff3399' | |||
} | |||
], | |||
undefined, | |||
{ timeInterval: 2000, maxDataPoints: 100 } | |||
) | |||
}, null, {}); | |||
this._initGraph(element, | |||
{ | |||
renderer: "bar", | |||
series: new Rickshaw.Series.FixedDuration([ | |||
{ | |||
name: pubSucceededStr, | |||
color: "#33cc33" | |||
}, { | |||
name: recSucceededStr, | |||
color: "#3333cc" | |||
}, { | |||
name: pubFailedStr, | |||
color: "#ff3300" | |||
}, { | |||
name: recFailedStr, | |||
color: "#ff3399" | |||
} | |||
], | |||
undefined, | |||
{ timeInterval: 2000, maxDataPoints: 100 } | |||
) | |||
}, | |||
null, | |||
{}); | |||
} | |||
RealtimeGraph.prototype = Object.create(BaseGraph.prototype); | |||
RealtimeGraph.prototype.appendHistory = function (statistics) { | |||
var newPubSucceeded = parseInt(statistics["published_succeeded:count"].intValue); | |||
var newPubFailed = parseInt(statistics["published_failed:count"].intValue); | |||
RealtimeGraph.prototype.appendHistory = function(statistics) { | |||
const newPubSucceeded = parseInt(statistics["published_succeeded:count"].intValue); | |||
const newPubFailed = parseInt(statistics["published_failed:count"].intValue); | |||
var newRecSucceeded = parseInt(statistics["received_succeeded:count"].intValue); | |||
var newRecFailed = parseInt(statistics["received_failed:count"].intValue); | |||
const newRecSucceeded = parseInt(statistics["received_succeeded:count"].intValue); | |||
const newRecFailed = parseInt(statistics["received_failed:count"].intValue); | |||
if (this._pubSucceeded !== null && this._pubFailed !== null && | |||
this._recSucceeded !== null && this._recFailed !== null | |||
if (this._pubSucceeded !== null && | |||
this._pubFailed !== null && | |||
this._recSucceeded !== null && | |||
this._recFailed !== null | |||
) { | |||
var pubSucceeded = newPubSucceeded - this._pubSucceeded; | |||
var pubFailed = newPubFailed - this._pubFailed; | |||
const pubSucceeded = newPubSucceeded - this._pubSucceeded; | |||
const pubFailed = newPubFailed - this._pubFailed; | |||
var recSucceeded = newRecSucceeded - this._recSucceeded; | |||
var recFailed = newRecFailed - this._recFailed; | |||
const recSucceeded = newRecSucceeded - this._recSucceeded; | |||
const recFailed = newRecFailed - this._recFailed; | |||
var dataObj = {}; | |||
const dataObj = {}; | |||
dataObj[this._pubFailedStr] = pubFailed; | |||
dataObj[this._pubSucceededStr] = pubSucceeded; | |||
dataObj[this._recFailedStr] = recFailed; | |||
@@ -176,31 +190,41 @@ | |||
return RealtimeGraph; | |||
})(); | |||
cap.HistoryGraph = (function () { | |||
function HistoryGraph(element, pubSucceeded, pubFailed, pubSucceededStr, pubFailedStr, | |||
recSucceeded, recFailed, recSucceededStr, recFailedStr) { | |||
this._initGraph(element, { | |||
renderer: 'area', | |||
series: [ | |||
{ | |||
color: '#33cc33', | |||
data: pubSucceeded, | |||
name: pubSucceededStr | |||
},{ | |||
color: '#3333cc', | |||
data: recSucceeded, | |||
name: recSucceededStr | |||
},{ | |||
color: '#ff3300', | |||
data: pubFailed, | |||
name: pubFailedStr | |||
}, { | |||
color: '#ff3399', | |||
data: recFailed, | |||
name: recFailedStr | |||
} | |||
] | |||
}, {}, { ticksTreatment: 'glow' }); | |||
cap.HistoryGraph = (function() { | |||
function HistoryGraph(element, | |||
pubSucceeded, | |||
pubFailed, | |||
pubSucceededStr, | |||
pubFailedStr, | |||
recSucceeded, | |||
recFailed, | |||
recSucceededStr, | |||
recFailedStr) { | |||
this._initGraph(element, | |||
{ | |||
renderer: "area", | |||
series: [ | |||
{ | |||
color: "#33cc33", | |||
data: pubSucceeded, | |||
name: pubSucceededStr | |||
}, { | |||
color: "#3333cc", | |||
data: recSucceeded, | |||
name: recSucceededStr | |||
}, { | |||
color: "#ff3300", | |||
data: pubFailed, | |||
name: pubFailedStr | |||
}, { | |||
color: "#ff3399", | |||
data: recFailed, | |||
name: recFailedStr | |||
} | |||
] | |||
}, | |||
{}, | |||
{ ticksTreatment: "glow" }); | |||
} | |||
HistoryGraph.prototype = Object.create(BaseGraph.prototype); | |||
@@ -208,7 +232,7 @@ | |||
return HistoryGraph; | |||
})(); | |||
cap.StatisticsPoller = (function () { | |||
cap.StatisticsPoller = (function() { | |||
function StatisticsPoller(metricsCallback, statisticsUrl, pollInterval) { | |||
this._metricsCallback = metricsCallback; | |||
this._listeners = []; | |||
@@ -217,14 +241,16 @@ | |||
this._intervalId = null; | |||
} | |||
StatisticsPoller.prototype.start = function () { | |||
StatisticsPoller.prototype.start = function() { | |||
var self = this; | |||
var intervalFunc = function () { | |||
const intervalFunc = function() { | |||
try { | |||
$.post(self._statisticsUrl, { metrics: self._metricsCallback() }, function (data) { | |||
self._notifyListeners(data); | |||
}); | |||
$.post(self._statisticsUrl, | |||
{ metrics: self._metricsCallback() }, | |||
function(data) { | |||
self._notifyListeners(data); | |||
}); | |||
} catch (e) { | |||
console.log(e); | |||
} | |||
@@ -233,19 +259,19 @@ | |||
this._intervalId = setInterval(intervalFunc, this._pollInterval); | |||
}; | |||
StatisticsPoller.prototype.stop = function () { | |||
StatisticsPoller.prototype.stop = function() { | |||
if (this._intervalId !== null) { | |||
clearInterval(this._intervalId); | |||
this._intervalId = null; | |||
} | |||
}; | |||
StatisticsPoller.prototype.addListener = function (listener) { | |||
StatisticsPoller.prototype.addListener = function(listener) { | |||
this._listeners.push(listener); | |||
}; | |||
StatisticsPoller.prototype._notifyListeners = function (statistics) { | |||
var length = this._listeners.length; | |||
StatisticsPoller.prototype._notifyListeners = function(statistics) { | |||
const length = this._listeners.length; | |||
var i; | |||
for (i = 0; i < length; i++) { | |||
@@ -256,36 +282,36 @@ | |||
return StatisticsPoller; | |||
})(); | |||
cap.Page = (function () { | |||
cap.Page = (function() { | |||
function Page(config) { | |||
this._metrics = new cap.Metrics(); | |||
var self = this; | |||
this._poller = new cap.StatisticsPoller( | |||
function () { return self._metrics.getNames(); }, | |||
function() { return self._metrics.getNames(); }, | |||
config.pollUrl, | |||
config.pollInterval); | |||
this._initialize(config.locale); | |||
this.realtimeGraph = this._createRealtimeGraph('realtimeGraph'); | |||
this.historyGraph = this._createHistoryGraph('historyGraph'); | |||
this.realtimeGraph = this._createRealtimeGraph("realtimeGraph"); | |||
this.historyGraph = this._createHistoryGraph("historyGraph"); | |||
this._poller.start(); | |||
}; | |||
Page.prototype._createRealtimeGraph = function (elementId) { | |||
var realtimeElement = document.getElementById(elementId); | |||
Page.prototype._createRealtimeGraph = function(elementId) { | |||
const realtimeElement = document.getElementById(elementId); | |||
if (realtimeElement) { | |||
var pubSucceeded = parseInt($(realtimeElement).data('published-succeeded')); | |||
var pubFailed = parseInt($(realtimeElement).data('published-failed')); | |||
var pubSucceededStr = $(realtimeElement).data('published-succeeded-string'); | |||
var pubFailedStr = $(realtimeElement).data('published-failed-string'); | |||
const pubSucceeded = parseInt($(realtimeElement).data("published-succeeded")); | |||
const pubFailed = parseInt($(realtimeElement).data("published-failed")); | |||
const pubSucceededStr = $(realtimeElement).data("published-succeeded-string"); | |||
const pubFailedStr = $(realtimeElement).data("published-failed-string"); | |||
var recSucceeded = parseInt($(realtimeElement).data('received-succeeded')); | |||
var recFailed = parseInt($(realtimeElement).data('received-failed')); | |||
var recSucceededStr = $(realtimeElement).data('received-succeeded-string'); | |||
var recFailedStr = $(realtimeElement).data('received-failed-string'); | |||
const recSucceeded = parseInt($(realtimeElement).data("received-succeeded")); | |||
const recFailed = parseInt($(realtimeElement).data("received-failed")); | |||
const recSucceededStr = $(realtimeElement).data("received-succeeded-string"); | |||
const recFailedStr = $(realtimeElement).data("received-failed-string"); | |||
var realtimeGraph = new Cap.RealtimeGraph(realtimeElement, | |||
pubSucceeded, | |||
@@ -298,11 +324,11 @@ | |||
recFailedStr | |||
); | |||
this._poller.addListener(function (data) { | |||
this._poller.addListener(function(data) { | |||
realtimeGraph.appendHistory(data); | |||
}); | |||
$(window).resize(function () { | |||
$(window).resize(function() { | |||
realtimeGraph.update(); | |||
}); | |||
@@ -312,30 +338,30 @@ | |||
return null; | |||
}; | |||
Page.prototype._createHistoryGraph = function (elementId) { | |||
var historyElement = document.getElementById(elementId); | |||
Page.prototype._createHistoryGraph = function(elementId) { | |||
const historyElement = document.getElementById(elementId); | |||
if (historyElement) { | |||
var createSeries = function (obj) { | |||
var series = []; | |||
for (var date in obj) { | |||
const createSeries = function(obj) { | |||
const series = []; | |||
for (let date in obj) { | |||
if (obj.hasOwnProperty(date)) { | |||
var value = obj[date]; | |||
var point = { x: Date.parse(date) / 1000, y: value }; | |||
const value = obj[date]; | |||
const point = { x: Date.parse(date) / 1000, y: value }; | |||
series.unshift(point); | |||
} | |||
} | |||
return series; | |||
}; | |||
var publishedSucceeded = createSeries($(historyElement).data("published-succeeded")); | |||
var publishedFailed = createSeries($(historyElement).data("published-failed")); | |||
var publishedSucceededStr = $(historyElement).data('published-succeeded-string'); | |||
var publishedFailedStr = $(historyElement).data('published-failed-string'); | |||
const publishedSucceeded = createSeries($(historyElement).data("published-succeeded")); | |||
const publishedFailed = createSeries($(historyElement).data("published-failed")); | |||
const publishedSucceededStr = $(historyElement).data("published-succeeded-string"); | |||
const publishedFailedStr = $(historyElement).data("published-failed-string"); | |||
var receivedSucceeded = createSeries($(historyElement).data("received-succeeded")); | |||
var receivedFailed = createSeries($(historyElement).data("received-failed")); | |||
var receivedSucceededStr = $(historyElement).data('received-succeeded-string'); | |||
var receivedFailedStr = $(historyElement).data('received-failed-string'); | |||
const receivedSucceeded = createSeries($(historyElement).data("received-succeeded")); | |||
const receivedFailed = createSeries($(historyElement).data("received-failed")); | |||
const receivedSucceededStr = $(historyElement).data("received-succeeded-string"); | |||
const receivedFailedStr = $(historyElement).data("received-failed-string"); | |||
var historyGraph = new Cap.HistoryGraph(historyElement, | |||
publishedSucceeded, | |||
@@ -348,7 +374,7 @@ | |||
receivedFailedStr, | |||
); | |||
$(window).resize(function () { | |||
$(window).resize(function() { | |||
historyGraph.update(); | |||
}); | |||
@@ -358,39 +384,39 @@ | |||
return null; | |||
}; | |||
Page.prototype._initialize = function (locale) { | |||
Page.prototype._initialize = function(locale) { | |||
moment.locale(locale); | |||
var updateRelativeDates = function () { | |||
$('*[data-moment]').each(function () { | |||
var $this = $(this); | |||
var timestamp = $this.data('moment'); | |||
const updateRelativeDates = function() { | |||
$("*[data-moment]").each(function() { | |||
const $this = $(this); | |||
const timestamp = $this.data("moment"); | |||
if (timestamp) { | |||
var time = moment(timestamp, 'X'); | |||
const time = moment(timestamp, "X"); | |||
$this.html(time.fromNow()) | |||
.attr('title', time.format('llll')) | |||
.attr('data-container', 'body'); | |||
.attr("title", time.format("llll")) | |||
.attr("data-container", "body"); | |||
} | |||
}); | |||
$('*[data-moment-title]').each(function () { | |||
var $this = $(this); | |||
var timestamp = $this.data('moment-title'); | |||
$("*[data-moment-title]").each(function() { | |||
const $this = $(this); | |||
const timestamp = $this.data("moment-title"); | |||
if (timestamp) { | |||
var time = moment(timestamp, 'X'); | |||
$this.prop('title', time.format('llll')) | |||
.attr('data-container', 'body'); | |||
const time = moment(timestamp, "X"); | |||
$this.prop("title", time.format("llll")) | |||
.attr("data-container", "body"); | |||
} | |||
}); | |||
$('*[data-moment-local]').each(function () { | |||
var $this = $(this); | |||
var timestamp = $this.data('moment-local'); | |||
$("*[data-moment-local]").each(function() { | |||
const $this = $(this); | |||
const timestamp = $this.data("moment-local"); | |||
if (timestamp) { | |||
var time = moment(timestamp, 'X'); | |||
$this.html(time.format('l LTS')); | |||
const time = moment(timestamp, "X"); | |||
$this.html(time.format("l LTS")); | |||
} | |||
}); | |||
}; | |||
@@ -401,163 +427,180 @@ | |||
$("*[title]").tooltip(); | |||
var self = this; | |||
$("*[data-metric]").each(function () { | |||
var name = $(this).data('metric'); | |||
$("*[data-metric]").each(function() { | |||
const name = $(this).data("metric"); | |||
self._metrics.addElement(name, this); | |||
}); | |||
this._poller.addListener(function (metrics) { | |||
for (var name in metrics) { | |||
var elements = self._metrics.getElements(name); | |||
for (var i = 0; i < elements.length; i++) { | |||
var metric = metrics[name]; | |||
var metricClass = metric ? "metric-" + metric.style : "metric-null"; | |||
var highlighted = metric && metric.highlighted ? "highlighted" : null; | |||
var value = metric ? metric.value : null; | |||
this._poller.addListener(function(metrics) { | |||
for (let name in metrics) { | |||
const elements = self._metrics.getElements(name); | |||
for (let i = 0; i < elements.length; i++) { | |||
const metric = metrics[name]; | |||
const metricClass = metric ? `metric-${metric.style}` : "metric-null"; | |||
const highlighted = metric && metric.highlighted ? "highlighted" : null; | |||
const value = metric ? metric.value : null; | |||
$(elements[i]) | |||
.text(value) | |||
.closest('.metric') | |||
.closest(".metric") | |||
.removeClass() | |||
.addClass(["metric", metricClass, highlighted].join(' ')); | |||
.addClass(["metric", metricClass, highlighted].join(" ")); | |||
} | |||
} | |||
}); | |||
$(document).on('click', '*[data-ajax]', function (e) { | |||
var $this = $(this); | |||
var confirmText = $this.data('confirm'); | |||
if (!confirmText || confirm(confirmText)) { | |||
$this.prop('disabled'); | |||
var loadingDelay = setTimeout(function () { | |||
$this.button('loading'); | |||
}, 100); | |||
$(document).on("click", | |||
"*[data-ajax]", | |||
function(e) { | |||
var $this = $(this); | |||
const confirmText = $this.data("confirm"); | |||
$.post($this.data('ajax'), function () { | |||
clearTimeout(loadingDelay); | |||
window.location.reload(); | |||
}); | |||
} | |||
if (!confirmText || confirm(confirmText)) { | |||
$this.prop("disabled"); | |||
var loadingDelay = setTimeout(function() { | |||
$this.button("loading"); | |||
}, | |||
100); | |||
$.post($this.data("ajax"), | |||
function() { | |||
clearTimeout(loadingDelay); | |||
window.location.reload(); | |||
}); | |||
} | |||
e.preventDefault(); | |||
}); | |||
e.preventDefault(); | |||
}); | |||
$(document).on('click', '.expander', function (e) { | |||
var $expander = $(this), | |||
$expandable = $expander.closest('tr').next().find('.expandable'); | |||
$(document).on("click", | |||
".expander", | |||
function(e) { | |||
var $expander = $(this), | |||
$expandable = $expander.closest("tr").next().find(".expandable"); | |||
if (!$expandable.is(':visible')) { | |||
$expander.text('Less details...'); | |||
} | |||
if (!$expandable.is(":visible")) { | |||
$expander.text("Less details..."); | |||
} | |||
$expandable.slideToggle( | |||
150, | |||
function () { | |||
if (!$expandable.is(':visible')) { | |||
$expander.text('More details...'); | |||
} | |||
}); | |||
e.preventDefault(); | |||
}); | |||
$expandable.slideToggle( | |||
150, | |||
function() { | |||
if (!$expandable.is(":visible")) { | |||
$expander.text("More details..."); | |||
} | |||
}); | |||
e.preventDefault(); | |||
}); | |||
$('.js-jobs-list').each(function () { | |||
$(".js-jobs-list").each(function() { | |||
var container = this; | |||
var selectRow = function (row, isSelected) { | |||
var $checkbox = $('.js-jobs-list-checkbox', row); | |||
var selectRow = function(row, isSelected) { | |||
const $checkbox = $(".js-jobs-list-checkbox", row); | |||
if ($checkbox.length > 0) { | |||
$checkbox.prop('checked', isSelected); | |||
$(row).toggleClass('highlight', isSelected); | |||
$checkbox.prop("checked", isSelected); | |||
$(row).toggleClass("highlight", isSelected); | |||
} | |||
}; | |||
var toggleRowSelection = function (row) { | |||
var $checkbox = $('.js-jobs-list-checkbox', row); | |||
var toggleRowSelection = function(row) { | |||
const $checkbox = $(".js-jobs-list-checkbox", row); | |||
if ($checkbox.length > 0) { | |||
var isSelected = $checkbox.is(':checked'); | |||
const isSelected = $checkbox.is(":checked"); | |||
selectRow(row, !isSelected); | |||
} | |||
}; | |||
var setListState = function (state) { | |||
$('.js-jobs-list-select-all', container) | |||
.prop('checked', state === 'all-selected') | |||
.prop('indeterminate', state === 'some-selected'); | |||
var setListState = function(state) { | |||
$(".js-jobs-list-select-all", container) | |||
.prop("checked", state === "all-selected") | |||
.prop("indeterminate", state === "some-selected"); | |||
$('.js-jobs-list-command', container) | |||
.prop('disabled', state === 'none-selected'); | |||
$(".js-jobs-list-command", container) | |||
.prop("disabled", state === "none-selected"); | |||
}; | |||
var updateListState = function () { | |||
var selectedRows = $('.js-jobs-list-checkbox', container).map(function () { | |||
return $(this).prop('checked'); | |||
var updateListState = function() { | |||
const selectedRows = $(".js-jobs-list-checkbox", container).map(function() { | |||
return $(this).prop("checked"); | |||
}).get(); | |||
var state = 'none-selected'; | |||
var state = "none-selected"; | |||
if (selectedRows.length > 0) { | |||
state = 'some-selected'; | |||
state = "some-selected"; | |||
if ($.inArray(false, selectedRows) === -1) { | |||
state = 'all-selected'; | |||
state = "all-selected"; | |||
} else if ($.inArray(true, selectedRows) === -1) { | |||
state = 'none-selected'; | |||
state = "none-selected"; | |||
} | |||
} | |||
setListState(state); | |||
}; | |||
$(this).on('click', '.js-jobs-list-checkbox', function (e) { | |||
selectRow( | |||
$(this).closest('.js-jobs-list-row').first(), | |||
$(this).is(':checked')); | |||
updateListState(); | |||
$(this).on("click", | |||
".js-jobs-list-checkbox", | |||
function(e) { | |||
selectRow( | |||
$(this).closest(".js-jobs-list-row").first(), | |||
$(this).is(":checked")); | |||
e.stopPropagation(); | |||
}); | |||
updateListState(); | |||
$(this).on('click', '.js-jobs-list-row', function (e) { | |||
if ($(e.target).is('a')) return; | |||
toggleRowSelection(this); | |||
updateListState(); | |||
}); | |||
e.stopPropagation(); | |||
}); | |||
$(this).on('click', '.js-jobs-list-select-all', function () { | |||
var selectRows = $(this).is(':checked'); | |||
$(this).on("click", | |||
".js-jobs-list-row", | |||
function(e) { | |||
if ($(e.target).is("a")) return; | |||
$('.js-jobs-list-row', container).each(function () { | |||
selectRow(this, selectRows); | |||
toggleRowSelection(this); | |||
updateListState(); | |||
}); | |||
updateListState(); | |||
}); | |||
$(this).on("click", | |||
".js-jobs-list-select-all", | |||
function() { | |||
var selectRows = $(this).is(":checked"); | |||
$(this).on('click', '.js-jobs-list-command', function (e) { | |||
var $this = $(this); | |||
var confirmText = $this.data('confirm'); | |||
$(".js-jobs-list-row", container).each(function() { | |||
selectRow(this, selectRows); | |||
}); | |||
var jobs = $("input[name='messages[]']:checked", container).map(function () { | |||
return $(this).val(); | |||
}).get(); | |||
updateListState(); | |||
}); | |||
if (!confirmText || confirm(confirmText)) { | |||
$this.prop('disabled'); | |||
var loadingDelay = setTimeout(function () { | |||
$this.button('loading'); | |||
}, 100); | |||
$.post($this.data('url'), { 'messages[]': jobs }, function () { | |||
clearTimeout(loadingDelay); | |||
window.location.reload(); | |||
}); | |||
} | |||
$(this).on("click", | |||
".js-jobs-list-command", | |||
function(e) { | |||
var $this = $(this); | |||
const confirmText = $this.data("confirm"); | |||
const jobs = $("input[name='messages[]']:checked", container).map(function() { | |||
return $(this).val(); | |||
}).get(); | |||
if (!confirmText || confirm(confirmText)) { | |||
$this.prop("disabled"); | |||
var loadingDelay = setTimeout(function() { | |||
$this.button("loading"); | |||
}, | |||
100); | |||
$.post($this.data("url"), | |||
{ 'messages[]': jobs }, | |||
function() { | |||
clearTimeout(loadingDelay); | |||
window.location.reload(); | |||
}); | |||
} | |||
e.preventDefault(); | |||
}); | |||
e.preventDefault(); | |||
}); | |||
updateListState(); | |||
}); | |||
@@ -567,20 +610,20 @@ | |||
})(); | |||
})(window.Cap = window.Cap || {}); | |||
$(function () { | |||
$(function() { | |||
Cap.page = new Cap.Page(Cap.config); | |||
}); | |||
(function () { | |||
(function() { | |||
var json = null; | |||
$(".openModal").click(function () { | |||
var url = $(this).data("url"); | |||
$(".openModal").click(function() { | |||
const url = $(this).data("url"); | |||
$.ajax({ | |||
url: url, | |||
dataType: "json", | |||
success: function (data) { | |||
success: function(data) { | |||
json = data; | |||
$("#formatBtn").click(); | |||
$(".modal").modal("show"); | |||
@@ -588,25 +631,25 @@ $(function () { | |||
}); | |||
}); | |||
$("#formatBtn").click(function () { | |||
$('#jsonContent').JSONView(json); | |||
$("#formatBtn").click(function() { | |||
$("#jsonContent").JSONView(json); | |||
}); | |||
$("#rawBtn").click(function () { | |||
$('#jsonContent').text(JSON.stringify(json)); | |||
$("#rawBtn").click(function() { | |||
$("#jsonContent").text(JSON.stringify(json)); | |||
}); | |||
$("#expandBtn").click(function () { | |||
$('#jsonContent').JSONView('expand'); | |||
$("#expandBtn").click(function() { | |||
$("#jsonContent").JSONView("expand"); | |||
}); | |||
$("#collapseBtn").click(function () { | |||
$('#jsonContent').JSONView('collapse'); | |||
$("#collapseBtn").click(function() { | |||
$("#jsonContent").JSONView("collapse"); | |||
}); | |||
})(); | |||
function nodeSwitch(id) { | |||
console.log("id:" + id); | |||
document.cookie = "cap.node=" + escape(id) + ";"; | |||
console.log(`id:${id}`); | |||
document.cookie = `cap.node=${escape(id)};`; | |||
} |
@@ -11,42 +11,6 @@ namespace DotNetCore.CAP.Dashboard | |||
{ | |||
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", | |||
@@ -61,7 +25,7 @@ namespace DotNetCore.CAP.Dashboard | |||
public static readonly DashboardMetric SubscriberCount = new DashboardMetric( | |||
"retries:count", | |||
"Metrics_Retries", | |||
"Metrics_Retries", | |||
page => | |||
{ | |||
long retryCount; | |||
@@ -91,14 +55,14 @@ namespace DotNetCore.CAP.Dashboard | |||
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); | |||
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); | |||
//---------------------------------------------------- | |||
@@ -111,12 +75,12 @@ namespace DotNetCore.CAP.Dashboard | |||
}); | |||
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 | |||
}); | |||
"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( | |||
@@ -128,12 +92,12 @@ namespace DotNetCore.CAP.Dashboard | |||
}); | |||
public static readonly DashboardMetric ReceivedSucceededCount = new DashboardMetric( | |||
"received_succeeded:count", | |||
"Metrics_SucceededJobs", | |||
page => new Metric(page.Statistics.ReceivedSucceeded.ToString("N0")) | |||
{ | |||
IntValue = page.Statistics.ReceivedSucceeded | |||
}); | |||
"received_succeeded:count", | |||
"Metrics_SucceededJobs", | |||
page => new Metric(page.Statistics.ReceivedSucceeded.ToString("N0")) | |||
{ | |||
IntValue = page.Statistics.ReceivedSucceeded | |||
}); | |||
//---------------------------------------------------- | |||
@@ -148,13 +112,49 @@ namespace DotNetCore.CAP.Dashboard | |||
}); | |||
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 | |||
}); | |||
"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 | |||
}); | |||
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(); | |||
} | |||
} | |||
} | |||
} |
@@ -34,8 +34,11 @@ namespace DotNetCore.CAP.Dashboard | |||
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 string GetQuery(string key) | |||
{ | |||
return _context.Request.Query[key]; | |||
} | |||
public override async Task<IList<string>> GetFormValuesAsync(string key) | |||
{ | |||
@@ -7,12 +7,12 @@ namespace DotNetCore.CAP.Dashboard | |||
internal class EmbeddedResourceDispatcher : IDashboardDispatcher | |||
{ | |||
private readonly Assembly _assembly; | |||
private readonly string _resourceName; | |||
private readonly string _contentType; | |||
private readonly string _resourceName; | |||
public EmbeddedResourceDispatcher( | |||
string contentType, | |||
Assembly assembly, | |||
string contentType, | |||
Assembly assembly, | |||
string resourceName) | |||
{ | |||
if (assembly != null) | |||
@@ -47,9 +47,8 @@ namespace DotNetCore.CAP.Dashboard | |||
using (var inputStream = assembly.GetManifestResourceStream(resourceName)) | |||
{ | |||
if (inputStream == null) | |||
{ | |||
throw new ArgumentException($@"Resource with name {resourceName} not found in assembly {assembly}."); | |||
} | |||
throw new ArgumentException( | |||
$@"Resource with name {resourceName} not found in assembly {assembly}."); | |||
inputStream.CopyTo(response.Body); | |||
} | |||
@@ -16,20 +16,18 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
public class GatewayProxyMiddleware | |||
{ | |||
public const string NodeCookieName = "cap.node"; | |||
private readonly ILogger _logger; | |||
private readonly RequestDelegate _next; | |||
private readonly ILogger _logger; | |||
private readonly IRequestMapper _requestMapper; | |||
private readonly IHttpRequester _requester; | |||
private readonly IRequestMapper _requestMapper; | |||
private INodeDiscoveryProvider _discoveryProvider; | |||
protected HttpRequestMessage DownstreamRequest { get; set; } | |||
public GatewayProxyMiddleware(RequestDelegate next, | |||
ILoggerFactory loggerFactory, | |||
IRequestMapper requestMapper, | |||
IHttpRequester requester) | |||
ILoggerFactory loggerFactory, | |||
IRequestMapper requestMapper, | |||
IHttpRequester requester) | |||
{ | |||
_next = next; | |||
_logger = loggerFactory.CreateLogger<GatewayProxyMiddleware>(); | |||
@@ -37,6 +35,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
_requester = requester; | |||
} | |||
protected HttpRequestMessage DownstreamRequest { get; set; } | |||
public async Task Invoke(HttpContext context, | |||
DiscoveryOptions discoveryOptions, | |||
INodeDiscoveryProvider discoveryProvider) | |||
@@ -53,7 +53,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
else | |||
{ | |||
//For performance reasons, we need to put this functionality in the else | |||
var isSwitchNode = request.Cookies.TryGetValue(NodeCookieName, out string requestNodeId); | |||
var isSwitchNode = request.Cookies.TryGetValue(NodeCookieName, out var requestNodeId); | |||
var isCurrentNode = discoveryOptions.NodeId.ToString() == requestNodeId; | |||
var isNodesPage = request.Path.StartsWithSegments(new PathString(pathMatch + "/nodes")); | |||
@@ -65,7 +65,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
{ | |||
_logger.LogDebug("started calling gateway proxy middleware"); | |||
if (TryGetRemoteNode(requestNodeId, out Node node)) | |||
if (TryGetRemoteNode(requestNodeId, out var node)) | |||
{ | |||
try | |||
{ | |||
@@ -94,31 +94,26 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) | |||
{ | |||
foreach (var httpResponseHeader in response.Content.Headers) | |||
{ | |||
AddHeaderIfDoesntExist(context, httpResponseHeader); | |||
} | |||
var content = await response.Content.ReadAsByteArrayAsync(); | |||
AddHeaderIfDoesntExist(context, | |||
new KeyValuePair<string, IEnumerable<string>>("Content-Length", new[] { content.Length.ToString() })); | |||
new KeyValuePair<string, IEnumerable<string>>("Content-Length", new[] {content.Length.ToString()})); | |||
context.Response.OnStarting(state => | |||
{ | |||
var httpContext = (HttpContext)state; | |||
var httpContext = (HttpContext) state; | |||
httpContext.Response.StatusCode = (int)response.StatusCode; | |||
httpContext.Response.StatusCode = (int) response.StatusCode; | |||
return Task.CompletedTask; | |||
}, context); | |||
using (Stream stream = new MemoryStream(content)) | |||
{ | |||
if (response.StatusCode != HttpStatusCode.NotModified) | |||
{ | |||
await stream.CopyToAsync(context.Response.Body); | |||
} | |||
} | |||
} | |||
@@ -139,10 +134,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
KeyValuePair<string, IEnumerable<string>> httpResponseHeader) | |||
{ | |||
if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) | |||
{ | |||
context.Response.Headers.Add(httpResponseHeader.Key, | |||
new StringValues(httpResponseHeader.Value.ToArray())); | |||
} | |||
} | |||
} | |||
} |
@@ -12,14 +12,14 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
{ | |||
public class RequestMapper : IRequestMapper | |||
{ | |||
private readonly string[] _unsupportedHeaders = { "host", "cookie" }; | |||
private const string SchemeDelimiter = "://"; | |||
private readonly string[] _unsupportedHeaders = {"host", "cookie"}; | |||
public async Task<HttpRequestMessage> Map(HttpRequest request) | |||
{ | |||
try | |||
{ | |||
var requestMessage = new HttpRequestMessage() | |||
var requestMessage = new HttpRequestMessage | |||
{ | |||
Content = await MapContent(request), | |||
Method = MapMethod(request), | |||
@@ -45,11 +45,9 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
FragmentString fragment = new FragmentString()) | |||
{ | |||
if (scheme == null) | |||
{ | |||
throw new ArgumentNullException(nameof(scheme)); | |||
} | |||
var combinedPath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/"; | |||
var combinedPath = pathBase.HasValue || path.HasValue ? (pathBase + path).ToString() : "/"; | |||
var encodedHost = host.ToString(); | |||
var encodedQuery = query.ToString(); | |||
@@ -57,7 +55,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
// PERF: Calculate string length to allocate correct buffer size for StringBuilder. | |||
var length = scheme.Length + SchemeDelimiter.Length + encodedHost.Length | |||
+ combinedPath.Length + encodedQuery.Length + encodedFragment.Length; | |||
+ combinedPath.Length + encodedQuery.Length + encodedFragment.Length; | |||
return new StringBuilder(length) | |||
.Append(scheme) | |||
@@ -77,13 +75,11 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
private async Task<HttpContent> MapContent(HttpRequest request) | |||
{ | |||
if (request.Body == null) | |||
{ | |||
return null; | |||
} | |||
var content = new ByteArrayContent(await ToByteArray(request.Body)); | |||
content.Headers.TryAddWithoutValidation("Content-Type", new[] { request.ContentType }); | |||
content.Headers.TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); | |||
return content; | |||
} | |||
@@ -101,12 +97,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) | |||
{ | |||
foreach (var header in request.Headers) | |||
{ | |||
if (IsSupportedHeader(header)) | |||
{ | |||
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); | |||
} | |||
} | |||
} | |||
private async Task<byte[]> ToByteArray(Stream stream) | |||
@@ -1,9 +1,9 @@ | |||
namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
{ | |||
using System.Net.Http; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Http; | |||
using System.Net.Http; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Http; | |||
namespace DotNetCore.CAP.Dashboard.GatewayProxy | |||
{ | |||
public interface IRequestMapper | |||
{ | |||
Task<HttpRequestMessage> Map(HttpRequest request); | |||
@@ -8,7 +8,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester | |||
{ | |||
internal class HttpClientBuilder : IHttpClientBuilder | |||
{ | |||
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>(); | |||
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = | |||
new Dictionary<int, Func<DelegatingHandler>>(); | |||
public IHttpClient Create() | |||
{ | |||
@@ -41,13 +42,13 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester | |||
/// </summary> | |||
internal class HttpClientWrapper : IHttpClient | |||
{ | |||
public HttpClient Client { get; } | |||
public HttpClientWrapper(HttpClient client) | |||
{ | |||
Client = client; | |||
} | |||
public HttpClient Client { get; } | |||
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request) | |||
{ | |||
return Client.SendAsync(request); | |||
@@ -44,15 +44,13 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester | |||
var httpClient = _cacheHandlers.Get(cacheKey); | |||
if (httpClient == null) | |||
{ | |||
httpClient = builder.Create(); | |||
} | |||
return httpClient; | |||
} | |||
private string GetCacheKey(HttpRequestMessage request, IHttpClientBuilder builder) | |||
{ | |||
string baseUrl = $"{request.RequestUri.Scheme}://{request.RequestUri.Authority}"; | |||
var baseUrl = $"{request.RequestUri.Scheme}://{request.RequestUri.Authority}"; | |||
return baseUrl; | |||
} | |||
@@ -5,7 +5,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester | |||
public interface IHttpClientBuilder | |||
{ | |||
/// <summary> | |||
/// Creates the <see cref="HttpClient"/> | |||
/// Creates the <see cref="HttpClient" /> | |||
/// </summary> | |||
IHttpClient Create(); | |||
} |
@@ -5,7 +5,8 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester | |||
{ | |||
public class MemoryHttpClientCache : IHttpClientCache | |||
{ | |||
private readonly ConcurrentDictionary<string, ConcurrentQueue<IHttpClient>> _httpClientsCache = new ConcurrentDictionary<string, ConcurrentQueue<IHttpClient>>(); | |||
private readonly ConcurrentDictionary<string, ConcurrentQueue<IHttpClient>> _httpClientsCache = | |||
new ConcurrentDictionary<string, ConcurrentQueue<IHttpClient>>(); | |||
public void Set(string id, IHttpClient client, TimeSpan expirationTime) | |||
{ | |||
@@ -30,9 +31,7 @@ namespace DotNetCore.CAP.Dashboard.GatewayProxy.Requester | |||
{ | |||
IHttpClient client = null; | |||
if (_httpClientsCache.TryGetValue(id, out var connectionQueue)) | |||
{ | |||
connectionQueue.TryDequeue(out client); | |||
} | |||
return client; | |||
} | |||
@@ -31,9 +31,7 @@ namespace DotNetCore.CAP.Dashboard | |||
public NonEscapedString MessagesSidebar(MessageType type) | |||
{ | |||
if (type == MessageType.Publish) | |||
{ | |||
return SidebarMenu(MessagesSidebarMenu.PublishedItems); | |||
} | |||
return SidebarMenu(MessagesSidebarMenu.ReceivedItems); | |||
} | |||
@@ -80,12 +78,11 @@ namespace DotNetCore.CAP.Dashboard | |||
public NonEscapedString StateLabel(string stateName) | |||
{ | |||
if (String.IsNullOrWhiteSpace(stateName)) | |||
{ | |||
if (string.IsNullOrWhiteSpace(stateName)) | |||
return Raw($"<em>{Strings.Common_NoState}</em>"); | |||
} | |||
return Raw($"<span class=\"label label-default\" style=\"background-color: {MessageHistoryRenderer.GetForegroundStateColor(stateName)};\">{stateName}</span>"); | |||
return Raw( | |||
$"<span class=\"label label-default\" style=\"background-color: {MessageHistoryRenderer.GetForegroundStateColor(stateName)};\">{stateName}</span>"); | |||
} | |||
public NonEscapedString RelativeTime(DateTime value) | |||
@@ -109,52 +106,36 @@ namespace DotNetCore.CAP.Dashboard | |||
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); | |||
@@ -163,7 +144,7 @@ namespace DotNetCore.CAP.Dashboard | |||
public string FormatProperties(IDictionary<string, string> properties) | |||
{ | |||
return String.Join(", ", properties.Select(x => $"{x.Key}: \"{x.Value}\"")); | |||
return string.Join(", ", properties.Select(x => $"{x.Key}: \"{x.Value}\"")); | |||
} | |||
public NonEscapedString QueueLabel(string queue) | |||
@@ -179,7 +160,7 @@ namespace DotNetCore.CAP.Dashboard | |||
{ | |||
var parts = serverId.Split(':'); | |||
var shortenedId = parts.Length > 1 | |||
? String.Join(":", parts.Take(parts.Length - 1)) | |||
? string.Join(":", parts.Take(parts.Length - 1)) | |||
: serverId; | |||
return new NonEscapedString( | |||
@@ -188,20 +169,40 @@ namespace DotNetCore.CAP.Dashboard | |||
public NonEscapedString NodeSwitchLink(string id) | |||
{ | |||
return Raw($"<a class=\"job-method\" onclick=\"nodeSwitch({id});\" href=\"javascript:;\">{Strings.NodePage_Switch}</a>"); | |||
return Raw( | |||
$"<a class=\"job-method\" onclick=\"nodeSwitch({id});\" href=\"javascript:;\">{Strings.NodePage_Switch}</a>"); | |||
} | |||
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); | |||
} | |||
#region MethodEscaped | |||
public NonEscapedString MethodEscaped(MethodInfo method) | |||
{ | |||
var @public = WrapKeyword("public"); | |||
var @async = string.Empty; | |||
var async = string.Empty; | |||
string @return; | |||
var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(method.ReturnType, out var coercedAwaitableInfo); | |||
if (isAwaitable) | |||
{ | |||
@async = WrapKeyword("async"); | |||
async = WrapKeyword("async"); | |||
var asyncResultType = coercedAwaitableInfo.AwaitableInfo.ResultType; | |||
@return = WrapType("Task") + WrapIdentifier("<") + WrapType(asyncResultType) + WrapIdentifier(">"); | |||
@@ -211,7 +212,7 @@ namespace DotNetCore.CAP.Dashboard | |||
@return = WrapType(method.ReturnType); | |||
} | |||
var @name = method.Name; | |||
var name = method.Name; | |||
string paramType = null; | |||
string paramName = null; | |||
@@ -227,7 +228,8 @@ namespace DotNetCore.CAP.Dashboard | |||
var paramString = paramType == null ? "();" : $"({paramType} {paramName});"; | |||
var outputString = @public + " " + (string.IsNullOrEmpty(@async) ? "" : @async + " ") + @return + " " + @name + paramString; | |||
var outputString = @public + " " + (string.IsNullOrEmpty(async) ? "" : async + " ") + @return + " " + name + | |||
paramString; | |||
return new NonEscapedString(outputString); | |||
} | |||
@@ -235,26 +237,15 @@ namespace DotNetCore.CAP.Dashboard | |||
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 == typeof(string) || type == typeof(decimal)) | |||
{ | |||
return WrapKeyword(type.Name.ToLower()); | |||
} | |||
else | |||
{ | |||
return WrapType(type.Name); | |||
} | |||
return WrapType(type.Name); | |||
} | |||
private string WrapIdentifier(string value) | |||
@@ -275,25 +266,8 @@ namespace DotNetCore.CAP.Dashboard | |||
private string Span(string @class, string value) | |||
{ | |||
return $"<span class=\"{@class}\">{value}</span>"; | |||
} | |||
#endregion | |||
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); | |||
} | |||
#endregion | |||
} | |||
} |
@@ -26,20 +26,18 @@ namespace DotNetCore.CAP.Dashboard | |||
string serialized = null; | |||
if (_command != null) | |||
{ | |||
object result = _command(context); | |||
var result = _command(context); | |||
var settings = new JsonSerializerSettings | |||
{ | |||
ContractResolver = new CamelCasePropertyNamesContractResolver(), | |||
Converters = new JsonConverter[] { new StringEnumConverter { CamelCaseText = true } } | |||
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 ?? string.Empty); | |||
@@ -27,7 +27,7 @@ namespace DotNetCore.CAP.Dashboard | |||
var settings = new JsonSerializerSettings | |||
{ | |||
ContractResolver = new CamelCasePropertyNamesContractResolver(), | |||
Converters = new JsonConverter[] { new StringEnumConverter { CamelCaseText = true } } | |||
Converters = new JsonConverter[] {new StringEnumConverter {CamelCaseText = true}} | |||
}; | |||
var serialized = JsonConvert.SerializeObject(result, settings); | |||
@@ -1,13 +1,11 @@ | |||
using System; | |||
namespace DotNetCore.CAP.Dashboard | |||
namespace DotNetCore.CAP.Dashboard | |||
{ | |||
public class LocalRequestsOnlyAuthorizationFilter : IDashboardAuthorizationFilter | |||
{ | |||
public bool Authorize(DashboardContext context) | |||
{ | |||
// if unknown, assume not local | |||
if (String.IsNullOrEmpty(context.Request.RemoteIpAddress)) | |||
if (string.IsNullOrEmpty(context.Request.RemoteIpAddress)) | |||
return false; | |||
// check if localhost | |||
@@ -20,12 +20,10 @@ namespace DotNetCore.CAP.Dashboard | |||
public IEnumerable<DashboardMetric> GetAllMetrics() | |||
{ | |||
var metrics = new List<DashboardMetric> { Metric }; | |||
var metrics = new List<DashboardMetric> {Metric}; | |||
if (Metrics != null) | |||
{ | |||
metrics.AddRange(Metrics); | |||
} | |||
return metrics.Where(x => x != null).ToList(); | |||
} | |||
@@ -1,5 +1,7 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics.CodeAnalysis; | |||
using System.Net; | |||
using System.Text; | |||
using DotNetCore.CAP.Infrastructure; | |||
@@ -16,7 +18,7 @@ namespace DotNetCore.CAP.Dashboard | |||
private static readonly IDictionary<string, string> ForegroundStateColors | |||
= new Dictionary<string, string>(); | |||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] | |||
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] | |||
static MessageHistoryRenderer() | |||
{ | |||
Register(StatusName.Succeeded, SucceededRenderer); | |||
@@ -44,9 +46,7 @@ namespace DotNetCore.CAP.Dashboard | |||
public static string GetBackgroundStateColor(string stateName) | |||
{ | |||
if (stateName == null || !BackgroundStateColors.ContainsKey(stateName)) | |||
{ | |||
return "inherit"; | |||
} | |||
return BackgroundStateColors[stateName]; | |||
} | |||
@@ -59,23 +59,18 @@ namespace DotNetCore.CAP.Dashboard | |||
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) | |||
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) | |||
@@ -141,10 +136,10 @@ namespace DotNetCore.CAP.Dashboard | |||
itemsAdded = true; | |||
} | |||
if (stateData.ContainsKey("Result") && !String.IsNullOrWhiteSpace(stateData["Result"])) | |||
if (stateData.ContainsKey("Result") && !string.IsNullOrWhiteSpace(stateData["Result"])) | |||
{ | |||
var result = stateData["Result"]; | |||
builder.Append($"<dt>Result:</dt><dd>{System.Net.WebUtility.HtmlEncode(result)}</dd>"); | |||
builder.Append($"<dt>Result:</dt><dd>{WebUtility.HtmlEncode(result)}</dd>"); | |||
itemsAdded = true; | |||
} | |||
@@ -171,13 +166,9 @@ namespace DotNetCore.CAP.Dashboard | |||
string serverId = null; | |||
if (stateData.ContainsKey("ServerId")) | |||
{ | |||
serverId = stateData["ServerId"]; | |||
} | |||
else if (stateData.ContainsKey("ServerName")) | |||
{ | |||
serverId = stateData["ServerName"]; | |||
} | |||
if (serverId != null) | |||
{ | |||
@@ -20,11 +20,12 @@ namespace DotNetCore.CAP.Dashboard | |||
Metric = DashboardMetrics.PublishedSucceededCount | |||
}); | |||
PublishedItems.Add(page => new MenuItem(Strings.SidebarMenu_Processing, page.Url.To("/published/processing")) | |||
{ | |||
Active = page.RequestPath.StartsWith("/published/processing"), | |||
Metric = DashboardMetrics.PublishedProcessingCount | |||
}); | |||
PublishedItems.Add(page => | |||
new MenuItem(Strings.SidebarMenu_Processing, page.Url.To("/published/processing")) | |||
{ | |||
Active = page.RequestPath.StartsWith("/published/processing"), | |||
Metric = DashboardMetrics.PublishedProcessingCount | |||
}); | |||
PublishedItems.Add(page => new MenuItem(Strings.SidebarMenu_Failed, page.Url.To("/published/failed")) | |||
{ | |||
@@ -20,7 +20,7 @@ | |||
Info, | |||
Success, | |||
Warning, | |||
Danger, | |||
Danger | |||
} | |||
internal static class MetricStyleExtensions | |||
@@ -7,9 +7,9 @@ namespace DotNetCore.CAP.Dashboard | |||
{ | |||
private const int PageItemsCount = 7; | |||
private const int DefaultRecordsPerPage = 10; | |||
private int _endPageIndex = 1; | |||
private int _startPageIndex = 1; | |||
private int _endPageIndex = 1; | |||
public Pager(int from, int perPage, long total) | |||
{ | |||
@@ -17,7 +17,7 @@ namespace DotNetCore.CAP.Dashboard | |||
RecordsPerPage = perPage > 0 ? perPage : DefaultRecordsPerPage; | |||
TotalRecordCount = total; | |||
CurrentPage = FromRecord / RecordsPerPage + 1; | |||
TotalPageCount = (int)Math.Ceiling((double)TotalRecordCount / RecordsPerPage); | |||
TotalPageCount = (int) Math.Ceiling((double) TotalRecordCount / RecordsPerPage); | |||
PagerItems = GenerateItems(); | |||
} | |||
@@ -110,7 +110,7 @@ namespace DotNetCore.CAP.Dashboard | |||
if (_endPageIndex < TotalPageCount - 1) | |||
{ | |||
var index = _startPageIndex + PageItemsCount; | |||
if (index > TotalPageCount) { index = TotalPageCount; } | |||
if (index > TotalPageCount) index = TotalPageCount; | |||
var item = new Item(index, false, ItemType.MorePage); | |||
results.Add(item); | |||
} | |||
@@ -1,21 +1,18 @@ | |||
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ | |||
@using System | |||
@using System.Collections.Generic | |||
@using DotNetCore.CAP.Models; | |||
@using DotNetCore.CAP.Dashboard | |||
@using DotNetCore.CAP.Dashboard.Pages | |||
@using DotNetCore.CAP.Dashboard.Resources | |||
@using DotNetCore.CAP.Models | |||
@using Newtonsoft.Json | |||
@inherits RazorPage | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
@{ | |||
Layout = new LayoutPage(Strings.HomePage_Title); | |||
var monitor = Storage.GetMonitoringApi(); | |||
IDictionary<DateTime, int> publishedSucceeded = monitor.HourlySucceededJobs(MessageType.Publish); | |||
IDictionary<DateTime, int> publishedFailed = monitor.HourlyFailedJobs(MessageType.Publish); | |||
var publishedSucceeded = monitor.HourlySucceededJobs(MessageType.Publish); | |||
var publishedFailed = monitor.HourlyFailedJobs(MessageType.Publish); | |||
IDictionary<DateTime, int> receivedSucceeded = monitor.HourlySucceededJobs(MessageType.Subscribe); | |||
IDictionary<DateTime, int> receivedFailed = monitor.HourlyFailedJobs(MessageType.Subscribe); | |||
var receivedSucceeded = monitor.HourlySucceededJobs(MessageType.Subscribe); | |||
var receivedFailed = monitor.HourlyFailedJobs(MessageType.Subscribe); | |||
} | |||
<div class="row"> | |||
@@ -41,7 +38,8 @@ | |||
data-received-succeeded="@Statistics.ReceivedSucceeded" | |||
data-received-failed="@Statistics.ReceivedFailed" | |||
data-received-succeeded-string="@Strings.HomePage_GraphHover_RSucceeded" | |||
data-received-failed-string="@Strings.HomePage_GraphHover_RFailed"></div> | |||
data-received-failed-string="@Strings.HomePage_GraphHover_RFailed"> | |||
</div> | |||
<div style="display: none;"> | |||
<span data-metric="published_succeeded:count"></span> | |||
<span data-metric="published_failed:count"></span> | |||
@@ -61,7 +59,7 @@ | |||
data-received-succeeded="@JsonConvert.SerializeObject(receivedSucceeded)" | |||
data-received-failed="@JsonConvert.SerializeObject(receivedFailed)" | |||
data-received-succeeded-string="@Strings.HomePage_GraphHover_RSucceeded" | |||
data-received-failed-string="@Strings.HomePage_GraphHover_RFailed"> | |||
data-received-failed-string="@Strings.HomePage_GraphHover_RFailed"> | |||
</div> | |||
</div> | |||
</div> |
@@ -2,10 +2,9 @@ | |||
@using System | |||
@using System.Globalization | |||
@using System.Reflection | |||
@using DotNetCore.CAP.Dashboard | |||
@using DotNetCore.CAP.Dashboard.Pages | |||
@using DotNetCore.CAP.Dashboard.Resources | |||
@inherits RazorPage | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
<!DOCTYPE html> | |||
<html lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName"> | |||
<head> | |||
@@ -16,63 +15,65 @@ | |||
@{ var version = GetType().GetTypeInfo().Assembly.GetName().Version; } | |||
<link rel="stylesheet" href="@Url.To($"/css{version.Major}{version.Minor}{version.Build}")"> | |||
</head> | |||
<body> | |||
<!-- Wrap all page content here --> | |||
<div id="wrap"> | |||
<body> | |||
<!-- Wrap all page content here --> | |||
<div id="wrap"> | |||
<!-- Fixed navbar --> | |||
<div class="navbar navbar-default navbar-fixed-top"> | |||
<div class="container"> | |||
<div class="navbar-header"> | |||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> | |||
<span class="icon-bar"></span> | |||
<span class="icon-bar"></span> | |||
<span class="icon-bar"></span> | |||
</button> | |||
<a class="navbar-brand" href="@Url.Home()">CAP Dashboard</a> | |||
</div> | |||
<div class="collapse navbar-collapse"> | |||
@Html.RenderPartial(new Navigation()) | |||
@if(@AppPath != null) { | |||
<ul class="nav navbar-nav navbar-right"> | |||
<li> | |||
<a href="@AppPath"> | |||
<span class="glyphicon glyphicon-log-out"></span> | |||
@Strings.LayoutPage_Back | |||
</a> | |||
</li> | |||
</ul> | |||
} | |||
</div> | |||
<!--/.nav-collapse --> | |||
</div> | |||
<!-- Fixed navbar --> | |||
<div class="navbar navbar-default navbar-fixed-top"> | |||
<div class="container"> | |||
<div class="navbar-header"> | |||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> | |||
<span class="icon-bar"></span> | |||
<span class="icon-bar"></span> | |||
<span class="icon-bar"></span> | |||
</button> | |||
<a class="navbar-brand" href="@Url.Home()">CAP Dashboard</a> | |||
</div> | |||
<!-- Begin page content --> | |||
<div class="container" style="margin-bottom: 20px;"> | |||
@RenderBody() | |||
<div class="collapse navbar-collapse"> | |||
@Html.RenderPartial(new Navigation()) | |||
@if (AppPath != null) | |||
{ | |||
<ul class="nav navbar-nav navbar-right"> | |||
<li> | |||
<a href="@AppPath"> | |||
<span class="glyphicon glyphicon-log-out"></span> | |||
@Strings.LayoutPage_Back | |||
</a> | |||
</li> | |||
</ul> | |||
} | |||
</div> | |||
<!--/.nav-collapse --> | |||
</div> | |||
</div> | |||
<div id="footer"> | |||
<div class="container"> | |||
<ul class="list-inline credit"> | |||
<li> | |||
<a href="https://github.com/dotnetcore/cap/" target="_blank">CAP @($"{version.Major}.{version.Minor}.{version.Build}") | |||
</a> | |||
</li> | |||
<li>@Storage</li> | |||
<li>@Strings.LayoutPage_Footer_Time @Html.LocalTime(DateTime.UtcNow)</li> | |||
<li>@String.Format(Strings.LayoutPage_Footer_Generatedms, GenerationTime.Elapsed.TotalMilliseconds.ToString("N"))</li> | |||
</ul> | |||
</div> | |||
</div> | |||
<div id="capConfig" | |||
data-pollinterval="@StatsPollingInterval" | |||
data-pollurl="@(Url.To("/stats"))"> | |||
</div> | |||
<!-- Begin page content --> | |||
<div class="container" style="margin-bottom: 20px;"> | |||
@RenderBody() | |||
</div> | |||
</div> | |||
<div id="footer"> | |||
<div class="container"> | |||
<ul class="list-inline credit"> | |||
<li> | |||
<a href="https://github.com/dotnetcore/cap/" target="_blank"> | |||
CAP @($"{version.Major}.{version.Minor}.{version.Build}") | |||
</a> | |||
</li> | |||
<li>@Storage</li> | |||
<li>@Strings.LayoutPage_Footer_Time @Html.LocalTime(DateTime.UtcNow)</li> | |||
<li>@string.Format(Strings.LayoutPage_Footer_Generatedms, GenerationTime.Elapsed.TotalMilliseconds.ToString("N"))</li> | |||
</ul> | |||
</div> | |||
</div> | |||
<div id="capConfig" | |||
data-pollinterval="@StatsPollingInterval" | |||
data-pollurl="@(Url.To("/stats"))"> | |||
</div> | |||
<script src="@Url.To($"/js{version.Major}{version.Minor}{version.Build}")"></script> | |||
</body> | |||
</html> | |||
<script src="@Url.To($"/js{version.Major}{version.Minor}{version.Build}")"></script> | |||
</body> | |||
</html> |
@@ -6,12 +6,11 @@ namespace DotNetCore.CAP.Dashboard.Pages | |||
{ | |||
internal partial class NodePage | |||
{ | |||
private IList<Node> _nodes; | |||
private INodeDiscoveryProvider _discoveryProvider; | |||
private IList<Node> _nodes; | |||
public NodePage() | |||
{ | |||
} | |||
public NodePage(string id) | |||
@@ -28,10 +27,10 @@ namespace DotNetCore.CAP.Dashboard.Pages | |||
if (_nodes == null) | |||
{ | |||
_discoveryProvider = RequestServices.GetService<INodeDiscoveryProvider>(); | |||
_nodes = _discoveryProvider.GetNodes().GetAwaiter().GetResult(); | |||
_nodes = _discoveryProvider.GetNodes().GetAwaiter().GetResult(); | |||
} | |||
return _nodes; | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -1,8 +1,7 @@ | |||
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ | |||
@using DotNetCore.CAP.Dashboard | |||
@using DotNetCore.CAP.Dashboard.Pages | |||
@using DotNetCore.CAP.Dashboard.Resources | |||
@inherits RazorPage | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
@{ | |||
Layout = new LayoutPage(Strings.NodePage_Title); | |||
} | |||
@@ -21,29 +20,29 @@ | |||
<div class="table-responsive"> | |||
<table class="table"> | |||
<thead> | |||
<tr> | |||
<th width="10%">编号</th> | |||
<th width="20%">节点名称</th> | |||
<th width="20%">IP地址</th> | |||
<th width="7%">端口号</th> | |||
<th>Tags</th> | |||
<th width="20%">操作</th> | |||
</tr> | |||
<tr> | |||
<th width="10%">编号</th> | |||
<th width="20%">节点名称</th> | |||
<th width="20%">IP地址</th> | |||
<th width="7%">端口号</th> | |||
<th>Tags</th> | |||
<th width="20%">操作</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
@foreach (var node in Nodes) | |||
{ | |||
<tr class="@(CurrentNodeId == node.Id? "active":null )"> | |||
<td>@node.Id</td> | |||
<td>@node.Name</td> | |||
<td>@node.Address</td> | |||
<td>@node.Port</td> | |||
<td>@node.Tags</td> | |||
<td> | |||
@Html.NodeSwitchLink(node.Id) | |||
</td> | |||
</tr> | |||
} | |||
@foreach (var node in Nodes) | |||
{ | |||
<tr class="@(CurrentNodeId == node.Id ? "active" : null)"> | |||
<td>@node.Id</td> | |||
<td>@node.Name</td> | |||
<td>@node.Address</td> | |||
<td>@node.Port</td> | |||
<td>@node.Tags</td> | |||
<td> | |||
@Html.NodeSwitchLink(node.Id) | |||
</td> | |||
</tr> | |||
} | |||
</tbody> | |||
</table> | |||
</div> | |||
@@ -13,14 +13,12 @@ namespace DotNetCore.CAP.Dashboard.Pages | |||
public int GetTotal(IMonitoringApi api) | |||
{ | |||
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, | |||
StringComparison.CurrentCultureIgnoreCase)) | |||
return api.PublishedSucceededCount(); | |||
} | |||
if (string.Equals(StatusName, Infrastructure.StatusName.Processing, StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
if (string.Equals(StatusName, Infrastructure.StatusName.Processing, | |||
StringComparison.CurrentCultureIgnoreCase)) | |||
return api.PublishedProcessingCount(); | |||
} | |||
return api.PublishedFailedCount(); | |||
} | |||
} |
@@ -1,11 +1,11 @@ | |||
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ | |||
@using System | |||
@using DotNetCore.CAP.Models; | |||
@using DotNetCore.CAP.Dashboard | |||
@using DotNetCore.CAP.Dashboard.Pages | |||
@using DotNetCore.CAP.Dashboard.Monitoring | |||
@using DotNetCore.CAP.Dashboard.Pages | |||
@using DotNetCore.CAP.Dashboard.Resources | |||
@inherits RazorPage | |||
@using DotNetCore.CAP.Models | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
@{ | |||
Layout = new LayoutPage(Strings.PublishedMessagesPage_Title); | |||
@@ -13,8 +13,8 @@ | |||
int.TryParse(Query("from"), out from); | |||
int.TryParse(Query("count"), out perPage); | |||
string name = Query("name"); | |||
string content = Query("content"); | |||
var name = Query("name"); | |||
var content = Query("content"); | |||
var monitor = Storage.GetMonitoringApi(); | |||
var pager = new Pager(from, perPage, GetTotal(monitor)); | |||
@@ -49,11 +49,11 @@ | |||
<div class="btn-toolbar btn-toolbar-top"> | |||
<form class="row"> | |||
<span class="col-md-3"> | |||
<input type="text" class="form-control" name="name" value="@Query("name")" placeholder="@Strings.MessagesPage_Query_MessageName" /> | |||
<input type="text" class="form-control" name="name" value="@Query("name")" placeholder="@Strings.MessagesPage_Query_MessageName"/> | |||
</span> | |||
<div class="col-md-5"> | |||
<div class="input-group"> | |||
<input type="text" class="form-control" name="content" value="@Query("content")" placeholder="@Strings.MessagesPage_Query_MessageBody" /> | |||
<input type="text" class="form-control" name="content" value="@Query("content")" placeholder="@Strings.MessagesPage_Query_MessageBody"/> | |||
<span class="input-group-btn"> | |||
<button class="btn btn-info">@Strings.MessagesPage_Query_Button</button> | |||
</span> | |||
@@ -76,51 +76,51 @@ | |||
<div class="table-responsive"> | |||
<table class="table"> | |||
<thead> | |||
<tr> | |||
<th style="width:60px;"> | |||
<input type="checkbox" class="js-jobs-list-select-all" /> | |||
</th> | |||
<th>@Strings.MessagesPage_Table_Code</th> | |||
<th>@Strings.MessagesPage_Table_Name</th> | |||
<th class="min-width">@Strings.MessagesPage_Table_Retries</th> | |||
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
<th>@Strings.MessagesPage_Table_State</th> | |||
} | |||
<th class="align-right">@Strings.MessagesPage_Table_ExpiresAt</th> | |||
</tr> | |||
<tr> | |||
<th style="width: 60px;"> | |||
<input type="checkbox" class="js-jobs-list-select-all"/> | |||
</th> | |||
<th>@Strings.MessagesPage_Table_Code</th> | |||
<th>@Strings.MessagesPage_Table_Name</th> | |||
<th class="min-width">@Strings.MessagesPage_Table_Retries</th> | |||
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
<th>@Strings.MessagesPage_Table_State</th> | |||
} | |||
<th class="align-right">@Strings.MessagesPage_Table_ExpiresAt</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
@foreach (var message in succeededMessages) | |||
{ | |||
<tr class="js-jobs-list-row hover"> | |||
<td> | |||
<input type="checkbox" class="js-jobs-list-checkbox" name="messages[]" value="@message.Id" /> | |||
</td> | |||
<td class="word-break"> | |||
<a href="javascript:;" data-url='@(Url.To("/published/message/")+message.Id)' class="openModal">#@message.Id</a> | |||
</td> | |||
<td> | |||
@message.Name | |||
</td> | |||
@foreach (var message in succeededMessages) | |||
{ | |||
<tr class="js-jobs-list-row hover"> | |||
<td> | |||
<input type="checkbox" class="js-jobs-list-checkbox" name="messages[]" value="@message.Id"/> | |||
</td> | |||
<td class="word-break"> | |||
<a href="javascript:;" data-url='@(Url.To("/published/message/") + message.Id)' class="openModal">#@message.Id</a> | |||
</td> | |||
<td> | |||
@message.Name | |||
</td> | |||
<td> | |||
@message.Retries | |||
</td> | |||
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
<td> | |||
@message.Retries | |||
@message.StatusName | |||
</td> | |||
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase)) | |||
} | |||
<td class="align-right"> | |||
@if (message.ExpiresAt.HasValue) | |||
{ | |||
<td> | |||
@message.StatusName | |||
</td> | |||
@Html.RelativeTime(message.ExpiresAt.Value) | |||
} | |||
<td class="align-right"> | |||
@if (message.ExpiresAt.HasValue) | |||
{ | |||
@Html.RelativeTime(message.ExpiresAt.Value) | |||
} | |||
</td> | |||
</td> | |||
</tr> | |||
} | |||
</tr> | |||
} | |||
</tbody> | |||
</table> | |||
</div> | |||
@@ -132,10 +132,12 @@ | |||
<div class="modal-dialog" role="document"> | |||
<div class="modal-content"> | |||
<div class="modal-header"> | |||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> | |||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |||
<span aria-hidden="true">×</span> | |||
</button> | |||
<h4 class="modal-title">Message Content</h4> | |||
</div> | |||
<div id="jsonContent" style="max-height:500px;overflow-y:auto;" class="modal-body"> | |||
<div id="jsonContent" style="max-height: 500px; overflow-y: auto;" class="modal-body"> | |||
</div> | |||
<div class="modal-footer"> | |||
<button type="button" class="btn btn-sm btn-primary" id="formatBtn" onclick="">@Strings.MessagesPage_Modal_Format</button> | |||
@@ -13,14 +13,12 @@ namespace DotNetCore.CAP.Dashboard.Pages | |||
public int GetTotal(IMonitoringApi api) | |||
{ | |||
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
if (string.Equals(StatusName, Infrastructure.StatusName.Succeeded, | |||
StringComparison.CurrentCultureIgnoreCase)) | |||
return api.ReceivedSucceededCount(); | |||
} | |||
if (string.Equals(StatusName, Infrastructure.StatusName.Processing, StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
if (string.Equals(StatusName, Infrastructure.StatusName.Processing, | |||
StringComparison.CurrentCultureIgnoreCase)) | |||
return api.ReceivedProcessingCount(); | |||
} | |||
return api.ReceivedFailedCount(); | |||
} | |||
} |
@@ -1,11 +1,11 @@ | |||
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ | |||
@using System | |||
@using DotNetCore.CAP.Models; | |||
@using DotNetCore.CAP.Dashboard | |||
@using DotNetCore.CAP.Dashboard.Pages | |||
@using DotNetCore.CAP.Dashboard.Monitoring | |||
@using DotNetCore.CAP.Dashboard.Pages | |||
@using DotNetCore.CAP.Dashboard.Resources | |||
@inherits RazorPage | |||
@using DotNetCore.CAP.Models | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
@{ | |||
Layout = new LayoutPage(Strings.ReceivedMessagesPage_Title); | |||
@@ -13,9 +13,9 @@ | |||
int.TryParse(Query("from"), out from); | |||
int.TryParse(Query("count"), out perPage); | |||
string group = Query("group"); | |||
string name = Query("name"); | |||
string content = Query("content"); | |||
var group = Query("group"); | |||
var name = Query("name"); | |||
var content = Query("content"); | |||
var monitor = Storage.GetMonitoringApi(); | |||
var pager = new Pager(from, perPage, GetTotal(monitor)); | |||
@@ -51,14 +51,14 @@ | |||
<div class="btn-toolbar btn-toolbar-top"> | |||
<form class="row"> | |||
<span class="col-md-2"> | |||
<input type="text" class="form-control" name="group" value="@Query("group")" placeholder="@Strings.MessagesPage_Query_MessageGroup" /> | |||
<input type="text" class="form-control" name="group" value="@Query("group")" placeholder="@Strings.MessagesPage_Query_MessageGroup"/> | |||
</span> | |||
<span class="col-md-3"> | |||
<input type="text" class="form-control" name="name" value="@Query("name")" placeholder="@Strings.MessagesPage_Query_MessageName" /> | |||
<input type="text" class="form-control" name="name" value="@Query("name")" placeholder="@Strings.MessagesPage_Query_MessageName"/> | |||
</span> | |||
<div class="col-md-5"> | |||
<div class="input-group"> | |||
<input type="text" class="form-control" name="content" value="@Query("content")" placeholder="@Strings.MessagesPage_Query_MessageBody" /> | |||
<input type="text" class="form-control" name="content" value="@Query("content")" placeholder="@Strings.MessagesPage_Query_MessageBody"/> | |||
<span class="input-group-btn"> | |||
<button class="btn btn-info">@Strings.MessagesPage_Query_Button</button> | |||
</span> | |||
@@ -81,55 +81,55 @@ | |||
<div class="table-responsive"> | |||
<table class="table"> | |||
<thead> | |||
<tr> | |||
<th style="width:60px;"> | |||
<input type="checkbox" class="js-jobs-list-select-all" /> | |||
</th> | |||
<th>@Strings.MessagesPage_Table_Code</th> | |||
<th>@Strings.MessagesPage_Table_Group</th> | |||
<th>@Strings.MessagesPage_Table_Name</th> | |||
<th class="min-width">@Strings.MessagesPage_Table_Retries</th> | |||
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
<th>@Strings.MessagesPage_Table_State</th> | |||
} | |||
<th class="align-right">@Strings.MessagesPage_Table_ExpiresAt</th> | |||
</tr> | |||
<tr> | |||
<th style="width: 60px;"> | |||
<input type="checkbox" class="js-jobs-list-select-all"/> | |||
</th> | |||
<th>@Strings.MessagesPage_Table_Code</th> | |||
<th>@Strings.MessagesPage_Table_Group</th> | |||
<th>@Strings.MessagesPage_Table_Name</th> | |||
<th class="min-width">@Strings.MessagesPage_Table_Retries</th> | |||
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
<th>@Strings.MessagesPage_Table_State</th> | |||
} | |||
<th class="align-right">@Strings.MessagesPage_Table_ExpiresAt</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
@foreach (var message in succeededMessages) | |||
{ | |||
<tr class="js-jobs-list-row hover"> | |||
<td> | |||
<input type="checkbox" class="js-jobs-list-checkbox" name="messages[]" value="@message.Id" /> | |||
</td> | |||
<td class="word-break"> | |||
<a href="javascript:;" data-url='@(Url.To("/received/message/")+message.Id)' class="openModal">#@message.Id</a> | |||
</td> | |||
<td> | |||
@message.Group | |||
</td> | |||
<td> | |||
@message.Name | |||
</td> | |||
@foreach (var message in succeededMessages) | |||
{ | |||
<tr class="js-jobs-list-row hover"> | |||
<td> | |||
<input type="checkbox" class="js-jobs-list-checkbox" name="messages[]" value="@message.Id"/> | |||
</td> | |||
<td class="word-break"> | |||
<a href="javascript:;" data-url='@(Url.To("/received/message/") + message.Id)' class="openModal">#@message.Id</a> | |||
</td> | |||
<td> | |||
@message.Group | |||
</td> | |||
<td> | |||
@message.Name | |||
</td> | |||
<td> | |||
@message.Retries | |||
</td> | |||
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase)) | |||
{ | |||
<td> | |||
@message.Retries | |||
@message.StatusName | |||
</td> | |||
@if (string.Equals(StatusName, "Processing", StringComparison.CurrentCultureIgnoreCase)) | |||
} | |||
<td class="align-right"> | |||
@if (message.ExpiresAt.HasValue) | |||
{ | |||
<td> | |||
@message.StatusName | |||
</td> | |||
@Html.RelativeTime(message.ExpiresAt.Value) | |||
} | |||
<td class="align-right"> | |||
@if (message.ExpiresAt.HasValue) | |||
{ | |||
@Html.RelativeTime(message.ExpiresAt.Value) | |||
} | |||
</td> | |||
</td> | |||
</tr> | |||
} | |||
</tr> | |||
} | |||
</tbody> | |||
</table> | |||
</div> | |||
@@ -141,10 +141,12 @@ | |||
<div class="modal-dialog" role="document"> | |||
<div class="modal-content"> | |||
<div class="modal-header"> | |||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> | |||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |||
<span aria-hidden="true">×</span> | |||
</button> | |||
<h4 class="modal-title">Message Content</h4> | |||
</div> | |||
<div id="jsonContent" style="max-height:500px;overflow-y:auto;" class="modal-body"> | |||
<div id="jsonContent" style="max-height: 500px; overflow-y: auto;" class="modal-body"> | |||
</div> | |||
<div class="modal-footer"> | |||
<button type="button" class="btn btn-sm btn-primary" id="formatBtn" onclick="">@Strings.MessagesPage_Modal_Format</button> | |||
@@ -1,9 +1,8 @@ | |||
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ | |||
@using DotNetCore.CAP.Internal | |||
@using DotNetCore.CAP.Dashboard | |||
@using DotNetCore.CAP.Dashboard.Pages | |||
@using DotNetCore.CAP.Dashboard.Resources | |||
@inherits RazorPage | |||
@using DotNetCore.CAP.Internal | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
@{ | |||
Layout = new LayoutPage(Strings.SubscribersPage_Title); | |||
@@ -26,39 +25,39 @@ | |||
<div class="table-responsive"> | |||
<table class="table subscribe-table"> | |||
<thead> | |||
<tr> | |||
<th width="20%">@Strings.Common_Group</th> | |||
<th width="40%"> | |||
@Strings.Common_Name | |||
</th> | |||
<th>@Strings.Common_Method</th> | |||
</tr> | |||
<tr> | |||
<th width="20%">@Strings.Common_Group</th> | |||
<th width="40%"> | |||
@Strings.Common_Name | |||
</th> | |||
<th>@Strings.Common_Method</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
@foreach (var subscriber in subscribers) | |||
@foreach (var subscriber in subscribers) | |||
{ | |||
var i = 0; | |||
var rowCount = subscriber.Value.Count; | |||
foreach (var column in subscriber.Value) | |||
{ | |||
var i = 0; | |||
var rowCount = subscriber.Value.Count; | |||
foreach (var column in subscriber.Value) | |||
{ | |||
<tr> | |||
@if (i == 0) | |||
{ | |||
<td rowspan="@rowCount">@subscriber.Key</td> | |||
} | |||
<td>@column.Attribute.Name</td> | |||
<td> | |||
<span style="color:#00bcd4">@column.ImplTypeInfo.Name</span>: | |||
<div class="job-snippet-code"> | |||
<code> | |||
<pre>@Html.MethodEscaped(column.MethodInfo)</pre> | |||
</code> | |||
</div> | |||
</td> | |||
</tr> | |||
i++; | |||
} | |||
<tr> | |||
@if (i == 0) | |||
{ | |||
<td rowspan="@rowCount">@subscriber.Key</td> | |||
} | |||
<td>@column.Attribute.Name</td> | |||
<td> | |||
<span style="color: #00bcd4">@column.ImplTypeInfo.Name</span>: | |||
<div class="job-snippet-code"> | |||
<code> | |||
<pre>@Html.MethodEscaped(column.MethodInfo)</pre> | |||
</code> | |||
</div> | |||
</td> | |||
</tr> | |||
i++; | |||
} | |||
} | |||
</tbody> | |||
</table> | |||
</div> | |||
@@ -7,7 +7,7 @@ | |||
var className = metric == null ? "metric-null" : metric.Style.ToClassName(); | |||
var highlighted = metric != null && metric.Highlighted ? "highlighted" : null; | |||
} | |||
<div class="metric @className @highlighted"> | |||
<div class="metric @className @highlighted"> | |||
<div class="metric-body" data-metric="@DashboardMetric.Name"> | |||
@(metric?.Value) | |||
</div> | |||
@@ -1,12 +1,17 @@ | |||
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ | |||
@using DotNetCore.CAP.Dashboard | |||
@inherits RazorPage | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
<ol class="breadcrumb"> | |||
<li><a href="@Url.Home()"><span class="glyphicon glyphicon-home"></span></a></li> | |||
<li> | |||
<a href="@Url.Home()"> | |||
<span class="glyphicon glyphicon-home"></span> | |||
</a> | |||
</li> | |||
@foreach (var item in Items) | |||
{ | |||
<li><a href="@item.Value">@item.Key</a></li> | |||
<li> | |||
<a href="@item.Value">@item.Key</a> | |||
</li> | |||
} | |||
<li class="active">@Title</li> | |||
</ol> |
@@ -1,6 +1,6 @@ | |||
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ | |||
@using DotNetCore.CAP.Dashboard | |||
@inherits RazorPage | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
@if (NavigationMenu.Items.Count > 0) | |||
{ | |||
<ul class="nav navbar-nav"> | |||
@@ -8,7 +8,10 @@ | |||
{ | |||
var itemValue = item(this); | |||
if (itemValue == null) { continue; } | |||
if (itemValue == null) | |||
{ | |||
continue; | |||
} | |||
<li class="@(itemValue.Active ? "active" : null)"> | |||
<a href="@itemValue.Url"> | |||
@@ -1,6 +1,6 @@ | |||
namespace DotNetCore.CAP.Dashboard.Pages | |||
{ | |||
partial class Paginator | |||
internal partial class Paginator | |||
{ | |||
private readonly Pager _pager; | |||
@@ -1,8 +1,8 @@ | |||
@* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ | |||
@using DotNetCore.CAP.Dashboard | |||
@using DotNetCore.CAP.Dashboard.Resources; | |||
@inherits RazorPage | |||
@using DotNetCore.CAP.Dashboard | |||
@using DotNetCore.CAP.Dashboard.Resources | |||
@inherits DotNetCore.CAP.Dashboard.RazorPage | |||
<div class="btn-toolbar"> | |||
@if (_pager.TotalPageCount > 1) | |||
{ | |||
@@ -13,7 +13,7 @@ | |||
{ | |||
case Pager.ItemType.Page: | |||
<a href="@_pager.PageUrl(page.PageIndex)" class="btn btn-default @(_pager.CurrentPage == page.PageIndex ? "active" : null)"> | |||
@page.PageIndex | |||
@page.PageIndex | |||
</a> | |||
break; | |||
case Pager.ItemType.NextPage: | |||
@@ -40,4 +40,4 @@ | |||
<div class="btn-toolbar-label"> | |||
@Strings.Paginator_TotalItems: @_pager.TotalRecordCount | |||
</div> | |||
</div> | |||
</div> |
@@ -1,6 +1,6 @@ | |||
namespace DotNetCore.CAP.Dashboard.Pages | |||
{ | |||
partial class PerPageSelector | |||
internal partial class PerPageSelector | |||
{ | |||
private readonly Pager _pager; | |||