From 544325a720c2d9f1abd78884a1599735cb92f052 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 17 Nov 2017 10:08:27 +0800 Subject: [PATCH 01/81] Release 2.1 (#55) * add dashboard branch. * add dashboard * add helper methods. * Add data model. * add options. * add empty implement * add dashbaord * add dashboard * Fixed spelling error * rename file. * add dashbaord feature * impl dashboard storage * update nuget reference. * add pages. * deleted unused fiels. * modify resource. * update resource. * rename * update dashboard * rename * impl monitoring api interface. * update solution files. * update samples. * dashboard. * add jsonview resource files. * add json dispatcher. * modify published pages. * add routes. * update pages. * update resources. * update dashboard. * add dashboard of sql server storage impl * nesting files. * fixed query bug. * update resource. * remove some api. * add subscriber page. * update sample * add resource. * remove api. * add SubscriberPage * add resource * generate cshtml. * modify html two table to one table. * update resource. * update css * update subscriber page. * refactor. * cleanup. * cleanup code. * impl history monitoring api. * add home page recevied message real-time * add legend styles. * update js. * modify axis color. * add resource. * update dashboard home page. * update css. * update resource. * modify DefaultSucceedMessageExpirationAfter to 24 hours. * add resx, * add consul discovery. * remove unused file. * add node page. * add node page * node discovery * add kafka sqlserver sample. * update sample. * add okstats. * refactor. * fixed kafka client bugs. * modify node and subscriber pages * refactor. * add Gateway middleware * remove unused files. * update resource. * update gateway. * refactor. * update samples. * remove base middleware * add node switch click event. * add NodeId config to options. * upgrade dependent version. * add PathMatch configuration * update NodePage.cshtml * remove session * remove matchPath * refactor * refactor dashboard middleware * gateway proxy middleware function maturation * remove cookie exp * refactor and remove files. * add CapCache to cache server nodes. * refactor. * renamed message dto. * add extended interface of IContentSerializer and JsonContentSerializer * modify unit test * check the requirement when CAP start. * correct spelling * cleanup code. * add resources. * processing pages will contains Scheduled and Enqueued messages. * processing pages will contains Scheduled and Enqueued messages. * ignore NU1701 Warning. * renamed file. * refactor * implements dashboard interface. * rename reference class. * add mysql monitoring api impl * fix bug of connection driver. * remove cap.UseDashboard. It's will be automatically enabled by registerd services. * fix sql bug * cleanup code and fix spelling * fix postgre sql bug. * fix mysql sql bug. * when storage a received message raising an eception, we will reject the message to queue. * fix spelling mistake * add dashboard instructions to readme * modify error log content. * fix postger sql bug. * fix consul discovery bug. * add dashboard introduction to readme.md * update english resource. * Update README.md * renamed files. * fix postgre sql bug. * cleanup code. * update tests. * rename file. * update samples. * Improved query performance without lock table. (#36) * update sample. * update samples. * fix data reader uncolsed bug. * update add jsonproperty * refactor * revert FetchNextMessageAsync sql * add helper method. * rafactor subscriber handler. * add FailedRetryCount options. * rafactor publisher excutor. * add IPublishExecutor * add failed message processor. * inject failed message processor. * refactor sql storage. * fixed unit tests. * fixed unit test. * fixed postgresql tests. * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * add LAN ip to LocalRequestsOnlyAuthorizationFilter * add and update resource. * add current node name to layout page if user enabled node discovery * modify unit tests. * add callback message sende tests. * add deserlizer by type to IContentSerializer * refactor unit tests * add summary comment. * refactor async method. * add comment and fixed spell error. * refactor. * add custom content serializer extension to CapBuilder * add IMessagePacker * refactor. * add connection pool for kafka producer. * add determines whether the query is null. * add connection pool size config to KafkaOption. * fixed json JObject bug * add custom message wapper interface * remove unused code. * fixed callback topic send error bug. * refactor. * update unit tests. * update samples. * upgrade dependent package. * remove some class from Abstraction namespce to Internal. * optimize consumer related code * add ICallbackMessageSender to DI with singleton. * add and fixed some unit tests. * refactor namespace. * modify class protected level * assemblies internal class are visible to test project * add DeSerialize method to IContentSerializer with type deseralize * refactoring * Fix the phone style dispaly problem * add logs * refactoring * upgrading `Confluent.Kafka` package * Optimize message queue error message prompt. * reorganize error message prompts. * modify error message prompt * add summary comments. * modify dependent * disabled print connection closed log. see: https://github.com/edenhill/librdkafka/issues/516 * Update README.md * Update README.md * Update README.zh-cn.md * Update README.zh-cn.md * fix dashboard not config discovery throw exceptions bug. * Update README.md * update readme * Fixed serialized the message type bug. (#53) * Update README.md * Update README.md * refactoring * refactoring * update readme. * update readme. * add summary comment. * refactoring * optimizing publisher interface * update readme * update readme. * add summary comment. * add summary comment. * add summary comment. * add summary comment. * upgrading package * add summary comment. * optimize the RabbitMQ connection pool * fix the producer connection returned * dispose resource when connection pool is full --- .travis.yml | 46 +- CAP.sln | 11 +- README.md | 222 +++-- README.zh-cn.md | 193 ++-- appveyor.yml | 2 +- build/version.props | 4 +- .../Sample.Kafka.SqlServer/AppDbContext.cs | 14 + .../CmsContentSerializer.cs | 50 + .../Controllers/ValuesController.cs | 137 +++ samples/Sample.Kafka.SqlServer/Program.cs | 23 + .../Sample.Kafka.SqlServer.csproj | 22 + samples/Sample.Kafka.SqlServer/Startup.cs | 40 + samples/Sample.RabbitMQ.MySql/AppDbContext.cs | 4 +- .../Controllers/ValuesController.cs | 2 +- samples/Sample.RabbitMQ.MySql/Program.cs | 17 +- .../Sample.RabbitMQ.MySql.csproj | 10 +- samples/Sample.RabbitMQ.MySql/Startup.cs | 7 +- .../AppDbContext.cs | 2 +- samples/Sample.RabbitMQ.PostgreSql/Startup.cs | 21 +- .../Sample.RabbitMQ.SqlServer/AppDbContext.cs | 8 +- .../Controllers/ValuesController.cs | 23 +- .../20170824130007_AddPersons.Designer.cs | 40 + .../Migrations/20170824130007_AddPersons.cs | 33 + .../Migrations/AppDbContextModelSnapshot.cs | 39 + samples/Sample.RabbitMQ.SqlServer/Program.cs | 19 +- .../Sample.RabbitMQ.SqlServer.csproj | 9 +- .../Services/ICmsService.cs | 13 + .../Services/IOrderService.cs | 12 + .../Services/Impl/CmsService.cs | 16 + .../Services/Impl/OrderService.cs | 17 + samples/Sample.RabbitMQ.SqlServer/Startup.cs | 20 +- .../CAP.KafkaCapOptionsExtension.cs | 4 + src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs | 52 +- .../CAP.Options.Extensions.cs | 7 +- src/DotNetCore.CAP.Kafka/ConnectionPool.cs | 78 ++ .../DotNetCore.CAP.Kafka.csproj | 4 +- src/DotNetCore.CAP.Kafka/IConnectionPool.cs | 12 + .../KafkaConsumerClient.cs | 38 +- .../PublishQueueExecutor.cs | 52 +- src/DotNetCore.CAP.MySql/CAP.EFOptions.cs | 2 +- .../CAP.MySqlCapOptionsExtension.cs | 11 +- src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs | 1 + .../CAP.Options.Extensions.cs | 12 +- src/DotNetCore.CAP.MySql/CapPublisher.cs | 39 +- .../DotNetCore.CAP.MySql.csproj | 2 +- .../IAdditionalProcessor.Default.cs | 21 +- .../MySqlFetchedMessage.cs | 6 +- .../MySqlMonitoringApi.cs | 194 ++++ src/DotNetCore.CAP.MySql/MySqlStorage.cs | 60 +- .../MySqlStorageConnection.cs | 99 +- .../MySqlStorageTransaction.cs | 18 +- .../CAP.EFOptions.cs | 2 +- .../CAP.Options.Extensions.cs | 12 +- .../CAP.PostgreSqlCapOptionsExtension.cs | 9 +- .../CAP.PostgreSqlOptions.cs | 1 + src/DotNetCore.CAP.PostgreSql/CapPublisher.cs | 37 +- .../IAdditionalProcessor.Default.cs | 23 +- .../PostgreSqlFetchedMessage.cs | 6 +- .../PostgreSqlMonitoringApi.cs | 195 ++++ .../PostgreSqlStorage.cs | 59 +- .../PostgreSqlStorageConnection.cs | 79 +- .../PostgreSqlStorageTransaction.cs | 22 +- .../CAP.Options.Extensions.cs | 5 +- .../CAP.RabbiMQOptions.cs | 3 +- .../CAP.RabbitMQCapOptionsExtension.cs | 7 +- .../ConnectionChannelPool.cs | 111 +++ src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs | 91 -- .../IConnectionChannelPool.cs | 13 + .../IConnectionPool.cs | 14 - .../PublishQueueExecutor.cs | 42 +- .../RabbitMQConsumerClient.cs | 61 +- .../RabbitMQConsumerClientFactory.cs | 13 +- src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs | 2 +- .../CAP.Options.Extensions.cs | 12 +- .../CAP.SqlServerCapOptionsExtension.cs | 11 +- .../CAP.SqlServerOptions.cs | 1 + src/DotNetCore.CAP.SqlServer/CapPublisher.cs | 40 +- .../IAdditionalProcessor.Default.cs | 22 +- .../SqlServerFetchedMessage.cs | 6 +- .../SqlServerMonitoringApi.cs | 197 ++++ .../SqlServerStorage.cs | 60 +- .../SqlServerStorageConnection.cs | 78 +- .../SqlServerStorageTransaction.cs | 18 +- .../Abstractions/CapPublisherBase.cs | 82 +- .../Abstractions/ConsumerInvokerContext.cs | 20 - .../Abstractions/IConsumerInvoker.cs | 15 - .../Abstractions/IContentSerializer.cs | 60 ++ .../Abstractions/IModelBinderFactory.cs | 18 + .../Abstractions/ISubscriberExecutor.cs | 17 + .../ModelBinding/ModelBindingResult.cs | 40 +- .../Abstractions/TopicAttribute.cs | 14 +- .../CAP.AppBuilderExtensions.cs | 46 +- src/DotNetCore.CAP/CAP.Builder.cs | 60 +- src/DotNetCore.CAP/CAP.Options.cs | 41 +- .../CAP.ServiceCollectionExtensions.cs | 32 +- .../Dashboard/BatchCommandDispatcher.cs | 34 + .../Dashboard/CAP.DashboardMiddleware.cs | 68 ++ .../Dashboard/CAP.DashboardOptions.cs | 31 + .../CAP.DashboardOptionsExtensions.cs | 50 + .../Dashboard/CombinedResourceDispatcher.cs | 31 + .../Dashboard/CommandDispatcher.cs | 35 + .../Dashboard/Content/css/bootstrap.min.css | 5 + .../Dashboard/Content/css/cap.css | 460 +++++++++ .../Dashboard/Content/css/jsonview.min.css | 1 + .../Dashboard/Content/css/rickshaw.min.css | 1 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 ++++++ .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../Dashboard/Content/js/bootstrap.min.js | 7 + .../Dashboard/Content/js/cap.js | 655 +++++++++++++ .../Dashboard/Content/js/d3.layout.min.js | 1 + .../Dashboard/Content/js/d3.min.js | 2 + .../Dashboard/Content/js/jquery-2.1.4.min.js | 4 + .../Dashboard/Content/js/jsonview.min.js | 1 + .../Content/js/moment-with-locales.min.js | 84 ++ .../Dashboard/Content/js/moment.min.js | 6 + .../Dashboard/Content/js/rickshaw.min.js | 3 + .../Content/resx/Strings.Designer.cs | 918 ++++++++++++++++++ .../Dashboard/Content/resx/Strings.resx | 405 ++++++++ .../Dashboard/Content/resx/Strings.zh.resx | 384 ++++++++ .../Dashboard/DashboardContext.cs | 49 + .../Dashboard/DashboardMetric.cs | 24 + .../Dashboard/DashboardMetrics.cs | 160 +++ .../Dashboard/DashboardRequest.cs | 49 + .../Dashboard/DashboardResponse.cs | 54 ++ .../Dashboard/DashboardRoutes.cs | 135 +++ .../Dashboard/EmbeddedResourceDispatcher.cs | 57 ++ .../Dashboard/GatewayProxy/DownstreamUrl.cs | 12 + .../GatewayProxy/GatewayProxyMiddleware.cs | 141 +++ .../GatewayProxy/IRequestMapper.Default.cs | 121 +++ .../Dashboard/GatewayProxy/IRequestMapper.cs | 11 + .../Requester/HttpClientBuilder.cs | 57 ++ .../Requester/HttpClientHttpRequester.cs | 58 ++ .../GatewayProxy/Requester/IHttpClient.cs | 12 + .../Requester/IHttpClientBuilder.cs | 12 + .../Requester/IHttpClientCache.cs | 15 + .../GatewayProxy/Requester/IHttpRequester.cs | 10 + .../Requester/MemoryHttpClientCache.cs | 43 + src/DotNetCore.CAP/Dashboard/HtmlHelper.cs | 273 ++++++ .../IDashboardAuthorizationFilter.cs | 7 + .../Dashboard/IDashboardDispatcher.cs | 9 + .../Dashboard/IMonitoringApi.cs | 30 + .../Dashboard/JsonDispatcher.cs | 46 + src/DotNetCore.CAP/Dashboard/JsonStats.cs | 54 ++ .../LocalRequestsOnlyAuthorizationFilter.cs | 28 + src/DotNetCore.CAP/Dashboard/MenuItem.cs | 31 + .../Dashboard/MessageHistoryRenderer.cs | 195 ++++ .../Dashboard/MessagesSidebarMenu.cs | 57 ++ src/DotNetCore.CAP/Dashboard/Metric.cs | 41 + .../Dashboard/Monitoring/MessageDto.cs | 23 + .../Dashboard/Monitoring/MessageQueryDto.cs | 20 + .../Dashboard/Monitoring/ServerDto.cs | 9 + .../Dashboard/Monitoring/StatisticsDto.cs | 16 + .../Dashboard/NavigationMenu.cs | 46 + .../Dashboard/NonEscapedString.cs | 17 + src/DotNetCore.CAP/Dashboard/Pager.cs | 156 +++ .../Dashboard/Pages/BlockMetric.cs | 12 + .../Dashboard/Pages/Breadcrumbs.cs | 16 + .../Dashboard/Pages/HomePage.cs | 9 + .../Dashboard/Pages/HomePage.cshtml | 65 ++ .../Dashboard/Pages/HomePage.generated.cs | 354 +++++++ .../Dashboard/Pages/InlineMetric.cs | 12 + .../Dashboard/Pages/LayoutPage.cs | 12 + .../Dashboard/Pages/LayoutPage.cshtml | 83 ++ .../Dashboard/Pages/LayoutPage.generated.cs | 326 +++++++ .../Dashboard/Pages/NodePage.cs | 40 + .../Dashboard/Pages/NodePage.cshtml | 51 + .../Dashboard/Pages/NodePage.generated.cs | 260 +++++ .../Dashboard/Pages/PublishedPage.cs | 25 + .../Dashboard/Pages/PublishedPage.cshtml | 154 +++ .../Pages/PublishedPage.generated.cs | 568 +++++++++++ .../Dashboard/Pages/ReceivedPage.cs | 25 + .../Dashboard/Pages/ReceivedPage.cshtml | 163 ++++ .../Dashboard/Pages/ReceivedPage.generated.cs | 617 ++++++++++++ .../Dashboard/Pages/SidebarMenu.cs | 15 + .../Dashboard/Pages/SubscriberPage.cshtml | 66 ++ .../Pages/SubscriberPage.generated.cs | 276 ++++++ .../Dashboard/Pages/_BlockMetric.cshtml | 17 + .../Dashboard/Pages/_BlockMetric.generated.cs | 113 +++ .../Dashboard/Pages/_Breadcrumbs.cshtml | 17 + .../Dashboard/Pages/_Breadcrumbs.generated.cs | 105 ++ .../Dashboard/Pages/_InlineMetric.cshtml | 9 + .../Pages/_InlineMetric.generated.cs | 96 ++ .../Dashboard/Pages/_Navigation.cshtml | 28 + .../Dashboard/Pages/_Navigation.generated.cs | 141 +++ .../Dashboard/Pages/_Paginator.cs | 12 + .../Dashboard/Pages/_Paginator.cshtml | 43 + .../Dashboard/Pages/_Paginator.generated.cs | 248 +++++ .../Dashboard/Pages/_PerPageSelector.cs | 12 + .../Dashboard/Pages/_PerPageSelector.cshtml | 16 + .../Pages/_PerPageSelector.generated.cs | 106 ++ .../Dashboard/Pages/_SidebarMenu.cshtml | 20 + .../Dashboard/Pages/_SidebarMenu.generated.cs | 139 +++ src/DotNetCore.CAP/Dashboard/RazorPage.cs | 171 ++++ .../Dashboard/RazorPageDispatcher.cs | 26 + .../Dashboard/RouteCollection.cs | 45 + .../Dashboard/RouteCollectionExtensions.cs | 68 ++ src/DotNetCore.CAP/Dashboard/UrlHelper.cs | 52 + src/DotNetCore.CAP/DotNetCore.CAP.csproj | 136 ++- src/DotNetCore.CAP/IBootstrapper.Default.cs | 59 +- src/DotNetCore.CAP/ICallbackPublisher.cs | 8 +- src/DotNetCore.CAP/ICapOptionsExtension.cs | 7 + src/DotNetCore.CAP/ICapPublisher.cs | 22 +- src/DotNetCore.CAP/IConsumerClient.cs | 20 +- src/DotNetCore.CAP/IConsumerClientFactory.cs | 4 +- .../IConsumerHandler.Default.cs | 63 +- src/DotNetCore.CAP/IConsumerHandler.cs | 3 + src/DotNetCore.CAP/IProcessingServer.cs | 3 +- src/DotNetCore.CAP/IPublishExecutor.cs | 18 + .../IQueueExecutor.Publish.Base.cs | 34 +- src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs | 144 --- .../IQueueExecutor.Subscribe.cs | 131 +++ src/DotNetCore.CAP/IStorage.cs | 11 + src/DotNetCore.CAP/IStorageConnection.cs | 22 +- src/DotNetCore.CAP/IStorageTransaction.cs | 4 + src/DotNetCore.CAP/Infrastructure/Helper.cs | 90 +- src/DotNetCore.CAP/Infrastructure/ObjectId.cs | 178 ++-- .../Infrastructure/WaitHandleEx.cs | 2 +- .../Infrastructure/WebHookProvider.cs | 12 - src/DotNetCore.CAP/Internal/CapCache.cs | 349 +++++++ .../ConsumerContext.cs | 16 +- .../Internal/ConsumerExecutedResult.cs | 22 + .../ConsumerExecutorDescriptor.cs | 5 +- .../Internal/ConsumerInvokerFactory.cs | 17 +- .../Internal/HashCodeCombiner.cs | 9 +- .../ICallbackMessageSender.Default.cs | 63 ++ .../Internal/ICallbackMessageSender.cs | 9 + .../Internal/IConsumerInvoker.Default.cs | 111 +-- .../Internal/IConsumerInvoker.cs | 16 + .../Internal/IConsumerInvokerFactory.cs | 8 +- .../IConsumerServiceSelector.Default.cs | 32 +- .../IConsumerServiceSelector.cs | 20 +- .../Internal/IContentSerializer.Json.cs | 24 + .../Internal/IMessagePacker.Default.cs | 25 + .../Internal/IModelBinder.ComplexType.cs | 10 +- .../Internal/IModelBinder.SimpleType.cs | 34 +- .../Internal/IModelBinderFactory.cs | 10 - .../Internal/ISubscriberExecutor.Default.cs | 61 ++ .../Internal/MethodMatcherCache.cs | 14 +- .../Internal/ModelBinderFactory.cs | 34 +- .../ObjectMethodExecutor/AwaitableInfo.cs | 5 +- .../CoercedAwaitableInfo.cs | 7 +- .../ObjectMethodExecutor.cs | 86 +- .../ObjectMethodExecutorAwaitable.cs | 10 +- .../ObjectMethodExecutorFSharpSupport.cs | 25 +- .../Internal/SubscriberNotFoundException.cs | 3 +- src/DotNetCore.CAP/LoggerExtensions.cs | 67 +- src/DotNetCore.CAP/MessageContext.cs | 17 + src/DotNetCore.CAP/Models/CapMessageDto.cs | 38 + .../Models/CapPublishedMessage.cs | 5 +- src/DotNetCore.CAP/Models/CapQueue.cs | 2 +- .../Models/CapReceivedMessage.cs | 5 +- src/DotNetCore.CAP/Models/Message.cs | 26 - .../NodeDiscovery/CAP.DiscoveryOptions.cs | 36 + .../CAP.DiscoveryOptionsExtensions.cs | 54 ++ .../IDiscoveryProviderFactory.Default.cs | 23 + .../IDiscoveryProviderFactory.cs | 7 + .../INodeDiscoveryProvider.Consul.cs | 103 ++ .../NodeDiscovery/INodeDiscoveryProvider.cs | 12 + .../NodeDiscovery/IProcessingServer.Consul.cs | 32 + src/DotNetCore.CAP/NodeDiscovery/Node.cs | 15 + src/DotNetCore.CAP/OperateResult.cs | 39 +- .../Processor/IDispatcher.Default.cs | 25 +- .../Processor/IProcessingServer.Cap.cs | 38 +- ...ssor.FailedJob.cs => IProcessor.Failed.cs} | 73 +- .../Processor/IProcessor.InfiniteRetry.cs | 11 +- .../Processor/IProcessor.PublishQueuer.cs | 13 +- .../Processor/IProcessor.SubscribeQueuer.cs | 17 +- .../Processor/ProcessingContext.cs | 15 +- src/DotNetCore.CAP/Processor/RetryBehavior.cs | 7 +- .../Processor/States/IState.Succeeded.cs | 12 +- .../Processor/States/IStateChanger.Default.cs | 8 - src/DotNetCore.CAP/Properties/AssemblyInfo.cs | 3 + src/DotNetCore.CAP/QueueExecutorFactory.cs | 6 +- .../ConnectionUtil.cs | 2 +- .../DotNetCore.CAP.MySql.Test.csproj | 8 +- .../MySqlStorageConnectionTest.cs | 16 +- test/DotNetCore.CAP.MySql.Test/TestHost.cs | 1 + .../DotNetCore.CAP.PostgreSql.Test.csproj | 6 +- .../PostgreSqlStorageConnectionTest.cs | 5 +- .../TestHost.cs | 1 + .../DotNetCore.CAP.SqlServer.Test.csproj | 6 +- .../SqlServerStorageConnectionTest.cs | 5 +- .../DotNetCore.CAP.SqlServer.Test/TestHost.cs | 1 + test/DotNetCore.CAP.Test/CAP.BuilderTest.cs | 67 +- .../CallbackMessageSenderTest.cs | 87 ++ .../ConsumerInvokerFactoryTest.cs | 56 +- .../ConsumerServiceSelectorTest.cs | 1 + .../DotNetCore.CAP.Test.csproj | 6 +- .../JsonContentSerializerTest.cs | 62 ++ .../ModelBinderFactoryTest.cs | 5 +- test/DotNetCore.CAP.Test/OperateResultTest.cs | 4 +- .../QueueExecutorFactoryTest.cs | 3 + test/DotNetCore.CAP.Test/Sample.cs | 4 + 296 files changed, 14846 insertions(+), 1875 deletions(-) create mode 100644 samples/Sample.Kafka.SqlServer/AppDbContext.cs create mode 100644 samples/Sample.Kafka.SqlServer/CmsContentSerializer.cs create mode 100644 samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs create mode 100644 samples/Sample.Kafka.SqlServer/Program.cs create mode 100644 samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj create mode 100644 samples/Sample.Kafka.SqlServer/Startup.cs create mode 100644 samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.Designer.cs create mode 100644 samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.cs create mode 100644 samples/Sample.RabbitMQ.SqlServer/Migrations/AppDbContextModelSnapshot.cs create mode 100644 samples/Sample.RabbitMQ.SqlServer/Services/ICmsService.cs create mode 100644 samples/Sample.RabbitMQ.SqlServer/Services/IOrderService.cs create mode 100644 samples/Sample.RabbitMQ.SqlServer/Services/Impl/CmsService.cs create mode 100644 samples/Sample.RabbitMQ.SqlServer/Services/Impl/OrderService.cs create mode 100644 src/DotNetCore.CAP.Kafka/ConnectionPool.cs create mode 100644 src/DotNetCore.CAP.Kafka/IConnectionPool.cs create mode 100644 src/DotNetCore.CAP.MySql/MySqlMonitoringApi.cs create mode 100644 src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs create mode 100644 src/DotNetCore.CAP.RabbitMQ/ConnectionChannelPool.cs delete mode 100644 src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs create mode 100644 src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.cs delete mode 100644 src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs create mode 100644 src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs delete mode 100644 src/DotNetCore.CAP/Abstractions/ConsumerInvokerContext.cs delete mode 100644 src/DotNetCore.CAP/Abstractions/IConsumerInvoker.cs create mode 100644 src/DotNetCore.CAP/Abstractions/IContentSerializer.cs create mode 100644 src/DotNetCore.CAP/Abstractions/IModelBinderFactory.cs create mode 100644 src/DotNetCore.CAP/Abstractions/ISubscriberExecutor.cs create mode 100644 src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs create mode 100644 src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs create mode 100644 src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs create mode 100644 src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs create mode 100644 src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs create mode 100644 src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Content/css/bootstrap.min.css create mode 100644 src/DotNetCore.CAP/Dashboard/Content/css/cap.css create mode 100644 src/DotNetCore.CAP/Dashboard/Content/css/jsonview.min.css create mode 100644 src/DotNetCore.CAP/Dashboard/Content/css/rickshaw.min.css create mode 100644 src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.eot create mode 100644 src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.svg create mode 100644 src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.ttf create mode 100644 src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.woff create mode 100644 src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.woff2 create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/bootstrap.min.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/cap.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/d3.layout.min.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/d3.min.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/jquery-2.1.4.min.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/jsonview.min.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/moment-with-locales.min.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/moment.min.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/js/rickshaw.min.js create mode 100644 src/DotNetCore.CAP/Dashboard/Content/resx/Strings.Designer.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Content/resx/Strings.resx create mode 100644 src/DotNetCore.CAP/Dashboard/Content/resx/Strings.zh.resx create mode 100644 src/DotNetCore.CAP/Dashboard/DashboardContext.cs create mode 100644 src/DotNetCore.CAP/Dashboard/DashboardMetric.cs create mode 100644 src/DotNetCore.CAP/Dashboard/DashboardMetrics.cs create mode 100644 src/DotNetCore.CAP/Dashboard/DashboardRequest.cs create mode 100644 src/DotNetCore.CAP/Dashboard/DashboardResponse.cs create mode 100644 src/DotNetCore.CAP/Dashboard/DashboardRoutes.cs create mode 100644 src/DotNetCore.CAP/Dashboard/EmbeddedResourceDispatcher.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/DownstreamUrl.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/GatewayProxyMiddleware.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.Default.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/IRequestMapper.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientBuilder.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/HttpClientHttpRequester.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClient.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientBuilder.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpClientCache.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/IHttpRequester.cs create mode 100644 src/DotNetCore.CAP/Dashboard/GatewayProxy/Requester/MemoryHttpClientCache.cs create mode 100644 src/DotNetCore.CAP/Dashboard/HtmlHelper.cs create mode 100644 src/DotNetCore.CAP/Dashboard/IDashboardAuthorizationFilter.cs create mode 100644 src/DotNetCore.CAP/Dashboard/IDashboardDispatcher.cs create mode 100644 src/DotNetCore.CAP/Dashboard/IMonitoringApi.cs create mode 100644 src/DotNetCore.CAP/Dashboard/JsonDispatcher.cs create mode 100644 src/DotNetCore.CAP/Dashboard/JsonStats.cs create mode 100644 src/DotNetCore.CAP/Dashboard/LocalRequestsOnlyAuthorizationFilter.cs create mode 100644 src/DotNetCore.CAP/Dashboard/MenuItem.cs create mode 100644 src/DotNetCore.CAP/Dashboard/MessageHistoryRenderer.cs create mode 100644 src/DotNetCore.CAP/Dashboard/MessagesSidebarMenu.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Metric.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Monitoring/MessageDto.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Monitoring/MessageQueryDto.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Monitoring/ServerDto.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Monitoring/StatisticsDto.cs create mode 100644 src/DotNetCore.CAP/Dashboard/NavigationMenu.cs create mode 100644 src/DotNetCore.CAP/Dashboard/NonEscapedString.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pager.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/BlockMetric.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/Breadcrumbs.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/HomePage.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/HomePage.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/HomePage.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/InlineMetric.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/LayoutPage.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/NodePage.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/NodePage.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/NodePage.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/PublishedPage.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/ReceivedPage.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/SidebarMenu.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/SubscriberPage.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/SubscriberPage.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_BlockMetric.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_BlockMetric.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_Breadcrumbs.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_Breadcrumbs.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_InlineMetric.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_InlineMetric.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_Navigation.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_Navigation.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_Paginator.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_Paginator.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_PerPageSelector.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_SidebarMenu.cshtml create mode 100644 src/DotNetCore.CAP/Dashboard/Pages/_SidebarMenu.generated.cs create mode 100644 src/DotNetCore.CAP/Dashboard/RazorPage.cs create mode 100644 src/DotNetCore.CAP/Dashboard/RazorPageDispatcher.cs create mode 100644 src/DotNetCore.CAP/Dashboard/RouteCollection.cs create mode 100644 src/DotNetCore.CAP/Dashboard/RouteCollectionExtensions.cs create mode 100644 src/DotNetCore.CAP/Dashboard/UrlHelper.cs create mode 100644 src/DotNetCore.CAP/IPublishExecutor.cs delete mode 100644 src/DotNetCore.CAP/IQueueExecutor.Subscibe.cs create mode 100644 src/DotNetCore.CAP/IQueueExecutor.Subscribe.cs delete mode 100644 src/DotNetCore.CAP/Infrastructure/WebHookProvider.cs create mode 100644 src/DotNetCore.CAP/Internal/CapCache.cs rename src/DotNetCore.CAP/{Abstractions => Internal}/ConsumerContext.cs (68%) create mode 100644 src/DotNetCore.CAP/Internal/ConsumerExecutedResult.cs rename src/DotNetCore.CAP/{Abstractions => Internal}/ConsumerExecutorDescriptor.cs (71%) create mode 100644 src/DotNetCore.CAP/Internal/ICallbackMessageSender.Default.cs create mode 100644 src/DotNetCore.CAP/Internal/ICallbackMessageSender.cs create mode 100644 src/DotNetCore.CAP/Internal/IConsumerInvoker.cs rename src/DotNetCore.CAP/{Abstractions => Internal}/IConsumerServiceSelector.cs (58%) create mode 100644 src/DotNetCore.CAP/Internal/IContentSerializer.Json.cs create mode 100644 src/DotNetCore.CAP/Internal/IMessagePacker.Default.cs delete mode 100644 src/DotNetCore.CAP/Internal/IModelBinderFactory.cs create mode 100644 src/DotNetCore.CAP/Internal/ISubscriberExecutor.Default.cs create mode 100644 src/DotNetCore.CAP/Models/CapMessageDto.cs delete mode 100644 src/DotNetCore.CAP/Models/Message.cs create mode 100644 src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptions.cs create mode 100644 src/DotNetCore.CAP/NodeDiscovery/CAP.DiscoveryOptionsExtensions.cs create mode 100644 src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.Default.cs create mode 100644 src/DotNetCore.CAP/NodeDiscovery/IDiscoveryProviderFactory.cs create mode 100644 src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.Consul.cs create mode 100644 src/DotNetCore.CAP/NodeDiscovery/INodeDiscoveryProvider.cs create mode 100644 src/DotNetCore.CAP/NodeDiscovery/IProcessingServer.Consul.cs create mode 100644 src/DotNetCore.CAP/NodeDiscovery/Node.cs rename src/DotNetCore.CAP/Processor/{IProcessor.FailedJob.cs => IProcessor.Failed.cs} (57%) create mode 100644 src/DotNetCore.CAP/Properties/AssemblyInfo.cs create mode 100644 test/DotNetCore.CAP.Test/CallbackMessageSenderTest.cs create mode 100644 test/DotNetCore.CAP.Test/JsonContentSerializerTest.cs diff --git a/.travis.yml b/.travis.yml index 52f2ebc..4d8bd00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,42 @@ -language: csharp +language: cpp sudo: required -mono: none +dist: trusty matrix: include: - os: linux dist: trusty # Ubuntu 14.04 - dotnet: 2.0.0 - mono: none - env: DOTNETCORE=1 sudo: required - os: osx osx_image: xcode8.3 # macOS 10.12 - dotnet: 2.0.0 - mono: none - env: DOTNETCORE=1 - -before_install: - - chmod a+x ./build.sh - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then ulimit -n 1024 ; fi - + +env: + global: + - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + - DOTNET_CLI_TELEMETRY_OPTOUT: 1 + - CLI_VERSION=2.0.0 + +addons: + apt: + packages: + - gettext + - libcurl4-openssl-dev + - libicu-dev + - libssl-dev + - libunwind8 + - zlib1g + +# Make sure build dependencies are installed. +before_install: + - if test "$TRAVIS_OS_NAME" == "osx"; then ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi + - export DOTNET_INSTALL_DIR="$PWD/.dotnetcli" + - export PATH="$DOTNET_INSTALL_DIR:$PATH" + install: - - export DOTNET_CLI_TELEMETRY_OPTOUT=1 - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then rvm get stable; brew update; brew install openssl; fi + - travis_retry curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 2.0 --version "$CLI_VERSION" --install-dir "$DOTNET_INSTALL_DIR" +# Run the build script script: - - ./build.sh \ No newline at end of file + - dotnet --info + - dotnet restore + - dotnet test test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj -f netcoreapp2.0 diff --git a/CAP.sln b/CAP.sln index c81828b..cee4bf6 100644 --- a/CAP.sln +++ b/CAP.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 +VisualStudioVersion = 15.0.26730.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B2AE124-6636-4DE9-83A3-70360DABD0C4}" EndProject @@ -60,7 +60,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.PostgreSql", "samples\Sample.RabbitMQ.PostgreSql\Sample.RabbitMQ.PostgreSql.csproj", "{A17E8E72-DFFC-4822-BB38-73D59A8B264E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetCore.CAP.PostgreSql.Test", "test\DotNetCore.CAP.PostgreSql.Test\DotNetCore.CAP.PostgreSql.Test.csproj", "{7CA3625D-1817-4695-881D-7E79A1E1DED2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.PostgreSql.Test", "test\DotNetCore.CAP.PostgreSql.Test\DotNetCore.CAP.PostgreSql.Test.csproj", "{7CA3625D-1817-4695-881D-7E79A1E1DED2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.SqlServer", "samples\Sample.Kafka.SqlServer\Sample.Kafka.SqlServer.csproj", "{573B4D39-5489-48B3-9B6C-5234249CB980}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -119,6 +121,10 @@ Global {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Release|Any CPU.ActiveCfg = Release|Any CPU {7CA3625D-1817-4695-881D-7E79A1E1DED2}.Release|Any CPU.Build.0 = Release|Any CPU + {573B4D39-5489-48B3-9B6C-5234249CB980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {573B4D39-5489-48B3-9B6C-5234249CB980}.Debug|Any CPU.Build.0 = Debug|Any CPU + {573B4D39-5489-48B3-9B6C-5234249CB980}.Release|Any CPU.ActiveCfg = Release|Any CPU + {573B4D39-5489-48B3-9B6C-5234249CB980}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -137,6 +143,7 @@ Global {82C403AB-ED68-4084-9A1D-11334F9F08F9} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {A17E8E72-DFFC-4822-BB38-73D59A8B264E} = {3A6B6931-A123-477A-9469-8B468B5385AF} {7CA3625D-1817-4695-881D-7E79A1E1DED2} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} + {573B4D39-5489-48B3-9B6C-5234249CB980} = {3A6B6931-A123-477A-9469-8B468B5385AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} diff --git a/README.md b/README.md index 5e6ce5f..14d2d09 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,13 @@ [![Member project of .NET China Foundation](https://img.shields.io/badge/member_project_of-.NET_CHINA-red.svg?style=flat&colorB=9E20C8)](https://github.com/dotnetcore) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dotnetcore/CAP/master/LICENSE.txt) -CAP is a .Net Standard library to achieve eventually consistent in distributed architectures system like SOA,MicroService. It is lightweight,easy to use and efficiently. +CAP is a library based on .Net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficiently. ## OverView -CAP is a library that used in an ASP.NET Core project, Of Course you can ues it in ASP.NET Core with .NET Framework. +In the process of building an SOA or MicroService system, we usually need to use the event to integrate each services. In the process, the simple use of message queue does not guarantee the reliability. CAP is adopted the local message table program integrated with the current database to solve the exception may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. -You can think of CAP as an EventBus because it has all the features of EventBus, and CAP provides a easier way to handle the publishing and subscribing than EventBus. - -CAP has the function of Message Persistence, and it makes messages reliability when your service is restarted or down. CAP provides a Publish Service based on Microsoft DI that integrates seamlessly with your business services and supports strong consistency transactions. +You can also use the CAP as an EventBus. The CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during the process of subscription and sending. This is a diagram of the CAP working in the ASP.NET Core MicroService architecture: @@ -32,22 +30,24 @@ You can run the following command to install the CAP in your project. PM> Install-Package DotNetCore.CAP ``` -If your Message Queue is using Kafka, you can: +If you want use Kafka to send integrating event, installing by: ``` PM> Install-Package DotNetCore.CAP.Kafka ``` -If your Message Queue is using RabbitMQ, you can: +If you want use RabbitMQ to send integrating event, installing by: ``` PM> Install-Package DotNetCore.CAP.RabbitMQ ``` -CAP supported SqlServer, MySql, PostgreSql as message store extension: +CAP supports SqlServer, MySql, PostgreSql as event log storage. ``` -//Select a database provider you are using + +// select a database provider you are using, event log table will integrate into. + PM> Install-Package DotNetCore.CAP.SqlServer PM> Install-Package DotNetCore.CAP.MySql PM> Install-Package DotNetCore.CAP.PostgreSql @@ -60,34 +60,34 @@ First,You need to config CAP in your Startup.cs: ```cs public void ConfigureServices(IServiceCollection services) { - ...... - - services.AddDbContext(); - - services.AddCap(x => - { - // If your SqlServer is using EF for data operations, you need to add the following configuration: - // Notice: You don't need to config x.UseSqlServer(""") again! - x.UseEntityFramework(); - - // If you are using Dapper,you need to add the config: - x.UseSqlServer("Your ConnectionStrings"); - //x.UseMySql("Your ConnectionStrings"); - //x.UsePostgreSql("Your ConnectionStrings"); - - // If your Message Queue is using RabbitMQ you need to add the config: - x.UseRabbitMQ("localhost"); - - // If your Message Queue is using Kafka you need to add the config: - x.UseKafka("localhost"); - }); + //...... + + services.AddDbContext(); + + services.AddCap(x => + { + // If you are using EF, you need to add the following configuration: + // Notice: You don't need to config x.UseSqlServer(""") again! CAP can autodiscovery. + x.UseEntityFramework(); + + // If you are using ado.net,you need to add the configuration: + x.UseSqlServer("Your ConnectionStrings"); + x.UseMySql("Your ConnectionStrings"); + x.UsePostgreSql("Your ConnectionStrings"); + + // If you are using RabbitMQ, you need to add the configuration: + x.UseRabbitMQ("localhost"); + + // If you are using Kafka, you need to add the configuration: + x.UseKafka("localhost"); + }); } public void Configure(IApplicationBuilder app) { - ..... + //..... - app.UseCap(); + app.UseCap(); } ``` @@ -96,37 +96,43 @@ public void Configure(IApplicationBuilder app) Inject `ICapPublisher` in your Controller, then use the `ICapPublisher` to send message -```cs +```c# public class PublishController : Controller { - private readonly ICapPublisher _publisher; - - public PublishController(ICapPublisher publisher) - { - _publisher = publisher; - } - - - [Route("~/checkAccount")] - public async Task PublishMessage() - { - // Specifies the message header and content to be sent - await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }); - - return Ok(); - } - - [Route("~/checkAccountWithTrans")] - public async Task PublishMessageWithTransaction([FromServices]AppDbContext dbContext) - { - using (var trans = dbContext.Database.BeginTransaction()) - { - await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }); - - trans.Commit(); - } - return Ok(); - } + [Route("~/publishWithTransactionUsingEF")] + public async Task PublishMessageWithTransactionUsingEF([FromServices]AppDbContext dbContext, [FromServices]ICapPublisher publisher) + { + using (var trans = dbContext.Database.BeginTransaction()) + { + // your business code + + //If you are using EF, CAP will automatic discovery current environment transaction, so you do not need to explicit pass parameters. + //Achieving atomicity between original database operation and the publish event log thanks to a local transaction. + await publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }); + + trans.Commit(); + } + return Ok(); + } + + [Route("~/publishWithTransactionUsingAdonet")] + public async Task PublishMessageWithTransactionUsingAdonet([FromServices]ICapPublisher publisher) + { + var connectionString = ""; + using (var sqlConnection = new SqlConnection(connectionString)) + { + sqlConnection.Open(); + using (var sqlTransaction = sqlConnection.BeginTransaction()) + { + // your business code + + publisher.Publish("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }, sqlTransaction); + + sqlTransaction.Commit(); + } + } + return Ok(); + } } ``` @@ -137,25 +143,16 @@ public class PublishController : Controller Add the Attribute `[CapSubscribe()]` on Action to subscribe message: -```cs +```c# public class PublishController : Controller { - private readonly ICapPublisher _publisher; - - public PublishController(ICapPublisher publisher) - { - _publisher = publisher; - } - - - [NoAction] - [CapSubscribe("xxx.services.account.check")] - public async Task CheckReceivedMessage(Person person) - { - Console.WriteLine(person.Name); - Console.WriteLine(person.Age); - return Task.CompletedTask; - } + [CapSubscribe("xxx.services.account.check")] + public async Task CheckReceivedMessage(Person person) + { + Console.WriteLine(person.Name); + Console.WriteLine(person.Age); + return Task.CompletedTask; + } } ``` @@ -164,37 +161,72 @@ public class PublishController : Controller If your subscribe method is not in the Controller,then your subscribe class need to Inheritance `ICapSubscribe`: -```cs +```c# namespace xxx.Service { - public interface ISubscriberService - { - public void CheckReceivedMessage(Person person); - } - - - public class SubscriberService: ISubscriberService, ICapSubscribe - { - [CapSubscribe("xxx.services.account.check")] - public void CheckReceivedMessage(Person person) - { - - } - } + public interface ISubscriberService + { + public void CheckReceivedMessage(Person person); + } + + + public class SubscriberService: ISubscriberService, ICapSubscribe + { + [CapSubscribe("xxx.services.account.check")] + public void CheckReceivedMessage(Person person) + { + } + } } ``` Then inject your `ISubscriberService` class in Startup.cs -```cs +```c# public void ConfigureServices(IServiceCollection services) { - services.AddTransient(); + services.AddTransient(); } ``` +### Dashboard + +CAP 2.1 and above provides the dashboard pages, you can easily view the sent and received messages. In addition, you can also view the message status in real time on the dashboard. + +In the distributed environment, the dashboard built-in integrated [Consul](http://consul.io) as a node discovery, while the realization of the gateway agent function, you can also easily view the node or other node data, It's like you are visiting local resources. + +```c# +services.AddCap(x => +{ + //... + + // Register Dashboard + x.UseDashboard(); + + // Register to Consul + x.UseDiscovery(d => + { + d.DiscoveryServerHostName = "localhost"; + d.DiscoveryServerPort = 8500; + d.CurrentNodeHostName = "localhost"; + d.CurrentNodePort = 5800; + d.NodeId = 1; + d.NodeName = "CAP No.1 Node"; + }); +}); +``` + +![dashboard](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220827302-189215107.png) + +![received](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220934115-1107747665.png) + +![subscibers](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220949193-884674167.png) + +![nodes](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004221001880-1162918362.png) + + ## Contribute One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes. diff --git a/README.zh-cn.md b/README.zh-cn.md index 6b65229..76d18dc 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -6,17 +6,16 @@ [![Member project of .NET China Foundation](https://img.shields.io/badge/member_project_of-.NET_CHINA-red.svg?style=flat&colorB=9E20C8)](https://github.com/dotnetcore) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dotnetcore/CAP/master/LICENSE.txt) -CAP 是一个在分布式系统(SOA、MicroService)中实现最终一致性的库,它具有轻量级、易使用、高性能等特点。 +CAP 是一个基于 .NET Standard 的 C# 库,它是一种处理分布式事务的解决方案,同样具有 EventBus 的功能,它具有轻量级、易使用、高性能等特点。 你可以在这里[CAP Wiki](https://github.com/dotnetcore/CAP/wiki)看到更多详细资料。 ## 预览(OverView) -CAP 是在一个 ASP.NET Core 项目中使用的库,当然他可以用于 ASP.NET Core On .NET Framework 中。 +在我们构建 SOA 或者 微服务系统的过程中,我们通常需要使用事件来对各个服务进行集成,在这过程中简单的使用消息队列并不能保证数据的最终一致性, +CAP 采用的是和当前数据库集成的本地消息表的方案来解决在分布式系统互相调用的各个环节可能出现的异常,它能够保证任何情况下事件消息都是不会丢失的。 -你可以把 CAP 看成是一个 EventBus,因为它具有 EventBus 的所有功能,并且 CAP 提供了更加简化的方式来处理 EventBus 中的发布和订阅。 - -CAP 具有消息持久化的功能,当你的服务进行重启或者宕机时它可以保证消息的可靠性。CAP提供了基于Microsoft DI 的 Publisher Service 服务,它可以和你的业务服务进行无缝结合,并且支持强一致性的事务。 +你同样可以把 CAP 当做 EventBus 来使用,CAP提供了一种更加简单的方式来实现事件消息的发布和订阅,在订阅以及发布的过程中,你不需要继承或实现任何接口。 这是CAP集在ASP.NET Core 微服务架构中的一个示意图: @@ -59,33 +58,33 @@ PM> Install-Package DotNetCore.CAP.PostgreSql 首先配置CAP到 Startup.cs 文件中,如下: -```cs +```c# public void ConfigureServices(IServiceCollection services) { - ...... + ...... - services.AddDbContext(); + services.AddDbContext(); - services.AddCap(x => - { - // 如果你的 SqlServer 使用的 EF 进行数据操作,你需要添加如下配置: - // 注意: 你不需要再次配置 x.UseSqlServer(""") - x.UseEntityFramework(); + services.AddCap(x => + { + // 如果你的 SqlServer 使用的 EF 进行数据操作,你需要添加如下配置: + // 注意: 你不需要再次配置 x.UseSqlServer(""") + x.UseEntityFramework(); - // 如果你使用的Dapper,你需要添加如下配置: - x.UseSqlServer("数据库连接字符串"); + // 如果你使用的Dapper,你需要添加如下配置: + x.UseSqlServer("数据库连接字符串"); - // 如果你使用的 RabbitMQ 作为MQ,你需要添加如下配置: - x.UseRabbitMQ("localhost"); + // 如果你使用的 RabbitMQ 作为MQ,你需要添加如下配置: + x.UseRabbitMQ("localhost"); - //如果你使用的 Kafka 作为MQ,你需要添加如下配置: - x.UseKafka("localhost"); - }); + //如果你使用的 Kafka 作为MQ,你需要添加如下配置: + x.UseKafka("localhost"); + }); } public void Configure(IApplicationBuilder app) { - ..... + ..... app.UseCap(); } @@ -96,37 +95,43 @@ public void Configure(IApplicationBuilder app) 在 Controller 中注入 `ICapPublisher` 然后使用 `ICapPublisher` 进行消息发送 -```cs +```c# public class PublishController : Controller { - private readonly ICapPublisher _publisher; - - public PublishController(ICapPublisher publisher) - { - _publisher = publisher; - } - - - [Route("~/checkAccount")] - public async Task PublishMessage() - { - //指定发送的消息头和内容 - await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }); - - return Ok(); - } - - [Route("~/checkAccountWithTrans")] - public async Task PublishMessageWithTransaction([FromServices]AppDbContext dbContext) - { - using (var trans = dbContext.Database.BeginTransaction()) - { - await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }); - - trans.Commit(); - } - return Ok(); - } + [Route("~/checkAccountWithTrans")] + public async Task PublishMessageWithTransaction([FromServices]AppDbContext dbContext, [FromServices]ICapPublisher publisher) + { + using (var trans = dbContext.Database.BeginTransaction()) + { + // 此处填写你的业务代码 + + //如果你使用的是EF,CAP会自动发现当前环境中的事务,所以你不必显式传递事务参数。 + //由于本地事务, 当前数据库的业务操作和发布事件日志之间将实现原子性。 + await publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }); + + trans.Commit(); + } + return Ok(); + } + + [Route("~/publishWithTransactionUsingAdonet")] + public async Task PublishMessageWithTransactionUsingAdonet([FromServices]ICapPublisher publisher) + { + var connectionString = ""; + using (var sqlConnection = new SqlConnection(connectionString)) + { + sqlConnection.Open(); + using (var sqlTransaction = sqlConnection.BeginTransaction()) + { + // 此处填写你的业务代码,通常情况下,你可以将业务代码使用一个委托传递进来进行封装该区域代码。 + + publisher.Publish("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }, sqlTransaction); + + sqlTransaction.Commit(); + } + } + return Ok(); + } } ``` @@ -137,25 +142,16 @@ public class PublishController : Controller 在 Action 上添加 CapSubscribeAttribute 来订阅相关消息。 -```cs +```c# public class PublishController : Controller { - private readonly ICapPublisher _publisher; - - public PublishController(ICapPublisher publisher) - { - _publisher = publisher; - } - - - [NoAction] - [CapSubscribe("xxx.services.account.check")] - public async Task CheckReceivedMessage(Person person) - { - Console.WriteLine(person.Name); - Console.WriteLine(person.Age); - return Task.CompletedTask; - } + [CapSubscribe("xxx.services.account.check")] + public async Task CheckReceivedMessage(Person person) + { + Console.WriteLine(person.Name); + Console.WriteLine(person.Age); + return Task.CompletedTask; + } } ``` @@ -164,37 +160,72 @@ public class PublishController : Controller 如果你的订阅方法没有位于 Controller 中,则你订阅的类需要继承 `ICapSubscribe`: -```cs +```c# namespace xxx.Service { - public interface ISubscriberService - { - public void CheckReceivedMessage(Person person); - } + public interface ISubscriberService + { + public void CheckReceivedMessage(Person person); + } - public class SubscriberService: ISubscriberService, ICapSubscribe - { - [CapSubscribe("xxx.services.account.check")] - public void CheckReceivedMessage(Person person) - { + public class SubscriberService: ISubscriberService, ICapSubscribe + { + [CapSubscribe("xxx.services.account.check")] + public void CheckReceivedMessage(Person person) + { - } - } + } + } } ``` 然后在 Startup.cs 中的 `ConfigureServices()` 中注入你的 `ISubscriberService` 类 -```cs +```c# public void ConfigureServices(IServiceCollection services) { - services.AddTransient(); + services.AddTransient(); } ``` +### Dashboard + +CAP 2.1+ 以上版本中提供了仪表盘(Dashboard)功能,你可以很方便的查看发出和接收到的消息。除此之外,你还可以在仪表盘中实时查看发送或者接收到的消息。 + +在分布式环境中,仪表盘内置集成了 [Consul](http://consul.io) 作为节点的注册发现,同时实现了网关代理功能,你同样可以方便的查看本节点或者其他节点的数据,它就像你访问本地资源一样。 + +```c# +services.AddCap(x => +{ + //... + + // 注册 Dashboard + x.UseDashboard(); + + // 注册节点到 Consul + x.UseDiscovery(d => + { + d.DiscoveryServerHostName = "localhost"; + d.DiscoveryServerPort = 8500; + d.CurrentNodeHostName = "localhost"; + d.CurrentNodePort = 5800; + d.NodeId = 1; + d.NodeName = "CAP No.1 Node"; + }); +}); +``` + +![dashboard](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220827302-189215107.png) + +![received](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220934115-1107747665.png) + +![subscibers](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220949193-884674167.png) + +![nodes](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004221001880-1162918362.png) + ## 贡献 贡献的最简单的方法之一就是是参与讨论和讨论问题(issue)。你也可以通过提交的 Pull Request 代码变更作出贡献。 diff --git a/appveyor.yml b/appveyor.yml index 4185499..05802e7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ environment: BUILDING_ON_PLATFORM: win BuildEnvironment: appveyor Cap_SqlServer_ConnectionStringTemplate: Server=(local)\SQL2014;Database={0};User ID=sa;Password=Password12! - Cap_MySql_ConnectionStringTemplate: Server=localhost;Database={0};Uid=root;Pwd=Password12! + Cap_MySql_ConnectionStringTemplate: Server=localhost;Database={0};Uid=root;Pwd=Password12!;Allow User Variables=True Cap_PostgreSql_ConnectionStringTemplate: Server=localhost;Database={0};UserId=postgres;Password=Password12! services: - mssql2014 diff --git a/build/version.props b/build/version.props index accac21..7dc91a1 100644 --- a/build/version.props +++ b/build/version.props @@ -1,8 +1,8 @@ 2 - 0 - 2 + 1 + 0 $(VersionMajor).$(VersionMinor).$(VersionPatch) diff --git a/samples/Sample.Kafka.SqlServer/AppDbContext.cs b/samples/Sample.Kafka.SqlServer/AppDbContext.cs new file mode 100644 index 0000000..78d6492 --- /dev/null +++ b/samples/Sample.Kafka.SqlServer/AppDbContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; + +namespace Sample.Kafka.SqlServer +{ + public class AppDbContext : DbContext + { + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + //optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=Sample.Kafka.SqlServer;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); + optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=TestCap;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); + } + } +} diff --git a/samples/Sample.Kafka.SqlServer/CmsContentSerializer.cs b/samples/Sample.Kafka.SqlServer/CmsContentSerializer.cs new file mode 100644 index 0000000..dbbd736 --- /dev/null +++ b/samples/Sample.Kafka.SqlServer/CmsContentSerializer.cs @@ -0,0 +1,50 @@ +using System; +using DotNetCore.CAP.Abstractions; +using DotNetCore.CAP.Models; +using Newtonsoft.Json; + +namespace Sample.RabbitMQ.SqlServer +{ + public class MessageContent : CapMessage + { + [JsonProperty("id")] + public override string Id { get; set; } + + [JsonProperty("createdTime")] + public override DateTime Timestamp { get; set; } + + [JsonProperty("msgBody")] + public override string Content { get; set; } + + [JsonProperty("callbackTopicName")] + public override string CallbackName { get; set; } + } + + public class MyMessagePacker : IMessagePacker + { + private readonly IContentSerializer _serializer; + + public MyMessagePacker(IContentSerializer serializer) + { + _serializer = serializer; + } + + public string Pack(CapMessage obj) + { + var content = new MessageContent + { + Id = obj.Id, + Content = obj.Content, + CallbackName = obj.CallbackName, + Timestamp = obj.Timestamp + }; + return _serializer.Serialize(content); + } + + public CapMessage UnPack(string packingMessage) + { + return _serializer.DeSerialize(packingMessage); + } + } +} + diff --git a/samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs b/samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs new file mode 100644 index 0000000..77a28b3 --- /dev/null +++ b/samples/Sample.Kafka.SqlServer/Controllers/ValuesController.cs @@ -0,0 +1,137 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using DotNetCore.CAP; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + +namespace Sample.Kafka.SqlServer.Controllers +{ + public class Person + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("uname")] + public string Name { get; set; } + + public HAHA Haha { get; set; } + + public override string ToString() + { + return "Name:" + Name + ";Id:" + Id + "Haha:" + Haha?.ToString(); + } + } + + public class HAHA + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("uname")] + public string Name { get; set; } + public override string ToString() + { + return "Name:" + Name + ";Id:" + Id; + } + } + + + [Route("api/[controller]")] + public class ValuesController : Controller, ICapSubscribe + { + private readonly ICapPublisher _capBus; + private readonly AppDbContext _dbContext; + + public ValuesController(ICapPublisher producer, AppDbContext dbContext) + { + _capBus = producer; + _dbContext = dbContext; + } + + + [Route("~/publish")] + public IActionResult PublishMessage() + { + var p = new Person + { + Id = Guid.NewGuid().ToString(), + Name = "杨晓东", + Haha = new HAHA + { + Id = Guid.NewGuid().ToString(), + Name = "1-1杨晓东", + } + }; + + _capBus.Publish("wl.yxd.test", p, "wl.yxd.test.callback"); + + + //_capBus.Publish("wl.cj.test", p); + return Ok(); + } + + [CapSubscribe("wl.yxd.test.callback")] + public void KafkaTestCallback(Person p) + { + Console.WriteLine("回调内容:" + p); + } + + + [CapSubscribe("wl.cj.test")] + public string KafkaTestReceived(Person person) + { + Console.WriteLine(person); + Debug.WriteLine(person); + return "this is callback message"; + } + + [Route("~/publishWithTrans")] + public async Task PublishMessageWithTransaction() + { + using (var trans = await _dbContext.Database.BeginTransactionAsync()) + { + await _capBus.PublishAsync("sample.rabbitmq.mysql", ""); + + trans.Commit(); + } + return Ok(); + } + + [CapSubscribe("sample.rabbitmq.mysql33333", Group = "Test.Group")] + public void KafkaTest22(Person person) + { + var aa = _dbContext.Database; + + _dbContext.Dispose(); + + Console.WriteLine("[sample.kafka.sqlserver] message received " + person.ToString()); + Debug.WriteLine("[sample.kafka.sqlserver] message received " + person.ToString()); + } + + //[CapSubscribe("sample.rabbitmq.mysql22222")] + //public void KafkaTest22(DateTime time) + //{ + // Console.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); + // Debug.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); + //} + + [CapSubscribe("sample.rabbitmq.mysql22222")] + public async Task KafkaTest33(DateTime time) + { + Console.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); + Debug.WriteLine("[sample.kafka.sqlserver] message received " + time.ToString()); + return await Task.FromResult(time); + } + + [NonAction] + [CapSubscribe("sample.kafka.sqlserver3")] + [CapSubscribe("sample.kafka.sqlserver4")] + public void KafkaTest() + { + Console.WriteLine("[sample.kafka.sqlserver] message received"); + Debug.WriteLine("[sample.kafka.sqlserver] message received"); + } + } +} \ No newline at end of file diff --git a/samples/Sample.Kafka.SqlServer/Program.cs b/samples/Sample.Kafka.SqlServer/Program.cs new file mode 100644 index 0000000..c8cd00a --- /dev/null +++ b/samples/Sample.Kafka.SqlServer/Program.cs @@ -0,0 +1,23 @@ +using System.IO; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace Sample.Kafka.SqlServer +{ + public class Program + { + + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + + } +} \ No newline at end of file diff --git a/samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj b/samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj new file mode 100644 index 0000000..a1e44b6 --- /dev/null +++ b/samples/Sample.Kafka.SqlServer/Sample.Kafka.SqlServer.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.0 + Sample.Kafka.SqlServer + NU1701 + NU1701 + + + + + + + + + + + + + + + diff --git a/samples/Sample.Kafka.SqlServer/Startup.cs b/samples/Sample.Kafka.SqlServer/Startup.cs new file mode 100644 index 0000000..bc15d25 --- /dev/null +++ b/samples/Sample.Kafka.SqlServer/Startup.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Sample.RabbitMQ.SqlServer; + +namespace Sample.Kafka.SqlServer +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(); + + services.AddCap(x => + { + x.UseEntityFramework(); + x.UseKafka("192.168.2.215:9092"); + x.UseDashboard(); + //x.UseDiscovery(d => + //{ + // d.DiscoveryServerHostName = "localhost"; + // d.DiscoveryServerPort = 8500; + // d.CurrentNodeHostName = "localhost"; + // d.CurrentNodePort = 5820; + // d.NodeName = "CAP 2号节点"; + //}); + }).AddMessagePacker(); + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + app.UseMvc(); + + app.UseCap(); + } + } +} \ No newline at end of file diff --git a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs index cf3c96d..f803e26 100644 --- a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs @@ -10,8 +10,8 @@ namespace Sample.RabbitMQ.MySql { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - //optionsBuilder.UseMySql("Server=localhost;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;"); - optionsBuilder.UseMySql("Server=192.168.2.206;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;"); + optionsBuilder.UseMySql("Server=localhost;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;Allow User Variables=True"); + //optionsBuilder.UseMySql("Server=192.168.2.206;Database=Sample.RabbitMQ.MySql;UserId=root;Password=123123;"); } } } diff --git a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs index 0138520..0b394cb 100644 --- a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs @@ -24,7 +24,7 @@ namespace Sample.RabbitMQ.MySql.Controllers public IActionResult PublishMessage() { _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now); - + return Ok(); } diff --git a/samples/Sample.RabbitMQ.MySql/Program.cs b/samples/Sample.RabbitMQ.MySql/Program.cs index 0f7c032..94095e2 100644 --- a/samples/Sample.RabbitMQ.MySql/Program.cs +++ b/samples/Sample.RabbitMQ.MySql/Program.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -13,20 +14,12 @@ namespace Sample.RabbitMQ.MySql { public static void Main(string[] args) { - var config = new ConfigurationBuilder() - .AddCommandLine(args) - .AddEnvironmentVariables("ASPNETCORE_") - .Build(); + BuildWebHost(args).Run(); + } - var host = new WebHostBuilder() - .UseConfiguration(config) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) .UseStartup() .Build(); - - host.Run(); - } } } diff --git a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj index 1c67eb0..4d5358c 100644 --- a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj +++ b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj @@ -10,14 +10,8 @@ - - - - - - - - + + diff --git a/samples/Sample.RabbitMQ.MySql/Startup.cs b/samples/Sample.RabbitMQ.MySql/Startup.cs index 19b1eac..12a2986 100644 --- a/samples/Sample.RabbitMQ.MySql/Startup.cs +++ b/samples/Sample.RabbitMQ.MySql/Startup.cs @@ -18,11 +18,8 @@ namespace Sample.RabbitMQ.MySql services.AddCap(x => { x.UseEntityFramework(); - x.UseRabbitMQ(y => { - y.HostName = "192.168.2.206"; - y.UserName = "admin"; - y.Password = "123123"; - }); + x.UseRabbitMQ("localhost"); + x.UseDashboard(); }); services.AddMvc(); diff --git a/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs b/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs index 4fefe5c..d4bde28 100644 --- a/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.PostgreSql/AppDbContext.cs @@ -10,7 +10,7 @@ namespace Sample.RabbitMQ.PostgreSql { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseNpgsql("Server=localhost;Database=Sample.RabbitMQ.PostgreSql;UserId=postgre;Password=123123;"); + optionsBuilder.UseNpgsql("Server=localhost;Database=Sample.RabbitMQ.PostgreSql;UserId=postgres;Password=123123;"); } } } diff --git a/samples/Sample.RabbitMQ.PostgreSql/Startup.cs b/samples/Sample.RabbitMQ.PostgreSql/Startup.cs index ac7ead0..b0477c0 100644 --- a/samples/Sample.RabbitMQ.PostgreSql/Startup.cs +++ b/samples/Sample.RabbitMQ.PostgreSql/Startup.cs @@ -4,10 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Sample.RabbitMQ.PostgreSql { @@ -16,7 +13,21 @@ namespace Sample.RabbitMQ.PostgreSql // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - + services.AddDbContext(); + services.AddCap(x => + { + x.UseEntityFramework(); + x.UseRabbitMQ("localhost"); + x.UseDashboard(); + x.UseDiscovery(d => + { + d.DiscoveryServerHostName = "localhost"; + d.DiscoveryServerPort = 8500; + d.CurrentNodeHostName = "localhost"; + d.CurrentNodePort = 5800; + d.NodeName = "CAP 一号节点"; + }); + }); services.AddMvc(); } @@ -24,6 +35,8 @@ namespace Sample.RabbitMQ.PostgreSql public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); + + app.UseCap(); } } } diff --git a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs index 0607bb0..1ba1fdb 100644 --- a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs @@ -1,13 +1,17 @@ using Microsoft.EntityFrameworkCore; +using Sample.RabbitMQ.SqlServer.Controllers; namespace Sample.RabbitMQ.SqlServer { public class AppDbContext : DbContext { + + public DbSet Persons { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=TestCap;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); - //optionsBuilder.UseSqlServer("Server=DESKTOP-M9R8T31;Initial Catalog=Sample.Kafka.SqlServer;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=True"); + //optionsBuilder.UseSqlServer("Server=192.168.2.206;Initial Catalog=TestCap;User Id=cmswuliu;Password=h7xY81agBn*Veiu3;MultipleActiveResultSets=True"); + optionsBuilder.UseSqlServer("Server=DESKTOP-M9R8T31;Initial Catalog=Sample.Kafka.SqlServer;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=True"); } } } diff --git a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs index 09b1df3..6c0ccef 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs @@ -2,12 +2,14 @@ using System.Diagnostics; using System.Threading.Tasks; using DotNetCore.CAP; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Sample.RabbitMQ.SqlServer.Controllers { public class Person { + public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } @@ -33,12 +35,17 @@ namespace Sample.RabbitMQ.SqlServer.Controllers [Route("~/publish")] public IActionResult PublishMessage() { - using(var trans = _dbContext.Database.BeginTransaction()) - { - //_capBus.Publish("sample.rabbitmq.mysql22222", DateTime.Now); - _capBus.Publish("sample.rabbitmq.mysql33333", new Person { Name = "宜兴", Age = 11 }); - trans.Commit(); - } + + _capBus.Publish("sample.rabbitmq.sqlserver.order.check", DateTime.Now); + + //var person = new Person + //{ + // Name = "杨晓东", + // Age = 11, + // Id = 23 + //}; + //_capBus.Publish("sample.rabbitmq.mysql33333", person); + return Ok(); } @@ -48,13 +55,13 @@ namespace Sample.RabbitMQ.SqlServer.Controllers using (var trans = await _dbContext.Database.BeginTransactionAsync()) { await _capBus.PublishAsync("sample.rabbitmq.mysql", ""); - + trans.Commit(); } return Ok(); } - [CapSubscribe("sample.rabbitmq.mysql33333")] + [CapSubscribe("sample.rabbitmq.mysql33333",Group ="Test.Group")] public void KafkaTest22(Person person) { var aa = _dbContext.Database; diff --git a/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.Designer.cs b/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.Designer.cs new file mode 100644 index 0000000..3d8122b --- /dev/null +++ b/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.Designer.cs @@ -0,0 +1,40 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Sample.RabbitMQ.SqlServer; +using System; + +namespace Sample.RabbitMQ.SqlServer.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20170824130007_AddPersons")] + partial class AddPersons + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Sample.RabbitMQ.SqlServer.Controllers.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Age"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Persons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.cs b/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.cs new file mode 100644 index 0000000..2fc048b --- /dev/null +++ b/samples/Sample.RabbitMQ.SqlServer/Migrations/20170824130007_AddPersons.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Sample.RabbitMQ.SqlServer.Migrations +{ + public partial class AddPersons : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Persons", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Age = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Persons", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Persons"); + } + } +} diff --git a/samples/Sample.RabbitMQ.SqlServer/Migrations/AppDbContextModelSnapshot.cs b/samples/Sample.RabbitMQ.SqlServer/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..0a20ac3 --- /dev/null +++ b/samples/Sample.RabbitMQ.SqlServer/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,39 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Sample.RabbitMQ.SqlServer; +using System; + +namespace Sample.RabbitMQ.SqlServer.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Sample.RabbitMQ.SqlServer.Controllers.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Age"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Persons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/Sample.RabbitMQ.SqlServer/Program.cs b/samples/Sample.RabbitMQ.SqlServer/Program.cs index ed4ff09..ebbd50e 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Program.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Program.cs @@ -8,28 +8,15 @@ namespace Sample.RabbitMQ.SqlServer { public class Program { - - //var config = new ConfigurationBuilder() - // .AddCommandLine(args) - // .AddEnvironmentVariables("ASPNETCORE_") - // .Build(); - - //var host = new WebHostBuilder() - // .UseConfiguration(config) - // .UseKestrel() - // .UseContentRoot(Directory.GetCurrentDirectory()) - // .UseIISIntegration() - // .UseStartup() - // .Build(); - - //host.Run(); + public static void Main(string[] args) - { + { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) + .UseUrls("http://*:5800") .UseStartup() .Build(); diff --git a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj index 2f9fdaf..e5c69ab 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj +++ b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj @@ -6,14 +6,7 @@ - - - - - - - - + diff --git a/samples/Sample.RabbitMQ.SqlServer/Services/ICmsService.cs b/samples/Sample.RabbitMQ.SqlServer/Services/ICmsService.cs new file mode 100644 index 0000000..5119cce --- /dev/null +++ b/samples/Sample.RabbitMQ.SqlServer/Services/ICmsService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotNetCore.CAP; + +namespace Sample.RabbitMQ.SqlServer.Services +{ + public interface ICmsService + { + void Add(); + } +} diff --git a/samples/Sample.RabbitMQ.SqlServer/Services/IOrderService.cs b/samples/Sample.RabbitMQ.SqlServer/Services/IOrderService.cs new file mode 100644 index 0000000..9f8e3ad --- /dev/null +++ b/samples/Sample.RabbitMQ.SqlServer/Services/IOrderService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sample.RabbitMQ.SqlServer.Services +{ + public interface IOrderService + { + void Check(); + } +} diff --git a/samples/Sample.RabbitMQ.SqlServer/Services/Impl/CmsService.cs b/samples/Sample.RabbitMQ.SqlServer/Services/Impl/CmsService.cs new file mode 100644 index 0000000..b8c7800 --- /dev/null +++ b/samples/Sample.RabbitMQ.SqlServer/Services/Impl/CmsService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotNetCore.CAP; + +namespace Sample.RabbitMQ.SqlServer.Services.Impl +{ + public class CmsService : ICmsService, ICapSubscribe + { + public void Add() + { + throw new NotImplementedException(); + } + } +} diff --git a/samples/Sample.RabbitMQ.SqlServer/Services/Impl/OrderService.cs b/samples/Sample.RabbitMQ.SqlServer/Services/Impl/OrderService.cs new file mode 100644 index 0000000..81d0eb7 --- /dev/null +++ b/samples/Sample.RabbitMQ.SqlServer/Services/Impl/OrderService.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotNetCore.CAP; + +namespace Sample.RabbitMQ.SqlServer.Services.Impl +{ + public class OrderService : IOrderService, ICapSubscribe + { + [CapSubscribe("sample.rabbitmq.sqlserver.order.check")] + public void Check() + { + Console.WriteLine("out"); + } + } +} diff --git a/samples/Sample.RabbitMQ.SqlServer/Startup.cs b/samples/Sample.RabbitMQ.SqlServer/Startup.cs index e6819ea..4821b97 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Startup.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Startup.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Sample.RabbitMQ.SqlServer.Services; +using Sample.RabbitMQ.SqlServer.Services.Impl; namespace Sample.RabbitMQ.SqlServer { @@ -11,13 +13,23 @@ namespace Sample.RabbitMQ.SqlServer { services.AddDbContext(); + services.AddScoped(); + services.AddTransient(); + services.AddCap(x => { x.UseEntityFramework(); - x.UseRabbitMQ(y=> { - y.HostName = "192.168.2.206"; - y.UserName = "admin"; - y.Password = "123123"; + x.UseRabbitMQ("localhost"); + x.UseDashboard(); + x.UseDiscovery(d => + { + d.DiscoveryServerHostName = "localhost"; + d.DiscoveryServerPort = 8500; + + d.CurrentNodeHostName = "192.168.1.11"; + d.CurrentNodePort = 5800; + d.NodeName = "CAP Node Windows"; + d.NodeId = 1; }); }); diff --git a/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs b/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs index 5c9b6ba..b2e2129 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.KafkaCapOptionsExtension.cs @@ -16,12 +16,16 @@ namespace DotNetCore.CAP public void AddServices(IServiceCollection services) { + services.AddSingleton(); + var kafkaOptions = new KafkaOptions(); _configure?.Invoke(kafkaOptions); services.AddSingleton(kafkaOptions); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs index 0790458..1b1f687 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -10,46 +11,51 @@ namespace DotNetCore.CAP /// public class KafkaOptions { - public KafkaOptions() - { - MainConfig = new Dictionary(); - } - /// /// librdkafka configuration parameters (refer to https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md). /// /// Topic configuration parameters are specified via the "default.topic.config" sub-dictionary config parameter. /// /// - public readonly IDictionary MainConfig; + public readonly ConcurrentDictionary MainConfig; + + private IEnumerable> _kafkaConfig; + + + public KafkaOptions() + { + MainConfig = new ConcurrentDictionary(); + } /// - /// The `bootstrap.servers` item config of . + /// Producer connection pool size, default is 10 + /// + public int ConnectionPoolSize { get; set; } = 10; + + /// + /// The `bootstrap.servers` item config of . /// /// Initial list of brokers as a CSV list of broker host or host:port. /// /// public string Servers { get; set; } - internal IEnumerable> AskafkaConfig() + internal IEnumerable> AsKafkaConfig() { - if (MainConfig.ContainsKey("bootstrap.servers")) + if (_kafkaConfig == null) { - return MainConfig.AsEnumerable(); + if (string.IsNullOrWhiteSpace(Servers)) + throw new ArgumentNullException(nameof(Servers)); + + MainConfig["bootstrap.servers"] = Servers; + MainConfig["queue.buffering.max.ms"] = "10"; + MainConfig["socket.blocking.max.ms"] = "10"; + MainConfig["enable.auto.commit"] = "false"; + MainConfig["log.connection.close"] = "false"; + + _kafkaConfig = MainConfig.AsEnumerable(); } - - if (string.IsNullOrWhiteSpace(Servers)) - { - throw new ArgumentNullException(nameof(Servers)); - } - - MainConfig.Add("bootstrap.servers", Servers); - - MainConfig["queue.buffering.max.ms"] = "10"; - MainConfig["socket.blocking.max.ms"] = "10"; - MainConfig["enable.auto.commit"] = "false"; - - return MainConfig.AsEnumerable(); + return _kafkaConfig; } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs index 5bf11b1..be980d6 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.Options.Extensions.cs @@ -9,18 +9,17 @@ namespace Microsoft.Extensions.DependencyInjection /// /// Configuration to use kafka in CAP. /// + /// CAP configuration options /// Kafka bootstrap server urls. public static CapOptions UseKafka(this CapOptions options, string bootstrapServers) { - return options.UseKafka(opt => - { - opt.Servers = bootstrapServers; - }); + return options.UseKafka(opt => { opt.Servers = bootstrapServers; }); } /// /// Configuration to use kafka in CAP. /// + /// CAP configuration options /// Provides programmatic configuration for the kafka . /// public static CapOptions UseKafka(this CapOptions options, Action configure) diff --git a/src/DotNetCore.CAP.Kafka/ConnectionPool.cs b/src/DotNetCore.CAP.Kafka/ConnectionPool.cs new file mode 100644 index 0000000..3746439 --- /dev/null +++ b/src/DotNetCore.CAP.Kafka/ConnectionPool.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using Confluent.Kafka; + +namespace DotNetCore.CAP.Kafka +{ + public class ConnectionPool : IConnectionPool, IDisposable + { + private readonly Func _activator; + private readonly ConcurrentQueue _pool = new ConcurrentQueue(); + private int _count; + + private int _maxSize; + + public ConnectionPool(KafkaOptions options) + { + _maxSize = options.ConnectionPoolSize; + _activator = CreateActivator(options); + } + + Producer IConnectionPool.Rent() + { + return Rent(); + } + + bool IConnectionPool.Return(Producer connection) + { + return Return(connection); + } + + public void Dispose() + { + _maxSize = 0; + + while (_pool.TryDequeue(out var context)) + context.Dispose(); + } + + private static Func CreateActivator(KafkaOptions options) + { + return () => new Producer(options.AsKafkaConfig()); + } + + public virtual Producer Rent() + { + if (_pool.TryDequeue(out var connection)) + { + Interlocked.Decrement(ref _count); + + Debug.Assert(_count >= 0); + + return connection; + } + + connection = _activator(); + + return connection; + } + + public virtual bool Return(Producer connection) + { + if (Interlocked.Increment(ref _count) <= _maxSize) + { + _pool.Enqueue(connection); + + return true; + } + + Interlocked.Decrement(ref _count); + + Debug.Assert(_maxSize == 0 || _pool.Count <= _maxSize); + + return false; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index 96f0191..724ffe7 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -9,12 +9,12 @@ - NU1605 + NU1605;NU1701 NU1701 - + diff --git a/src/DotNetCore.CAP.Kafka/IConnectionPool.cs b/src/DotNetCore.CAP.Kafka/IConnectionPool.cs new file mode 100644 index 0000000..36d5912 --- /dev/null +++ b/src/DotNetCore.CAP.Kafka/IConnectionPool.cs @@ -0,0 +1,12 @@ + +using Confluent.Kafka; + +namespace DotNetCore.CAP.Kafka +{ + public interface IConnectionPool + { + Producer Rent(); + + bool Return(Producer context); + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs b/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs index 1b65188..2568536 100644 --- a/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs +++ b/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs @@ -13,12 +13,6 @@ namespace DotNetCore.CAP.Kafka private readonly KafkaOptions _kafkaOptions; private Consumer _consumerClient; - public event EventHandler OnMessageReceieved; - - public event EventHandler OnError; - - public IDeserializer 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 StringDeserializer { get; set; } + + public event EventHandler OnMessageReceived; + + public event EventHandler OnError; + public void Subscribe(IEnumerable 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); @@ -47,6 +45,7 @@ namespace DotNetCore.CAP.Kafka cancellationToken.ThrowIfCancellationRequested(); _consumerClient.Poll(timeout); } + // ReSharper disable once FunctionNeverReturns } public void Commit() @@ -54,6 +53,11 @@ namespace DotNetCore.CAP.Kafka _consumerClient.CommitAsync(); } + public void Reject() + { + // Ignore, Kafka will not commit offset when not commit. + } + public void Dispose() { _consumerClient.Dispose(); @@ -63,15 +67,23 @@ namespace DotNetCore.CAP.Kafka private void InitKafkaClient() { - _kafkaOptions.MainConfig.Add("group.id", _groupId); + _kafkaOptions.MainConfig["group.id"] = _groupId; - var config = _kafkaOptions.AskafkaConfig(); + var config = _kafkaOptions.AsKafkaConfig(); _consumerClient = new Consumer(config, null, StringDeserializer); - + _consumerClient.OnConsumeError += ConsumerClient_OnConsumeError; _consumerClient.OnMessage += ConsumerClient_OnMessage; _consumerClient.OnError += ConsumerClient_OnError; } + private void ConsumerClient_OnConsumeError(object sender, Message e) + { + var message = e.Deserialize(null, StringDeserializer); + + OnError?.Invoke(sender, $"An error occurred during consume the message; Topic:'{e.Topic}'," + + $"Message:'{message.Value}', Reason:'{e.Error}'."); + } + private void ConsumerClient_OnMessage(object sender, Message e) { var message = new MessageContext @@ -81,12 +93,12 @@ namespace DotNetCore.CAP.Kafka Content = e.Value }; - OnMessageReceieved?.Invoke(sender, message); + OnMessageReceived?.Invoke(sender, message); } private void ConsumerClient_OnError(object sender, Error e) { - OnError?.Invoke(sender, e.Reason); + OnError?.Invoke(sender, e.ToString()); } #endregion private methods diff --git a/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs b/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs index 8b455d7..6bbda6b 100644 --- a/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs +++ b/src/DotNetCore.CAP.Kafka/PublishQueueExecutor.cs @@ -1,7 +1,6 @@ using System; using System.Text; using System.Threading.Tasks; -using Confluent.Kafka; using DotNetCore.CAP.Processor.States; using Microsoft.Extensions.Logging; @@ -9,51 +8,54 @@ namespace DotNetCore.CAP.Kafka { internal class PublishQueueExecutor : BasePublishQueueExecutor { + private readonly ConnectionPool _connectionPool; private readonly ILogger _logger; - private readonly KafkaOptions _kafkaOptions; public PublishQueueExecutor( CapOptions options, IStateChanger stateChanger, - KafkaOptions kafkaOptions, + ConnectionPool connectionPool, ILogger logger) : base(options, stateChanger, logger) { _logger = logger; - _kafkaOptions = kafkaOptions; + _connectionPool = connectionPool; } - public override Task PublishAsync(string keyName, string content) + public override async Task PublishAsync(string keyName, string content) { + var producer = _connectionPool.Rent(); try { - var config = _kafkaOptions.AskafkaConfig(); var contentBytes = Encoding.UTF8.GetBytes(content); - using (var producer = new Producer(config)) + + var message = await producer.ProduceAsync(keyName, null, contentBytes); + + if (!message.Error.HasError) { - var message = producer.ProduceAsync(keyName, null, contentBytes).Result; - - if (!message.Error.HasError) - { - _logger.LogDebug($"kafka topic message [{keyName}] has been published."); - - return Task.FromResult(OperateResult.Success); - } - else - { - return Task.FromResult(OperateResult.Failed(new OperateError - { - Code = message.Error.Code.ToString(), - Description = message.Error.Reason - })); - } + _logger.LogDebug($"kafka topic message [{keyName}] has been published."); + + return OperateResult.Success; } + return OperateResult.Failed(new OperateError + { + 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(ex, + $"An error occurred during sending the topic message to kafka. Topic:[{keyName}], Exception: {ex.Message}"); - return Task.FromResult(OperateResult.Failed(ex)); + return OperateResult.Failed(ex); + } + finally + { + var returned = _connectionPool.Return(producer); + if (!returned) + producer.Dispose(); } } } diff --git a/src/DotNetCore.CAP.MySql/CAP.EFOptions.cs b/src/DotNetCore.CAP.MySql/CAP.EFOptions.cs index d7a8ffa..3cd77a3 100644 --- a/src/DotNetCore.CAP.MySql/CAP.EFOptions.cs +++ b/src/DotNetCore.CAP.MySql/CAP.EFOptions.cs @@ -6,7 +6,7 @@ namespace DotNetCore.CAP public class EFOptions { /// - /// EF dbcontext type. + /// EF db context type. /// internal Type DbContextType { get; set; } } diff --git a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs index ffe2cab..3d29fa1 100644 --- a/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.MySql/CAP.MySqlCapOptionsExtension.cs @@ -18,32 +18,29 @@ namespace DotNetCore.CAP public void AddServices(IServiceCollection services) { + services.AddSingleton(); services.AddSingleton(); - services.AddScoped(); + services.AddSingleton(); services.AddScoped(); - services.AddTransient(); + services.AddScoped(); services.AddTransient(); var mysqlOptions = new MySqlOptions(); _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); - } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs b/src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs index 7ad3801..5bec849 100644 --- a/src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs +++ b/src/DotNetCore.CAP.MySql/CAP.MySqlOptions.cs @@ -1,4 +1,5 @@ // ReSharper disable once CheckNamespace + namespace DotNetCore.CAP { public class MySqlOptions : EFOptions diff --git a/src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs index f838262..5f66f6c 100644 --- a/src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.MySql/CAP.Options.Extensions.cs @@ -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 configure) @@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseEntityFramework(this CapOptions options) where TContext : DbContext { - return options.UseEntityFramework(opt => - { - opt.DbContextType = typeof(TContext); - }); + return options.UseEntityFramework(opt => { opt.DbContextType = typeof(TContext); }); } public static CapOptions UseEntityFramework(this CapOptions options, Action 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)); diff --git a/src/DotNetCore.CAP.MySql/CapPublisher.cs b/src/DotNetCore.CAP.MySql/CapPublisher.cs index 0d6a1b3..276335b 100644 --- a/src/DotNetCore.CAP.MySql/CapPublisher.cs +++ b/src/DotNetCore.CAP.MySql/CapPublisher.cs @@ -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 logger, @@ -25,10 +25,16 @@ namespace DotNetCore.CAP.MySql _options = options; _logger = logger; - if (_options.DbContextType != null) + if (_options.DbContextType == null) return; + IsUsingEF = true; + _dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType); + } + + public async Task PublishAsync(CapPublishedMessage message) + { + using (var conn = new MySqlConnection(_options.ConnectionString)) { - IsUsingEF = true; - _dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); + await conn.ExecuteAsync(PrepareSql(), message); } } @@ -45,36 +51,33 @@ 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.ToString()); + _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 Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, + CapPublishedMessage message) { - await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); + dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); - _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); - } + _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); - } + return Task.CompletedTask; } #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 diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index 00316dd..011148b 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs index 3398dc0..a59d9e2 100644 --- a/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.MySql/IAdditionalProcessor.Default.cs @@ -9,21 +9,16 @@ namespace DotNetCore.CAP.MySql { internal class DefaultAdditionalProcessor : IAdditionalProcessor { - private readonly IServiceProvider _provider; - 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( - IServiceProvider provider, - ILogger logger, + public DefaultAdditionalProcessor(ILogger logger, MySqlOptions mysqlOptions) { _logger = logger; - _provider = provider; _options = mysqlOptions; } @@ -31,20 +26,22 @@ namespace DotNetCore.CAP.MySql { _logger.LogDebug("Collecting expired entities."); - var tables = new string[]{ + var tables = new[] + { $"{_options.TableNamePrefix}.published", $"{_options.TableNamePrefix}.received" }; foreach (var table in tables) { - var removedCount = 0; + int removedCount; do { 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) diff --git a/src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs b/src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs index f9a4ce1..8e4934e 100644 --- a/src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs +++ b/src/DotNetCore.CAP.MySql/MySqlFetchedMessage.cs @@ -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, diff --git a/src/DotNetCore.CAP.MySql/MySqlMonitoringApi.cs b/src/DotNetCore.CAP.MySql/MySqlMonitoringApi.cs new file mode 100644 index 0000000..49fd270 --- /dev/null +++ b/src/DotNetCore.CAP.MySql/MySqlMonitoringApi.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Dapper; +using DotNetCore.CAP.Dashboard; +using DotNetCore.CAP.Dashboard.Monitoring; +using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP.MySql +{ + internal class MySqlMonitoringApi : IMonitoringApi + { + private readonly string _prefix; + private readonly MySqlStorage _storage; + + public MySqlMonitoringApi(IStorage storage, MySqlOptions options) + { + _storage = storage as MySqlStorage ?? throw new ArgumentNullException(nameof(storage)); + _prefix = options?.TableNamePrefix ?? throw new ArgumentNullException(nameof(options)); + } + + public StatisticsDto GetStatistics() + { + var sql = string.Format(@" +set transaction isolation level read committed; +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'; +select count(Id) from `{0}.received` where StatusName = N'Failed'; +select count(Id) from `{0}.published` where StatusName in (N'Processing',N'Scheduled',N'Enqueued'); +select count(Id) from `{0}.received` where StatusName in (N'Processing',N'Scheduled',N'Enqueued');", _prefix); + + var statistics = UseConnection(connection => + { + var stats = new StatisticsDto(); + using (var multi = connection.QueryMultiple(sql)) + { + stats.PublishedSucceeded = multi.ReadSingle(); + stats.ReceivedSucceeded = multi.ReadSingle(); + + stats.PublishedFailed = multi.ReadSingle(); + stats.ReceivedFailed = multi.ReadSingle(); + + stats.PublishedProcessing = multi.ReadSingle(); + stats.ReceivedProcessing = multi.ReadSingle(); + } + return stats; + }); + return statistics; + } + + public IDictionary HourlyFailedJobs(MessageType type) + { + var tableName = type == MessageType.Publish ? "published" : "received"; + return UseConnection(connection => + GetHourlyTimelineStats(connection, tableName, StatusName.Failed)); + } + + public IDictionary HourlySucceededJobs(MessageType type) + { + var tableName = type == MessageType.Publish ? "published" : "received"; + return UseConnection(connection => + GetHourlyTimelineStats(connection, tableName, StatusName.Succeeded)); + } + + public IList Messages(MessageQueryDto queryDto) + { + 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)) + 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 `{_prefix}.{tableName}` where 1=1 {where} order by Added desc limit @Limit offset @Offset"; + + return UseConnection(conn => conn.Query(sqlQuery, new + { + queryDto.StatusName, + queryDto.Group, + queryDto.Name, + queryDto.Content, + Offset = queryDto.CurrentPage * queryDto.PageSize, + Limit = queryDto.PageSize + }).ToList()); + } + + public int PublishedFailedCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Failed)); + } + + public int PublishedProcessingCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Processing)); + } + + public int PublishedSucceededCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Succeeded)); + } + + public int ReceivedFailedCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Failed)); + } + + public int ReceivedProcessingCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Processing)); + } + + public int ReceivedSucceededCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Succeeded)); + } + + private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName) + { + var sqlQuery = statusName == StatusName.Processing + ? $"select count(Id) from `{_prefix}.{tableName}` where StatusName in (N'Processing',N'Scheduled',N'Enqueued')" + : $"select count(Id) from `{_prefix}.{tableName}` where StatusName = @state"; + + var count = connection.ExecuteScalar(sqlQuery, new {state = statusName}); + return count; + } + + private T UseConnection(Func action) + { + return _storage.UseConnection(action); + } + + private Dictionary GetHourlyTimelineStats(IDbConnection connection, string tableName, + string statusName) + { + var endDate = DateTime.Now; + var dates = new List(); + for (var i = 0; i < 24; i++) + { + dates.Add(endDate); + endDate = endDate.AddHours(-1); + } + + var keyMaps = dates.ToDictionary(x => x.ToString("yyyy-MM-dd-HH"), x => x); + + return GetTimelineStats(connection, tableName, statusName, keyMaps); + } + + private Dictionary GetTimelineStats( + IDbConnection connection, + string tableName, + string statusName, + IDictionary keyMaps) + { + var sqlQuery = + $@" +select aggr.* from ( + select date_format(`Added`,'%Y-%m-%d-%H') as `Key`, + count(id) `Count` + from `{_prefix}.{tableName}` + where StatusName = @statusName + group by date_format(`Added`,'%Y-%m-%d-%H') +) aggr where `Key` in @keys;"; + + var valuesMap = connection.Query( + 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(); + for (var i = 0; i < keyMaps.Count; i++) + { + var value = valuesMap[keyMaps.ElementAt(i).Key]; + result.Add(keyMaps.ElementAt(i).Value, value); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/MySqlStorage.cs b/src/DotNetCore.CAP.MySql/MySqlStorage.cs index 137a69f..6e9c56e 100644 --- a/src/DotNetCore.CAP.MySql/MySqlStorage.cs +++ b/src/DotNetCore.CAP.MySql/MySqlStorage.cs @@ -1,6 +1,9 @@ +using System; +using System.Data; using System.Threading; using System.Threading.Tasks; using Dapper; +using DotNetCore.CAP.Dashboard; using Microsoft.Extensions.Logging; using MySql.Data.MySqlClient; @@ -8,15 +11,30 @@ namespace DotNetCore.CAP.MySql { public class MySqlStorage : IStorage { - private readonly MySqlOptions _options; + private readonly IDbConnection _existingConnection = null; private readonly ILogger _logger; + private readonly MySqlOptions _options; + private readonly CapOptions _capOptions; - public MySqlStorage(ILogger logger, MySqlOptions options) + public MySqlStorage(ILogger logger, + MySqlOptions options, + CapOptions capOptions) { _options = options; + _capOptions = capOptions; _logger = logger; } + public IStorageConnection GetConnection() + { + return new MySqlStorageConnection(_options, _capOptions); + } + + public IMonitoringApi GetMonitoringApi() + { + return new MySqlMonitoringApi(this, _options); + } + public async Task InitializeAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return; @@ -32,7 +50,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 @@ -62,5 +80,41 @@ CREATE TABLE IF NOT EXISTS `{prefix}.published` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; return batchSql; } + + internal T UseConnection(Func func) + { + IDbConnection connection = null; + + try + { + connection = CreateAndOpenConnection(); + return func(connection); + } + finally + { + ReleaseConnection(connection); + } + } + + internal IDbConnection CreateAndOpenConnection() + { + var connection = _existingConnection ?? new MySqlConnection(_options.ConnectionString); + + if (connection.State == ConnectionState.Closed) + connection.Open(); + + return connection; + } + + internal bool IsExistingConnection(IDbConnection connection) + { + return connection != null && ReferenceEquals(connection, _existingConnection); + } + + internal void ReleaseConnection(IDbConnection connection) + { + if (connection != null && !IsExistingConnection(connection)) + connection.Dispose(); + } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs b/src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs index 946f697..23d1abd 100644 --- a/src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs +++ b/src/DotNetCore.CAP.MySql/MySqlStorageConnection.cs @@ -11,16 +11,19 @@ namespace DotNetCore.CAP.MySql { public class MySqlStorageConnection : IStorageConnection { - private readonly MySqlOptions _options; + private readonly CapOptions _capOptions; private readonly string _prefix; - public MySqlStorageConnection(MySqlOptions options) + private const string DateTimeMaxValue = "9999-12-31 23:59:59"; + + public MySqlStorageConnection(MySqlOptions options, CapOptions capOptions) { - _options = options; - _prefix = _options.TableNamePrefix; + _capOptions = capOptions; + Options = options; + _prefix = Options.TableNamePrefix; } - public MySqlOptions Options => _options; + public MySqlOptions Options { get; } public IStorageTransaction CreateTransaction() { @@ -31,7 +34,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(sql); } @@ -39,26 +42,23 @@ namespace DotNetCore.CAP.MySql public Task FetchNextMessageAsync() { - //Last execute statement(FOR UPDATE to fix dirty read) : - - //SET TRANSACTION ISOLATION LEVEL READ COMMITTED; - //START TRANSACTION; - //SELECT MessageId,MessageType FROM `{_prefix}.queue` LIMIT 1 FOR UPDATE; - //DELETE FROM `{_prefix}.queue` LIMIT 1; - //COMMIT; - var sql = $@" SELECT `MessageId`,`MessageType` FROM `{_prefix}.queue` LIMIT 1 FOR UPDATE; DELETE FROM `{_prefix}.queue` LIMIT 1;"; + // var sql = $@" + //SELECT @MId:=`MessageId` as MessageId, @MType:=`MessageType` as MessageType FROM `{_prefix}.queue` LIMIT 1; + //DELETE FROM `{_prefix}.queue` where `MessageId` = @MId AND `MessageType`=@MType;"; return FetchNextMessageCoreAsync(sql); } public async Task GetNextPublishedMessageToBeEnqueuedAsync() { - var sql = $"SELECT * FROM `{_prefix}.published` WHERE `StatusName` = '{StatusName.Scheduled}' LIMIT 1;"; + var sql = $@" +UPDATE `{_prefix}.published` SET Id=LAST_INSERT_ID(Id),ExpiresAt='{DateTimeMaxValue}' WHERE ExpiresAt IS NULL AND `StatusName` = '{StatusName.Scheduled}' LIMIT 1; +SELECT * FROM `{_prefix}.published` WHERE Id=LAST_INSERT_ID();"; - using (var connection = new MySqlConnection(_options.ConnectionString)) + using (var connection = new MySqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } @@ -66,16 +66,14 @@ DELETE FROM `{_prefix}.queue` LIMIT 1;"; public async Task> GetFailedPublishedMessages() { - var sql = $"SELECT * FROM `{_prefix}.published` WHERE `StatusName` = '{StatusName.Failed}';"; + var sql = $"SELECT * FROM `{_prefix}.published` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `StatusName` = '{StatusName.Failed}' LIMIT 200;"; - using (var connection = new MySqlConnection(_options.ConnectionString)) + using (var connection = new MySqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); } } - // CapReceviedMessage - public async Task StoreReceivedMessageAsync(CapReceivedMessage message) { if (message == null) throw new ArgumentNullException(nameof(message)); @@ -84,7 +82,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); } @@ -93,44 +91,82 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; public async Task 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(sql); } } - public async Task GetNextReceviedMessageToBeEnqueuedAsync() + public async Task GetNextReceivedMessageToBeEnqueuedAsync() { - var sql = $"SELECT * FROM `{_prefix}.received` WHERE `StatusName` = '{StatusName.Scheduled}' LIMIT 1;"; - using (var connection = new MySqlConnection(_options.ConnectionString)) + var sql = $@" +UPDATE `{_prefix}.received` SET Id=LAST_INSERT_ID(Id),ExpiresAt='{DateTimeMaxValue}' WHERE ExpiresAt IS NULL AND `StatusName` = '{StatusName.Scheduled}' LIMIT 1; +SELECT * FROM `{_prefix}.received` WHERE Id=LAST_INSERT_ID();"; + + using (var connection = new MySqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } } - public async Task> GetFailedReceviedMessages() + public async Task> GetFailedReceivedMessages() { - var sql = $"SELECT * FROM `{_prefix}.received` WHERE `StatusName` = '{StatusName.Failed}';"; - using (var connection = new MySqlConnection(_options.ConnectionString)) + var sql = $"SELECT * FROM `{_prefix}.received` WHERE `Retries`<{_capOptions.FailedRetryCount} AND `StatusName` = '{StatusName.Failed}' LIMIT 200;"; + using (var connection = new MySqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); } } + public void Dispose() { } + 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 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 = null; try { - fetchedMessage = await connection.QueryFirstOrDefaultAsync(sql, args, transaction); + //fetchedMessage = await connection.QuerySingleOrDefaultAsync(sql, args, transaction); + // An anomaly with unknown causes, sometimes QuerySingleOrDefaultAsync can't return expected result. + using (var reader = connection.ExecuteReader(sql, args, transaction)) + { + while (reader.Read()) + { + fetchedMessage = new FetchedMessage + { + MessageId = (int)reader.GetInt64(0), + MessageType = (MessageType)reader.GetInt64(1) + }; + } + } } catch (MySqlException) { @@ -146,7 +182,8 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; return null; } - return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction); + return new MySqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, + transaction); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs b/src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs index d93be89..dd8472e 100644 --- a/src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs +++ b/src/DotNetCore.CAP.MySql/MySqlStorageTransaction.cs @@ -7,12 +7,12 @@ using MySql.Data.MySqlClient; namespace DotNetCore.CAP.MySql { - public class MySqlStorageTransaction : IStorageTransaction, IDisposable + 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,`Content`= @Content,`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,`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() diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs b/src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs index 12fc9bf..294ed6a 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.EFOptions.cs @@ -9,7 +9,7 @@ namespace DotNetCore.CAP /// /// Gets or sets the schema to use when creating database objects. - /// Default is . + /// Default is . /// public string Schema { get; set; } = DefaultSchema; diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs index 1505f02..9fde846 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.Options.Extensions.cs @@ -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 configure) @@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseEntityFramework(this CapOptions options) where TContext : DbContext { - return options.UseEntityFramework(opt => - { - opt.DbContextType = typeof(TContext); - }); + return options.UseEntityFramework(opt => { opt.DbContextType = typeof(TContext); }); } public static CapOptions UseEntityFramework(this CapOptions options, Action 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)); diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs index 29e5311..e2d4a14 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs @@ -18,17 +18,17 @@ namespace DotNetCore.CAP public void AddServices(IServiceCollection services) { + services.AddSingleton(); services.AddSingleton(); - services.AddScoped(); + services.AddSingleton(); services.AddScoped(); - services.AddTransient(); + services.AddScoped(); services.AddTransient(); var postgreSqlOptions = new PostgreSqlOptions(); _configure(postgreSqlOptions); if (postgreSqlOptions.DbContextType != null) - { services.AddSingleton(x => { using (var scope = x.CreateScope()) @@ -39,11 +39,8 @@ namespace DotNetCore.CAP return postgreSqlOptions; } }); - } else - { services.AddSingleton(postgreSqlOptions); - } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs index 9a95464..7368b94 100644 --- a/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs +++ b/src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs @@ -1,4 +1,5 @@ // ReSharper disable once CheckNamespace + namespace DotNetCore.CAP { public class PostgreSqlOptions : EFOptions diff --git a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs index 0f4e6c9..2c62532 100644 --- a/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs +++ b/src/DotNetCore.CAP.PostgreSql/CapPublisher.cs @@ -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 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,33 @@ 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.ToString()); + _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 Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, + CapPublishedMessage message) { - await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); + dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); - _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); - } + _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); - } + return Task.CompletedTask; } #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 diff --git a/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs index 1c79f60..256e14a 100644 --- a/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.PostgreSql/IAdditionalProcessor.Default.cs @@ -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 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 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) diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs index 7189595..943db78 100644 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlFetchedMessage.cs @@ -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, diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs new file mode 100644 index 0000000..df6020a --- /dev/null +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Dapper; +using DotNetCore.CAP.Dashboard; +using DotNetCore.CAP.Dashboard.Monitoring; +using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP.PostgreSql +{ + public class PostgreSqlMonitoringApi : IMonitoringApi + { + private readonly PostgreSqlOptions _options; + private readonly PostgreSqlStorage _storage; + + public PostgreSqlMonitoringApi(IStorage storage, PostgreSqlOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _storage = storage as PostgreSqlStorage ?? throw new ArgumentNullException(nameof(storage)); + } + + public StatisticsDto GetStatistics() + { + 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'; +select count(""Id"") from ""{0}"".""received"" where ""StatusName"" = N'Failed'; +select count(""Id"") from ""{0}"".""published"" where ""StatusName"" in (N'Processing',N'Scheduled',N'Enqueued'); +select count(""Id"") from ""{0}"".""received"" where ""StatusName"" in (N'Processing',N'Scheduled',N'Enqueued');", + _options.Schema); + + var statistics = UseConnection(connection => + { + var stats = new StatisticsDto(); + using (var multi = connection.QueryMultiple(sql)) + { + stats.PublishedSucceeded = multi.ReadSingle(); + stats.ReceivedSucceeded = multi.ReadSingle(); + + stats.PublishedFailed = multi.ReadSingle(); + stats.ReceivedFailed = multi.ReadSingle(); + + stats.PublishedProcessing = multi.ReadSingle(); + stats.ReceivedProcessing = multi.ReadSingle(); + } + return stats; + }); + return statistics; + } + + public IList Messages(MessageQueryDto queryDto) + { + 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)) + where += " and \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')"; + else + where += " and Lower(\"StatusName\") = Lower(@StatusName)"; + if (!string.IsNullOrEmpty(queryDto.Name)) + where += " and Lower(\"Name\") = Lower(@Name)"; + if (!string.IsNullOrEmpty(queryDto.Group)) + where += " and Lower(\"Group\") = Lower(@Group)"; + if (!string.IsNullOrEmpty(queryDto.Content)) + where += " and \"Content\" ILike '%@Content%'"; + + var sqlQuery = + $"select * from \"{_options.Schema}\".\"{tableName}\" where 1=1 {where} order by \"Added\" desc offset @Offset limit @Limit"; + + return UseConnection(conn => conn.Query(sqlQuery, new + { + queryDto.StatusName, + queryDto.Group, + queryDto.Name, + queryDto.Content, + Offset = queryDto.CurrentPage * queryDto.PageSize, + Limit = queryDto.PageSize + }).ToList()); + } + + public int PublishedFailedCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Failed)); + } + + public int PublishedProcessingCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Processing)); + } + + public int PublishedSucceededCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "published", StatusName.Succeeded)); + } + + public int ReceivedFailedCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Failed)); + } + + public int ReceivedProcessingCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Processing)); + } + + public int ReceivedSucceededCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "received", StatusName.Succeeded)); + } + + public IDictionary HourlySucceededJobs(MessageType type) + { + var tableName = type == MessageType.Publish ? "published" : "received"; + return UseConnection(connection => + GetHourlyTimelineStats(connection, tableName, StatusName.Succeeded)); + } + + public IDictionary HourlyFailedJobs(MessageType type) + { + var tableName = type == MessageType.Publish ? "published" : "received"; + return UseConnection(connection => + GetHourlyTimelineStats(connection, tableName, StatusName.Failed)); + } + + private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName) + { + var sqlQuery = statusName == StatusName.Processing + ? $"select count(\"Id\") from \"{_options.Schema}\".\"{tableName}\" where \"StatusName\" in (N'Processing',N'Scheduled',N'Enqueued')" + : $"select count(\"Id\") from \"{_options.Schema}\".\"{tableName}\" where Lower(\"StatusName\") = Lower(@state)"; + + var count = connection.ExecuteScalar(sqlQuery, new { state = statusName }); + return count; + } + + private T UseConnection(Func action) + { + return _storage.UseConnection(action); + } + + private Dictionary GetHourlyTimelineStats(IDbConnection connection, string tableName, + string statusName) + { + var endDate = DateTime.Now; + var dates = new List(); + for (var i = 0; i < 24; i++) + { + dates.Add(endDate); + endDate = endDate.AddHours(-1); + } + + var keyMaps = dates.ToDictionary(x => x.ToString("yyyy-MM-dd-HH"), x => x); + + return GetTimelineStats(connection, tableName, statusName, keyMaps); + } + + private Dictionary GetTimelineStats( + IDbConnection connection, + string tableName, + string statusName, + IDictionary keyMaps) + { + var sqlQuery = + $@" +with aggr as ( + select to_char(""Added"",'yyyy-MM-dd-HH') as ""Key"", + count(""Id"") as ""Count"" + from ""{_options.Schema}"".""{tableName}"" + where ""StatusName"" = @statusName + group by to_char(""Added"", 'yyyy-MM-dd-HH') +) +select ""Key"",""Count"" from aggr where ""Key""= Any(@keys);"; + + var valuesMap = connection.Query(sqlQuery,new { keys = keyMaps.Keys.ToList(), statusName }) + .ToList() + .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(); + for (var i = 0; i < keyMaps.Count; i++) + { + var value = valuesMap[keyMaps.ElementAt(i).Key]; + result.Add(keyMaps.ElementAt(i).Value, value); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs index 2704780..e1fd4f0 100644 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs @@ -1,7 +1,9 @@ +using System; +using System.Data; using System.Threading; using System.Threading.Tasks; using Dapper; -using Microsoft.EntityFrameworkCore; +using DotNetCore.CAP.Dashboard; using Microsoft.Extensions.Logging; using Npgsql; @@ -9,13 +11,28 @@ namespace DotNetCore.CAP.PostgreSql { public class PostgreSqlStorage : IStorage { - private readonly PostgreSqlOptions _options; + private readonly IDbConnection _existingConnection = null; private readonly ILogger _logger; + private readonly CapOptions _capOptions; + private readonly PostgreSqlOptions _options; - public PostgreSqlStorage(ILogger logger, PostgreSqlOptions options) + public PostgreSqlStorage(ILogger logger, + CapOptions capOptions, + PostgreSqlOptions options) { _options = options; _logger = logger; + _capOptions = capOptions; + } + + public IStorageConnection GetConnection() + { + return new PostgreSqlStorageConnection(_options, _capOptions); + } + + public IMonitoringApi GetMonitoringApi() + { + return new PostgreSqlMonitoringApi(this, _options); } public async Task InitializeAsync(CancellationToken cancellationToken) @@ -31,6 +48,42 @@ namespace DotNetCore.CAP.PostgreSql _logger.LogDebug("Ensuring all create database tables script are applied."); } + internal T UseConnection(Func func) + { + IDbConnection connection = null; + + try + { + connection = CreateAndOpenConnection(); + return func(connection); + } + finally + { + ReleaseConnection(connection); + } + } + + internal IDbConnection CreateAndOpenConnection() + { + var connection = _existingConnection ?? new NpgsqlConnection(_options.ConnectionString); + + if (connection.State == ConnectionState.Closed) + connection.Open(); + + return connection; + } + + internal bool IsExistingConnection(IDbConnection connection) + { + return connection != null && ReferenceEquals(connection, _existingConnection); + } + + internal void ReleaseConnection(IDbConnection connection) + { + if (connection != null && !IsExistingConnection(connection)) + connection.Dispose(); + } + protected virtual string CreateDbTablesScript(string schema) { var batchSql = $@" diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs index 3ddd3ab..e7be1a3 100644 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs @@ -11,14 +11,15 @@ namespace DotNetCore.CAP.PostgreSql { public class PostgreSqlStorageConnection : IStorageConnection { - private readonly PostgreSqlOptions _options; + private readonly CapOptions _capOptions; - public PostgreSqlStorageConnection(PostgreSqlOptions options) + public PostgreSqlStorageConnection(PostgreSqlOptions options, CapOptions capOptions) { - _options = options; + _capOptions = capOptions; + Options = options; } - public PostgreSqlOptions Options => _options; + public PostgreSqlOptions Options { get; } public IStorageTransaction CreateTransaction() { @@ -27,9 +28,9 @@ namespace DotNetCore.CAP.PostgreSql public async Task GetPublishedMessageAsync(int id) { - var sql = $"SELECT * FROM \"{_options.Schema}\".\"published\" WHERE \"Id\"={id}"; + var sql = $"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"Id\"={id} FOR UPDATE SKIP LOCKED"; - using (var connection = new NpgsqlConnection(_options.ConnectionString)) + using (var connection = new NpgsqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } @@ -37,15 +38,16 @@ namespace DotNetCore.CAP.PostgreSql public Task FetchNextMessageAsync() { - var sql = $@"DELETE FROM ""{_options.Schema}"".""queue"" WHERE ""MessageId"" = (SELECT ""MessageId"" FROM ""{_options.Schema}"".""queue"" FOR UPDATE SKIP LOCKED LIMIT 1) RETURNING *;"; + var sql = $@"DELETE FROM ""{Options.Schema}"".""queue"" WHERE ""MessageId"" = (SELECT ""MessageId"" FROM ""{Options.Schema}"".""queue"" FOR UPDATE SKIP LOCKED LIMIT 1) RETURNING *;"; return FetchNextMessageCoreAsync(sql); } public async Task GetNextPublishedMessageToBeEnqueuedAsync() { - var sql = $"SELECT * FROM \"{_options.Schema}\".\"published\" WHERE \"StatusName\" = '{StatusName.Scheduled}' FOR UPDATE SKIP LOCKED LIMIT 1;"; + var sql = + $"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"StatusName\" = '{StatusName.Scheduled}' FOR UPDATE SKIP LOCKED LIMIT 1;"; - using (var connection = new NpgsqlConnection(_options.ConnectionString)) + using (var connection = new NpgsqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } @@ -53,23 +55,23 @@ namespace DotNetCore.CAP.PostgreSql public async Task> GetFailedPublishedMessages() { - var sql = $"SELECT * FROM \"{_options.Schema}\".\"published\" WHERE \"StatusName\"='{StatusName.Failed}' LIMIT 1000;"; + var sql = + $"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"Retries\"<{_capOptions.FailedRetryCount} AND \"StatusName\"='{StatusName.Failed}' LIMIT 200;"; - using (var connection = new NpgsqlConnection(_options.ConnectionString)) + using (var connection = new NpgsqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); } } - // CapReceviedMessage - public async Task StoreReceivedMessageAsync(CapReceivedMessage message) { if (message == null) throw new ArgumentNullException(nameof(message)); - var sql = $"INSERT INTO \"{_options.Schema}\".\"received\"(\"Name\",\"Group\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; + var sql = + $"INSERT INTO \"{Options.Schema}\".\"received\"(\"Name\",\"Group\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; - using (var connection = new NpgsqlConnection(_options.ConnectionString)) + using (var connection = new NpgsqlConnection(Options.ConnectionString)) { await connection.ExecuteAsync(sql, message); } @@ -77,26 +79,28 @@ namespace DotNetCore.CAP.PostgreSql public async Task GetReceivedMessageAsync(int id) { - var sql = $"SELECT * FROM \"{_options.Schema}\".\"received\" WHERE \"Id\"={id}"; - using (var connection = new NpgsqlConnection(_options.ConnectionString)) + var sql = $"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"Id\"={id} FOR UPDATE SKIP LOCKED"; + using (var connection = new NpgsqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } } - public async Task GetNextReceviedMessageToBeEnqueuedAsync() + public async Task GetNextReceivedMessageToBeEnqueuedAsync() { - var sql = $"SELECT * FROM \"{_options.Schema}\".\"received\" WHERE \"StatusName\" = '{StatusName.Scheduled}' FOR UPDATE SKIP LOCKED LIMIT 1;"; - using (var connection = new NpgsqlConnection(_options.ConnectionString)) + var sql = + $"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"StatusName\" = '{StatusName.Scheduled}' FOR UPDATE SKIP LOCKED LIMIT 1;"; + using (var connection = new NpgsqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } } - public async Task> GetFailedReceviedMessages() + public async Task> GetFailedReceivedMessages() { - var sql = $"SELECT * FROM \"{_options.Schema}\".\"received\" WHERE \"StatusName\"='{StatusName.Failed}' LIMIT 1000;"; - using (var connection = new NpgsqlConnection(_options.ConnectionString)) + var sql = + $"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"Retries\"<{_capOptions.FailedRetryCount} AND \"StatusName\"='{StatusName.Failed}' LIMIT 200;"; + using (var connection = new NpgsqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); } @@ -106,13 +110,35 @@ namespace DotNetCore.CAP.PostgreSql { } + public bool ChangePublishedState(int messageId, string state) + { + var sql = + $"UPDATE \"{Options.Schema}\".\"published\" SET \"Retries\"=\"Retries\"+1,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}"; + + using (var connection = new NpgsqlConnection(Options.ConnectionString)) + { + return connection.Execute(sql) > 0; + } + } + + public bool ChangeReceivedState(int messageId, string state) + { + var sql = + $"UPDATE \"{Options.Schema}\".\"received\" SET \"Retries\"=\"Retries\"+1,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}"; + + using (var connection = new NpgsqlConnection(Options.ConnectionString)) + { + return connection.Execute(sql) > 0; + } + } + private async Task FetchNextMessageCoreAsync(string sql, object args = null) { //here don't use `using` to dispose - var connection = new NpgsqlConnection(_options.ConnectionString); + var connection = new NpgsqlConnection(Options.ConnectionString); await connection.OpenAsync(); var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); - FetchedMessage fetchedMessage = null; + FetchedMessage fetchedMessage; try { fetchedMessage = await connection.QueryFirstOrDefaultAsync(sql, args, transaction); @@ -131,7 +157,8 @@ namespace DotNetCore.CAP.PostgreSql return null; } - return new PostgreSqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction); + return new PostgreSqlFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, + transaction); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs index ac106d4..3cb4931 100644 --- a/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs +++ b/src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs @@ -7,12 +7,12 @@ using Npgsql; namespace DotNetCore.CAP.PostgreSql { - public class PostgreSqlStorageTransaction : IStorageTransaction, IDisposable + 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,""Content""= @Content,""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,""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() diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs index 4d59112..bdf0487 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.Options.Extensions.cs @@ -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 configure) diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs index b1061d8..149c1f3 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbiMQOptions.cs @@ -1,6 +1,5 @@ -using System; +// ReSharper disable once CheckNamespace -// ReSharper disable once CheckNamespace namespace DotNetCore.CAP { public class RabbitMQOptions diff --git a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs index abc6d2b..c2eb13e 100644 --- a/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.RabbitMQ/CAP.RabbitMQCapOptionsExtension.cs @@ -16,15 +16,16 @@ namespace DotNetCore.CAP public void AddServices(IServiceCollection services) { + services.AddSingleton(); + var options = new RabbitMQOptions(); _configure?.Invoke(options); services.AddSingleton(options); services.AddSingleton(); - - services.AddSingleton(); - + services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/ConnectionChannelPool.cs b/src/DotNetCore.CAP.RabbitMQ/ConnectionChannelPool.cs new file mode 100644 index 0000000..3ddedb1 --- /dev/null +++ b/src/DotNetCore.CAP.RabbitMQ/ConnectionChannelPool.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using Microsoft.Extensions.Logging; +using RabbitMQ.Client; + +namespace DotNetCore.CAP.RabbitMQ +{ + public class ConnectionChannelPool : IConnectionChannelPool, IDisposable + { + private const int DefaultPoolSize = 15; + private readonly Func _connectionActivator; + private readonly ILogger _logger; + private readonly ConcurrentQueue _pool = new ConcurrentQueue(); + private IConnection _connection; + + private int _count; + private int _maxSize; + + public ConnectionChannelPool(ILogger logger, + RabbitMQOptions options) + { + _logger = logger; + _maxSize = DefaultPoolSize; + + _connectionActivator = CreateConnection(options); + } + + IModel IConnectionChannelPool.Rent() + { + return Rent(); + } + + bool IConnectionChannelPool.Return(IModel connection) + { + return Return(connection); + } + + public IConnection GetConnection() + { + if (_connection != null && _connection.IsOpen) + return _connection; + _connection = _connectionActivator(); + _connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown; + return _connection; + } + + public void Dispose() + { + _maxSize = 0; + + while (_pool.TryDequeue(out var context)) + context.Dispose(); + } + + private static Func CreateConnection(RabbitMQOptions options) + { + var factory = new ConnectionFactory + { + HostName = options.HostName, + UserName = options.UserName, + Port = options.Port, + Password = options.Password, + VirtualHost = options.VirtualHost, + RequestedConnectionTimeout = options.RequestedConnectionTimeout, + SocketReadTimeout = options.SocketReadTimeout, + SocketWriteTimeout = options.SocketWriteTimeout + }; + + return () => factory.CreateConnection(); + } + + private void RabbitMQ_ConnectionShutdown(object sender, ShutdownEventArgs e) + { + _logger.LogWarning($"RabbitMQ client connection closed! {e}"); + } + + public virtual IModel Rent() + { + if (_pool.TryDequeue(out var model)) + { + Interlocked.Decrement(ref _count); + + Debug.Assert(_count >= 0); + + return model; + } + + model = GetConnection().CreateModel(); + + return model; + } + + public virtual bool Return(IModel connection) + { + if (Interlocked.Increment(ref _count) <= _maxSize) + { + _pool.Enqueue(connection); + + return true; + } + + Interlocked.Decrement(ref _count); + + Debug.Assert(_maxSize == 0 || _pool.Count <= _maxSize); + + return false; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs b/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs deleted file mode 100644 index 5db81e7..0000000 --- a/src/DotNetCore.CAP.RabbitMQ/ConnectionPool.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Threading; -using RabbitMQ.Client; - -namespace DotNetCore.CAP.RabbitMQ -{ - public class ConnectionPool : IConnectionPool, IDisposable - { - private const int DefaultPoolSize = 15; - - private readonly ConcurrentQueue _pool = new ConcurrentQueue(); - - private readonly Func _activator; - - private int _maxSize; - private int _count; - - public ConnectionPool(RabbitMQOptions options) - { - _maxSize = DefaultPoolSize; - - _activator = CreateActivator(options); - } - - private static Func CreateActivator(RabbitMQOptions options) - { - var factory = new ConnectionFactory() - { - HostName = options.HostName, - UserName = options.UserName, - Port = options.Port, - Password = options.Password, - VirtualHost = options.VirtualHost, - RequestedConnectionTimeout = options.RequestedConnectionTimeout, - SocketReadTimeout = options.SocketReadTimeout, - SocketWriteTimeout = options.SocketWriteTimeout - }; - - return () => factory.CreateConnection(); - } - - public virtual IConnection Rent() - { - if (_pool.TryDequeue(out IConnection connection)) - { - Interlocked.Decrement(ref _count); - - Debug.Assert(_count >= 0); - - return connection; - } - - connection = _activator(); - - return connection; - } - - public virtual bool Return(IConnection connection) - { - if (Interlocked.Increment(ref _count) <= _maxSize) - { - _pool.Enqueue(connection); - - return true; - } - - Interlocked.Decrement(ref _count); - - Debug.Assert(_maxSize == 0 || _pool.Count <= _maxSize); - - 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(); - } - } - } -} diff --git a/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.cs b/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.cs new file mode 100644 index 0000000..c39985b --- /dev/null +++ b/src/DotNetCore.CAP.RabbitMQ/IConnectionChannelPool.cs @@ -0,0 +1,13 @@ +using RabbitMQ.Client; + +namespace DotNetCore.CAP.RabbitMQ +{ + public interface IConnectionChannelPool + { + IConnection GetConnection(); + + IModel Rent(); + + bool Return(IModel context); + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs b/src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs deleted file mode 100644 index 9097f28..0000000 --- a/src/DotNetCore.CAP.RabbitMQ/IConnectionPool.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using RabbitMQ.Client; - -namespace DotNetCore.CAP.RabbitMQ -{ - public interface IConnectionPool - { - IConnection Rent(); - - bool Return(IConnection context); - } -} diff --git a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs index 0a47501..3f0045e 100644 --- a/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs +++ b/src/DotNetCore.CAP.RabbitMQ/PublishQueueExecutor.cs @@ -9,49 +9,43 @@ namespace DotNetCore.CAP.RabbitMQ { internal sealed class PublishQueueExecutor : BasePublishQueueExecutor { + private readonly IConnectionChannelPool _connectionChannelPool; private readonly ILogger _logger; - private readonly ConnectionPool _connectionPool; private readonly RabbitMQOptions _rabbitMQOptions; - public PublishQueueExecutor( - CapOptions options, - IStateChanger stateChanger, - ConnectionPool connectionPool, - RabbitMQOptions rabbitMQOptions, - ILogger logger) + public PublishQueueExecutor(ILogger logger, CapOptions options, + RabbitMQOptions rabbitMQOptions, IConnectionChannelPool connectionChannelPool, IStateChanger stateChanger) : base(options, stateChanger, logger) { _logger = logger; - _connectionPool = connectionPool; + _connectionChannelPool = connectionChannelPool; _rabbitMQOptions = rabbitMQOptions; } public override Task PublishAsync(string keyName, string content) { - var connection = _connectionPool.Rent(); - + var channel = _connectionChannelPool.Rent(); try { - using (var channel = connection.CreateModel()) - { - var body = Encoding.UTF8.GetBytes(content); + var body = Encoding.UTF8.GetBytes(content); + + channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, true); + channel.BasicPublish(_rabbitMQOptions.TopicExchangeName, + keyName, + null, + body); - channel.ExchangeDeclare(_rabbitMQOptions.TopicExchangeName, RabbitMQOptions.ExchangeType, durable: true); - channel.BasicPublish(exchange: _rabbitMQOptions.TopicExchangeName, - routingKey: keyName, - basicProperties: null, - body: body); + _logger.LogDebug($"RabbitMQ topic message [{keyName}] has been published."); - _logger.LogDebug($"rabbitmq topic message [{keyName}] has been published."); - } return Task.FromResult(OperateResult.Success); } 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 been 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 @@ -59,7 +53,9 @@ namespace DotNetCore.CAP.RabbitMQ } finally { - _connectionPool.Return(connection); + var returned = _connectionChannelPool.Return(channel); + if (!returned) + channel.Dispose(); } } } diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs index 0172c2b..596d1ec 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClient.cs @@ -10,59 +10,36 @@ namespace DotNetCore.CAP.RabbitMQ { internal sealed class RabbitMQConsumerClient : IConsumerClient { + private readonly IConnectionChannelPool _connectionChannelPool; private readonly string _exchageName; private readonly string _queueName; private readonly RabbitMQOptions _rabbitMQOptions; - private ConnectionPool _connectionPool; private IModel _channel; private ulong _deliveryTag; - public event EventHandler OnMessageReceieved; - - public event EventHandler OnError; - public RabbitMQConsumerClient(string queueName, - ConnectionPool connectionPool, - RabbitMQOptions options) + IConnectionChannelPool connectionChannelPool, + RabbitMQOptions options) { _queueName = queueName; - _connectionPool = connectionPool; + _connectionChannelPool = connectionChannelPool; _rabbitMQOptions = options; _exchageName = options.TopicExchangeName; InitClient(); } - private void InitClient() - { - var connection = _connectionPool.Rent(); - - _channel = connection.CreateModel(); + public event EventHandler OnMessageReceived; - _channel.ExchangeDeclare( - exchange: _exchageName, - type: RabbitMQOptions.ExchangeType, - durable: true); - - var arguments = new Dictionary { { "x-message-ttl", (int)_rabbitMQOptions.QueueMessageExpires } }; - _channel.QueueDeclare(_queueName, - durable: true, - exclusive: false, - autoDelete: false, - arguments: arguments); - - _connectionPool.Return(connection); - } + public event EventHandler OnError; public void Subscribe(IEnumerable 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() @@ -82,11 +57,33 @@ namespace DotNetCore.CAP.RabbitMQ _channel.BasicAck(_deliveryTag, false); } + public void Reject() + { + _channel.BasicReject(_deliveryTag, true); + } + public void Dispose() { _channel.Dispose(); } + private void InitClient() + { + var connection = _connectionChannelPool.GetConnection(); + + _channel = connection.CreateModel(); + + _channel.ExchangeDeclare( + _exchageName, + RabbitMQOptions.ExchangeType, + true); + + var arguments = new Dictionary { + { "x-message-ttl", _rabbitMQOptions.QueueMessageExpires } + }; + _channel.QueueDeclare(_queueName, true, false, false, arguments); + } + private void OnConsumerReceived(object sender, BasicDeliverEventArgs e) { _deliveryTag = e.DeliveryTag; @@ -96,7 +93,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) diff --git a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs index 753fc05..0a03c58 100644 --- a/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs +++ b/src/DotNetCore.CAP.RabbitMQ/RabbitMQConsumerClientFactory.cs @@ -1,23 +1,20 @@ -using Microsoft.Extensions.Options; -using RabbitMQ.Client; - -namespace DotNetCore.CAP.RabbitMQ +namespace DotNetCore.CAP.RabbitMQ { internal sealed class RabbitMQConsumerClientFactory : IConsumerClientFactory { + private readonly IConnectionChannelPool _connectionChannelPool; private readonly RabbitMQOptions _rabbitMQOptions; - private readonly ConnectionPool _connectionPool; - public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, ConnectionPool pool) + public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, IConnectionChannelPool channelPool) { _rabbitMQOptions = rabbitMQOptions; - _connectionPool = pool; + _connectionChannelPool = channelPool; } public IConsumerClient Create(string groupId) { - return new RabbitMQConsumerClient(groupId, _connectionPool, _rabbitMQOptions); + return new RabbitMQConsumerClient(groupId, _connectionChannelPool, _rabbitMQOptions); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs b/src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs index b48dcd2..0c62833 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.EFOptions.cs @@ -9,7 +9,7 @@ namespace DotNetCore.CAP /// /// Gets or sets the schema to use when creating database objects. - /// Default is . + /// Default is . /// public string Schema { get; set; } = DefaultSchema; diff --git a/src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs index d87e260..c977921 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.Options.Extensions.cs @@ -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 configure) @@ -27,10 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection public static CapOptions UseEntityFramework(this CapOptions options) where TContext : DbContext { - return options.UseEntityFramework(opt => - { - opt.DbContextType = typeof(TContext); - }); + return options.UseEntityFramework(opt => { opt.DbContextType = typeof(TContext); }); } public static CapOptions UseEntityFramework(this CapOptions options, Action 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)); diff --git a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs index f928031..ba17ff0 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerCapOptionsExtension.cs @@ -18,10 +18,11 @@ namespace DotNetCore.CAP public void AddServices(IServiceCollection services) { + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); + services.AddScoped(); + services.AddScoped(); services.AddTransient(); AddSqlServerOptions(services); } @@ -33,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); - } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs index a0f3fa2..f2d44a4 100644 --- a/src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs +++ b/src/DotNetCore.CAP.SqlServer/CAP.SqlServerOptions.cs @@ -1,4 +1,5 @@ // ReSharper disable once CheckNamespace + namespace DotNetCore.CAP { public class SqlServerOptions : EFOptions diff --git a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs index 18c17c2..9410a4c 100644 --- a/src/DotNetCore.CAP.SqlServer/CapPublisher.cs +++ b/src/DotNetCore.CAP.SqlServer/CapPublisher.cs @@ -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 logger, @@ -25,10 +25,17 @@ namespace DotNetCore.CAP.SqlServer _logger = logger; _options = options; - if (_options.DbContextType != null) + if (_options.DbContextType == null) return; + + IsUsingEF = true; + _dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); + } + + public async Task PublishAsync(CapPublishedMessage message) + { + using (var conn = new SqlConnection(_options.ConnectionString)) { - IsUsingEF = true; - _dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType); + await conn.ExecuteAsync(PrepareSql(), message); } } @@ -45,36 +52,33 @@ 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.ToString()); + _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 Task ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction, + CapPublishedMessage message) { - await dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); + dbConnection.ExecuteAsync(PrepareSql(), message, dbTransaction); - _logger.LogInformation("Published Message has been persisted in the database. name:" + message.ToString()); - } + _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); - } + return Task.CompletedTask; } #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 diff --git a/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs b/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs index 7c1bac1..e87f87a 100644 --- a/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs +++ b/src/DotNetCore.CAP.SqlServer/IAdditionalProcessor.Default.cs @@ -9,26 +9,22 @@ namespace DotNetCore.CAP.SqlServer { public class DefaultAdditionalProcessor : IAdditionalProcessor { - private readonly IServiceProvider _provider; - 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" }; - public DefaultAdditionalProcessor( - IServiceProvider provider, - ILogger logger, + 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 logger, SqlServerOptions sqlServerOptions) { _logger = logger; - _provider = provider; _options = sqlServerOptions; } @@ -38,7 +34,7 @@ namespace DotNetCore.CAP.SqlServer foreach (var table in Tables) { - var removedCount = 0; + int removedCount; do { using (var connection = new SqlConnection(_options.ConnectionString)) @@ -46,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) diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs b/src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs index 06bc461..e423258 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerFetchedMessage.cs @@ -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, diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs b/src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs new file mode 100644 index 0000000..1d0d1c0 --- /dev/null +++ b/src/DotNetCore.CAP.SqlServer/SqlServerMonitoringApi.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Dapper; +using DotNetCore.CAP.Dashboard; +using DotNetCore.CAP.Dashboard.Monitoring; +using DotNetCore.CAP.Infrastructure; +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP.SqlServer +{ + internal class SqlServerMonitoringApi : IMonitoringApi + { + private readonly SqlServerOptions _options; + private readonly SqlServerStorage _storage; + + public SqlServerMonitoringApi(IStorage storage, SqlServerOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _storage = storage as SqlServerStorage ?? throw new ArgumentNullException(nameof(storage)); + } + + public StatisticsDto GetStatistics() + { + 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'; +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); + + var statistics = UseConnection(connection => + { + var stats = new StatisticsDto(); + using (var multi = connection.QueryMultiple(sql)) + { + stats.PublishedSucceeded = multi.ReadSingle(); + stats.ReceivedSucceeded = multi.ReadSingle(); + + stats.PublishedFailed = multi.ReadSingle(); + stats.ReceivedFailed = multi.ReadSingle(); + + stats.PublishedProcessing = multi.ReadSingle(); + stats.ReceivedProcessing = multi.ReadSingle(); + } + return stats; + }); + return statistics; + } + + public IDictionary HourlyFailedJobs(MessageType type) + { + var tableName = type == MessageType.Publish ? "Published" : "Received"; + return UseConnection(connection => + GetHourlyTimelineStats(connection, tableName, StatusName.Failed)); + } + + public IDictionary HourlySucceededJobs(MessageType type) + { + var tableName = type == MessageType.Publish ? "Published" : "Received"; + return UseConnection(connection => + GetHourlyTimelineStats(connection, tableName, StatusName.Succeeded)); + } + + public IList Messages(MessageQueryDto queryDto) + { + 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)) + 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"; + + return UseConnection(conn => conn.Query(sqlQuery, new + { + queryDto.StatusName, + queryDto.Group, + queryDto.Name, + queryDto.Content, + Offset = queryDto.CurrentPage * queryDto.PageSize, + Limit = queryDto.PageSize + }).ToList()); + } + + public int PublishedFailedCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Failed)); + } + + public int PublishedProcessingCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Processing)); + } + + public int PublishedSucceededCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "Published", StatusName.Succeeded)); + } + + public int ReceivedFailedCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Failed)); + } + + public int ReceivedProcessingCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Processing)); + } + + public int ReceivedSucceededCount() + { + return UseConnection(conn => GetNumberOfMessage(conn, "Received", StatusName.Succeeded)); + } + + private int GetNumberOfMessage(IDbConnection connection, string tableName, string statusName) + { + var sqlQuery = statusName == StatusName.Processing + ? $"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(sqlQuery, new {state = statusName}); + return count; + } + + private T UseConnection(Func action) + { + return _storage.UseConnection(action); + } + + private Dictionary GetHourlyTimelineStats(IDbConnection connection, string tableName, + string statusName) + { + var endDate = DateTime.Now; + var dates = new List(); + for (var i = 0; i < 24; i++) + { + dates.Add(endDate); + endDate = endDate.AddHours(-1); + } + + var keyMaps = dates.ToDictionary(x => x.ToString("yyyy-MM-dd-HH"), x => x); + + return GetTimelineStats(connection, tableName, statusName, keyMaps); + } + + private Dictionary GetTimelineStats( + IDbConnection connection, + string tableName, + string statusName, + IDictionary keyMaps) + { + //SQL Server 2012+ + var sqlQuery = + $@" +with aggr as ( + select FORMAT(Added,'yyyy-MM-dd-HH') as [Key], + count(id) [Count] + from [{_options.Schema}].{tableName} + where StatusName = @statusName + group by FORMAT(Added,'yyyy-MM-dd-HH') +) +select [Key], [Count] from aggr with (nolock) where [Key] in @keys;"; + + var valuesMap = connection.Query( + sqlQuery, + new {keys = keyMaps.Keys, statusName}) + .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(); + for (var i = 0; i < keyMaps.Count; i++) + { + var value = valuesMap[keyMaps.ElementAt(i).Key]; + result.Add(keyMaps.ElementAt(i).Value, value); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs b/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs index 9ef0247..57825b5 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerStorage.cs @@ -1,20 +1,38 @@ +using System; +using System.Data; using System.Data.SqlClient; using System.Threading; using System.Threading.Tasks; using Dapper; +using DotNetCore.CAP.Dashboard; using Microsoft.Extensions.Logging; namespace DotNetCore.CAP.SqlServer { public class SqlServerStorage : IStorage { - private readonly SqlServerOptions _options; + private readonly IDbConnection _existingConnection = null; private readonly ILogger _logger; + private readonly CapOptions _capOptions; + private readonly SqlServerOptions _options; - public SqlServerStorage(ILogger logger, SqlServerOptions options) + public SqlServerStorage(ILogger logger, + CapOptions capOptions, + SqlServerOptions options) { _options = options; _logger = logger; + _capOptions = capOptions; + } + + public IStorageConnection GetConnection() + { + return new SqlServerStorageConnection(_options, _capOptions); + } + + public IMonitoringApi GetMonitoringApi() + { + return new SqlServerMonitoringApi(this, _options); } public async Task InitializeAsync(CancellationToken cancellationToken) @@ -33,7 +51,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}') @@ -83,5 +101,41 @@ CREATE TABLE [{schema}].[Published]( END;"; return batchSql; } + + internal T UseConnection(Func func) + { + IDbConnection connection = null; + + try + { + connection = CreateAndOpenConnection(); + return func(connection); + } + finally + { + ReleaseConnection(connection); + } + } + + internal IDbConnection CreateAndOpenConnection() + { + var connection = _existingConnection ?? new SqlConnection(_options.ConnectionString); + + if (connection.State == ConnectionState.Closed) + connection.Open(); + + return connection; + } + + internal bool IsExistingConnection(IDbConnection connection) + { + return connection != null && ReferenceEquals(connection, _existingConnection); + } + + internal void ReleaseConnection(IDbConnection connection) + { + if (connection != null && !IsExistingConnection(connection)) + connection.Dispose(); + } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs b/src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs index b8fba46..2e66d97 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerStorageConnection.cs @@ -11,14 +11,15 @@ namespace DotNetCore.CAP.SqlServer { public class SqlServerStorageConnection : IStorageConnection { - private readonly SqlServerOptions _options; + private readonly CapOptions _capOptions; - public SqlServerStorageConnection(SqlServerOptions options) + public SqlServerStorageConnection(SqlServerOptions options, CapOptions capOptions) { - _options = options; + _capOptions = capOptions; + Options = options; } - public SqlServerOptions Options => _options; + public SqlServerOptions Options { get; } public IStorageTransaction CreateTransaction() { @@ -27,9 +28,9 @@ namespace DotNetCore.CAP.SqlServer public async Task GetPublishedMessageAsync(int id) { - var sql = $@"SELECT * FROM [{_options.Schema}].[Published] WITH (readpast) WHERE Id={id}"; + var sql = $@"SELECT * FROM [{Options.Schema}].[Published] WITH (readpast) WHERE Id={id}"; - using (var connection = new SqlConnection(_options.ConnectionString)) + using (var connection = new SqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } @@ -39,7 +40,7 @@ namespace DotNetCore.CAP.SqlServer { var sql = $@" DELETE TOP (1) -FROM [{_options.Schema}].[Queue] WITH (readpast, updlock, rowlock) +FROM [{Options.Schema}].[Queue] WITH (readpast, updlock, rowlock) OUTPUT DELETED.MessageId,DELETED.[MessageType];"; return FetchNextMessageCoreAsync(sql); @@ -47,9 +48,10 @@ OUTPUT DELETED.MessageId,DELETED.[MessageType];"; public async Task GetNextPublishedMessageToBeEnqueuedAsync() { - var sql = $"SELECT TOP (1) * FROM [{_options.Schema}].[Published] WITH (readpast) WHERE StatusName = '{StatusName.Scheduled}'"; + var sql = + $"SELECT TOP (1) * FROM [{Options.Schema}].[Published] WITH (readpast) WHERE StatusName = '{StatusName.Scheduled}'"; - using (var connection = new SqlConnection(_options.ConnectionString)) + using (var connection = new SqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } @@ -57,25 +59,37 @@ OUTPUT DELETED.MessageId,DELETED.[MessageType];"; public async Task> GetFailedPublishedMessages() { - var sql = $"SELECT * FROM [{_options.Schema}].[Published] WITH (readpast) WHERE StatusName = '{StatusName.Failed}'"; + var sql = + $"SELECT TOP (200) * FROM [{Options.Schema}].[Published] WITH (readpast) WHERE Retries<{_capOptions.FailedRetryCount} AND StatusName = '{StatusName.Failed}'"; - using (var connection = new SqlConnection(_options.ConnectionString)) + using (var connection = new SqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); } } - // CapReceviedMessage + public bool ChangePublishedState(int messageId, string state) + { + var sql = + $"UPDATE [{Options.Schema}].[Published] SET Retries=Retries+1,StatusName = '{state}' WHERE Id={messageId}"; + + using (var connection = new SqlConnection(Options.ConnectionString)) + { + return connection.Execute(sql) > 0; + } + } + + // CapReceivedMessage public async Task StoreReceivedMessageAsync(CapReceivedMessage message) { if (message == null) throw new ArgumentNullException(nameof(message)); var sql = $@" -INSERT INTO [{_options.Schema}].[Received]([Name],[Group],[Content],[Retries],[Added],[ExpiresAt],[StatusName]) +INSERT INTO [{Options.Schema}].[Received]([Name],[Group],[Content],[Retries],[Added],[ExpiresAt],[StatusName]) VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; - using (var connection = new SqlConnection(_options.ConnectionString)) + using (var connection = new SqlConnection(Options.ConnectionString)) { await connection.ExecuteAsync(sql, message); } @@ -83,31 +97,44 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; public async Task GetReceivedMessageAsync(int id) { - var sql = $@"SELECT * FROM [{_options.Schema}].[Received] WITH (readpast) WHERE Id={id}"; - using (var connection = new SqlConnection(_options.ConnectionString)) + var sql = $@"SELECT * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE Id={id}"; + using (var connection = new SqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } } - public async Task GetNextReceviedMessageToBeEnqueuedAsync() + public async Task GetNextReceivedMessageToBeEnqueuedAsync() { - var sql = $"SELECT TOP (1) * FROM [{_options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Scheduled}'"; - using (var connection = new SqlConnection(_options.ConnectionString)) + var sql = + $"SELECT TOP (1) * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Scheduled}'"; + using (var connection = new SqlConnection(Options.ConnectionString)) { return await connection.QueryFirstOrDefaultAsync(sql); } } - public async Task> GetFailedReceviedMessages() + public async Task> GetFailedReceivedMessages() { - var sql = $"SELECT * FROM [{_options.Schema}].[Received] WITH (readpast) WHERE StatusName = '{StatusName.Failed}'"; - using (var connection = new SqlConnection(_options.ConnectionString)) + var sql = + $"SELECT TOP (200) * FROM [{Options.Schema}].[Received] WITH (readpast) WHERE Retries<{_capOptions.FailedRetryCount} AND StatusName = '{StatusName.Failed}'"; + using (var connection = new SqlConnection(Options.ConnectionString)) { return await connection.QueryAsync(sql); } } + public bool ChangeReceivedState(int messageId, string state) + { + var sql = + $"UPDATE [{Options.Schema}].[Received] SET Retries=Retries+1,StatusName = '{state}' WHERE Id={messageId}"; + + using (var connection = new SqlConnection(Options.ConnectionString)) + { + return connection.Execute(sql) > 0; + } + } + public void Dispose() { } @@ -115,10 +142,10 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; private async Task FetchNextMessageCoreAsync(string sql, object args = null) { //here don't use `using` to dispose - var connection = new SqlConnection(_options.ConnectionString); + var connection = new SqlConnection(Options.ConnectionString); await connection.OpenAsync(); var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); - FetchedMessage fetchedMessage = null; + FetchedMessage fetchedMessage; try { fetchedMessage = await connection.QueryFirstOrDefaultAsync(sql, args, transaction); @@ -137,7 +164,8 @@ VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);"; return null; } - return new SqlServerFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, transaction); + return new SqlServerFetchedMessage(fetchedMessage.MessageId, fetchedMessage.MessageType, connection, + transaction); } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs b/src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs index b30c616..3cf8ae6 100644 --- a/src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs +++ b/src/DotNetCore.CAP.SqlServer/SqlServerStorageTransaction.cs @@ -7,12 +7,12 @@ using DotNetCore.CAP.Models; namespace DotNetCore.CAP.SqlServer { - public class SqlServerStorageTransaction : IStorageTransaction, IDisposable + 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,[Content] = @Content,[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,[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() diff --git a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs index 2eeddc1..f821824 100644 --- a/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs +++ b/src/DotNetCore.CAP/Abstractions/CapPublisherBase.cs @@ -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; } @@ -36,22 +36,20 @@ namespace DotNetCore.CAP.Abstractions return PublishWithTransAsync(name, content); } - public void Publish(string name, T contentObj, IDbConnection dbConnection, - string callbackName = null, IDbTransaction dbTransaction = null) + public void Publish(string name, T contentObj, IDbTransaction dbTransaction, string callbackName = null) { CheckIsAdoNet(name); - PrepareConnectionForAdo(dbConnection, dbTransaction); + PrepareConnectionForAdo(dbTransaction); var content = Serialize(contentObj, callbackName); PublishWithTrans(name, content); } - public Task PublishAsync(string name, T contentObj, IDbConnection dbConnection, - string callbackName = null, IDbTransaction dbTransaction = null) + public Task PublishAsync(string name, T contentObj, IDbTransaction dbTransaction, string callbackName = null) { CheckIsAdoNet(name); - PrepareConnectionForAdo(dbConnection, dbTransaction); + PrepareConnectionForAdo(dbTransaction); var content = Serialize(contentObj, callbackName); @@ -60,54 +58,72 @@ 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); - #region private methods - - private string Serialize(T obj, string callbackName = null) + protected virtual string Serialize(T obj, string callbackName = null) { - var message = new Message(obj) + var packer = (IMessagePacker)ServiceProvider.GetService(typeof(IMessagePacker)); + string content; + if (obj != null) + { + if (Helper.IsComplexType(obj.GetType())) + { + var serializer = (IContentSerializer)ServiceProvider.GetService(typeof(IContentSerializer)); + content = serializer.Serialize(obj); + } + else + { + content = obj.ToString(); + } + } + else + { + content = string.Empty; + } + + var message = new CapMessageDto(content) { CallbackName = callbackName }; - return Helper.ToJson(message); + return packer.Pack(message); } - private void PrepareConnectionForAdo(IDbConnection dbConnection, IDbTransaction dbTransaction) + #region private methods + + private void PrepareConnectionForAdo(IDbTransaction dbTransaction) { - DbConnection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection)); + DbTransaction = dbTransaction ?? throw new ArgumentNullException(nameof(dbTransaction)); + DbConnection = DbTransaction.Connection; if (DbConnection.State != ConnectionState.Open) { IsCapOpenedConn = true; DbConnection.Open(); } - DbTranasaction = dbTransaction; - if (DbTranasaction == null) - { - IsCapOpenedTrans = true; - DbTranasaction = dbConnection.BeginTransaction(IsolationLevel.ReadCommitted); - } } private void CheckIsUsingEF(string name) { 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) + private Task PublishWithTransAsync(string name, string content) { var message = new CapPublishedMessage { @@ -116,11 +132,13 @@ namespace DotNetCore.CAP.Abstractions StatusName = StatusName.Scheduled }; - await ExecuteAsync(DbConnection, DbTranasaction, message); + ExecuteAsync(DbConnection, DbTransaction, message); ClosedCap(); PublishQueuer.PulseEvent.Set(); + + return Task.CompletedTask; } private void PublishWithTrans(string name, string content) @@ -132,7 +150,7 @@ namespace DotNetCore.CAP.Abstractions StatusName = StatusName.Scheduled }; - Execute(DbConnection, DbTranasaction, message); + Execute(DbConnection, DbTransaction, message); ClosedCap(); @@ -143,18 +161,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(); } diff --git a/src/DotNetCore.CAP/Abstractions/ConsumerInvokerContext.cs b/src/DotNetCore.CAP/Abstractions/ConsumerInvokerContext.cs deleted file mode 100644 index f9cb369..0000000 --- a/src/DotNetCore.CAP/Abstractions/ConsumerInvokerContext.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace DotNetCore.CAP.Abstractions -{ - /// - /// a context of consumer invoker. - /// - public class ConsumerInvokerContext - { - public ConsumerInvokerContext(ConsumerContext consumerContext) - { - ConsumerContext = consumerContext ?? - throw new ArgumentNullException(nameof(consumerContext)); - } - - public ConsumerContext ConsumerContext { get; set; } - - public IConsumerInvoker Result { get; set; } - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Abstractions/IConsumerInvoker.cs b/src/DotNetCore.CAP/Abstractions/IConsumerInvoker.cs deleted file mode 100644 index 00f37d6..0000000 --- a/src/DotNetCore.CAP/Abstractions/IConsumerInvoker.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Threading.Tasks; - -namespace DotNetCore.CAP.Abstractions -{ - /// - /// Perform user definition method of consumers. - /// - public interface IConsumerInvoker - { - /// - /// begin to invoke method. - /// - Task InvokeAsync(); - } -} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Abstractions/IContentSerializer.cs b/src/DotNetCore.CAP/Abstractions/IContentSerializer.cs new file mode 100644 index 0000000..402843a --- /dev/null +++ b/src/DotNetCore.CAP/Abstractions/IContentSerializer.cs @@ -0,0 +1,60 @@ +using System; +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP.Abstractions +{ + /// + /// Message content serializer. + /// By default, CAP will use Json as a serializer, and you can customize this interface to achieve serialization of other methods. + /// + public interface IContentSerializer + { + /// + /// Serializes the specified object to a string. + /// + /// The type of the value being serialized. + /// The object to serialize. + /// A string representation of the object. + string Serialize(T value); + + /// + /// Deserializes the string to the specified .NET type. + /// + /// The type of the object to deserialize to. + /// The content string to deserialize. + /// The deserialized object from the string. + T DeSerialize(string value); + + /// + /// Deserializes the string to the specified .NET type. + /// + /// The string to deserialize. + /// The type of the object to deserialize to. + /// The deserialized object from the string. + object DeSerialize(string value, Type type); + } + + /// + /// CAP message content wapper. + /// You can customize the message body filed name of the wrapper or add fields that you interested. + /// + /// + /// We use the wrapper to provide some additional information for the message content,which is important for CAP。 + /// Typically, we may need to customize the field display name of the message, + /// which includes interacting with other message components, which can be adapted in this manner + /// + public interface IMessagePacker + { + /// + /// Package a message object + /// + /// The obj message to be packed. + string Pack(CapMessage obj); + + /// + /// Unpack a message strings to object. + /// + /// The string of packed message. + CapMessage UnPack(string packingMessage); + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Abstractions/IModelBinderFactory.cs b/src/DotNetCore.CAP/Abstractions/IModelBinderFactory.cs new file mode 100644 index 0000000..df3423f --- /dev/null +++ b/src/DotNetCore.CAP/Abstractions/IModelBinderFactory.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using DotNetCore.CAP.Abstractions.ModelBinding; + +namespace DotNetCore.CAP.Abstractions +{ + /// + /// Model binder factory. + /// + public interface IModelBinderFactory + { + /// + /// Create a model binder by parameter. + /// + /// The method parameter info + /// A model binder instance. + IModelBinder CreateBinder(ParameterInfo parameter); + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Abstractions/ISubscriberExecutor.cs b/src/DotNetCore.CAP/Abstractions/ISubscriberExecutor.cs new file mode 100644 index 0000000..18c9f5d --- /dev/null +++ b/src/DotNetCore.CAP/Abstractions/ISubscriberExecutor.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using DotNetCore.CAP.Models; + +namespace DotNetCore.CAP.Abstractions +{ + /// + /// Consumer method executor. + /// + public interface ISubscriberExecutor + { + /// + /// Execute the consumer method. + /// + /// The received message. + Task ExecuteAsync(CapReceivedMessage receivedMessage); + } +} diff --git a/src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs b/src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs index b2896c1..0a23d2b 100644 --- a/src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs +++ b/src/DotNetCore.CAP/Abstractions/ModelBinding/ModelBindingResult.cs @@ -8,22 +8,22 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding public struct ModelBindingResult { /// - /// Creates a representing a failed model binding operation. + /// Creates a representing a failed model binding operation. /// - /// A representing a failed model binding operation. + /// A representing a failed model binding operation. public static ModelBindingResult Failed() { - return new ModelBindingResult(model: null, isSuccess: false); + return new ModelBindingResult(null, false); } /// - /// Creates a representing a successful model binding operation. + /// Creates a representing a successful model binding operation. /// /// The model value. May be null. - /// A representing a successful model bind. + /// A representing a successful model bind. 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() @@ -77,14 +67,14 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding { return IsSuccess == other.IsSuccess && - object.Equals(Model, other.Model); + Equals(Model, other.Model); } /// - /// Compares objects for equality. + /// Compares objects for equality. /// - /// A . - /// A . + /// A . + /// A . /// true if the objects are equal, otherwise false. public static bool operator ==(ModelBindingResult x, ModelBindingResult y) { @@ -92,10 +82,10 @@ namespace DotNetCore.CAP.Abstractions.ModelBinding } /// - /// Compares objects for inequality. + /// Compares objects for inequality. /// - /// A . - /// A . + /// A . + /// A . /// true if the objects are not equal, otherwise false. public static bool operator !=(ModelBindingResult x, ModelBindingResult y) { diff --git a/src/DotNetCore.CAP/Abstractions/TopicAttribute.cs b/src/DotNetCore.CAP/Abstractions/TopicAttribute.cs index bc4bb8f..454572f 100644 --- a/src/DotNetCore.CAP/Abstractions/TopicAttribute.cs +++ b/src/DotNetCore.CAP/Abstractions/TopicAttribute.cs @@ -2,8 +2,9 @@ namespace DotNetCore.CAP.Abstractions { + /// /// - /// An abstract attribute that for kafka attribute or rabbitmq attribute + /// An abstract attribute that for kafka attribute or rabbit mq attribute /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class TopicAttribute : Attribute @@ -14,19 +15,14 @@ namespace DotNetCore.CAP.Abstractions } /// - /// topic or exchange route key name. + /// Topic or exchange route key name. /// public string Name { get; } /// - /// kafak --> groups.id - /// rabbitmq --> queue.name + /// kafka --> groups.id + /// rabbit MQ --> queue.name /// public string Group { get; set; } = "cap.default.group"; - - /// - /// unused now - /// - public bool IsOneWay { get; set; } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs b/src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs index 77c389b..51ccbff 100644 --- a/src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs +++ b/src/DotNetCore.CAP/CAP.AppBuilderExtensions.cs @@ -1,37 +1,59 @@ using System; using DotNetCore.CAP; +using DotNetCore.CAP.Dashboard.GatewayProxy; using Microsoft.Extensions.DependencyInjection; +// ReSharper disable once CheckNamespace namespace Microsoft.AspNetCore.Builder { /// - /// app extensions for + /// app extensions for /// public static class AppBuilderExtensions { - /// + /// /// Enables cap for the current application /// - /// The instance this method extends. - /// The instance this method extends. + /// The instance this method extends. + /// The instance this method extends. public static IApplicationBuilder UseCap(this IApplicationBuilder app) { if (app == null) - { throw new ArgumentNullException(nameof(app)); - } - var marker = app.ApplicationServices.GetService(); - - if (marker == null) - { - throw new InvalidOperationException("Add Cap must be called on the service collection."); - } + CheckRequirement(app); var provider = app.ApplicationServices; + var bootstrapper = provider.GetRequiredService(); bootstrapper.BootstrapAsync(); + + if (provider.GetService() != null) + { + if (provider.GetService() != null) + app.UseMiddleware(); + app.UseMiddleware(); + } + return app; } + + private static void CheckRequirement(IApplicationBuilder app) + { + var marker = app.ApplicationServices.GetService(); + if (marker == null) + throw new InvalidOperationException( + "AddCap() must be called on the service collection. eg: services.AddCap(...)"); + + var messageQueueMarker = app.ApplicationServices.GetService(); + 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(); + if (databaseMarker == null) + throw new InvalidOperationException( + "You must be config used database provider at AddCap() options! eg: services.AddCap(options=>{ options.UseSqlServer(...) })"); + } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/CAP.Builder.cs b/src/DotNetCore.CAP/CAP.Builder.cs index e9cf165..e59270d 100644 --- a/src/DotNetCore.CAP/CAP.Builder.cs +++ b/src/DotNetCore.CAP/CAP.Builder.cs @@ -1,4 +1,5 @@ using System; +using DotNetCore.CAP.Abstractions; using Microsoft.Extensions.DependencyInjection; namespace DotNetCore.CAP @@ -10,10 +11,24 @@ namespace DotNetCore.CAP { } + /// + /// Used to verify cap database storage extension was added on a ServiceCollection + /// + public class CapDatabaseStorageMarkerService + { + } + + /// + /// Used to verify cap message queue extension was added on a ServiceCollection + /// + public class CapMessageQueueMakerService + { + } + /// /// Allows fine grained configuration of CAP services. /// - public class CapBuilder + public sealed class CapBuilder { public CapBuilder(IServiceCollection services) { @@ -21,9 +36,39 @@ namespace DotNetCore.CAP } /// - /// Gets the where MVC services are configured. + /// Gets the where MVC services are configured. + /// + public IServiceCollection Services { get; } + + /// + /// Add an . + /// + /// The type of the service. + public CapBuilder AddProducerService() + where T : class, ICapPublisher + { + return AddScoped(typeof(ICapPublisher), typeof(T)); + } + + /// + /// Add a custom content serializer /// - public IServiceCollection Services { get; private set; } + /// The type of the service. + public CapBuilder AddContentSerializer() + where T : class, IContentSerializer + { + return AddSingleton(typeof(IContentSerializer), typeof(T)); + } + + /// + /// Add a custom message wapper + /// + /// The type of the service. + public CapBuilder AddMessagePacker() + where T : class, IMessagePacker + { + return AddSingleton(typeof(IMessagePacker), typeof(T)); + } /// /// Adds a scoped service of the type specified in serviceType with an implementation @@ -35,13 +80,12 @@ namespace DotNetCore.CAP } /// - /// Add an . + /// Adds a singleton service of the type specified in serviceType with an implementation /// - /// The type of the service. - public virtual CapBuilder AddProducerService() - where T : class, ICapPublisher + private CapBuilder AddSingleton(Type serviceType, Type concreteType) { - return AddScoped(typeof(ICapPublisher), typeof(T)); + Services.AddSingleton(serviceType, concreteType); + return this; } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/CAP.Options.cs b/src/DotNetCore.CAP/CAP.Options.cs index d6cd441..b80d0e0 100644 --- a/src/DotNetCore.CAP/CAP.Options.cs +++ b/src/DotNetCore.CAP/CAP.Options.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using DotNetCore.CAP.Models; namespace DotNetCore.CAP { @@ -8,8 +9,6 @@ namespace DotNetCore.CAP /// public class CapOptions { - internal IList Extensions { get; private set; } - /// /// Default value for polling delay timeout, in seconds. /// @@ -21,26 +20,34 @@ namespace DotNetCore.CAP public const int DefaultQueueProcessorCount = 2; /// - /// Default successed message expriation timespan, in seconds. + /// Default succeeded message expiration time span, in seconds. /// - public const int DefaultSuccessMessageExpirationAfter = 3600; + public const int DefaultSucceedMessageExpirationAfter = 24 * 3600; /// /// Failed message retry waiting interval. /// - public const int DefaultFailedMessageWaitingInterval = 180; + public const int DefaultFailedMessageWaitingInterval = 600; + + /// + /// Failed message retry count. + /// + public const int DefaultFailedRetryCount = 100; public CapOptions() { PollingDelay = DefaultPollingDelay; QueueProcessorCount = DefaultQueueProcessorCount; - SuccessedMessageExpiredAfter = DefaultSuccessMessageExpirationAfter; - FailedMessageWaitingInterval = DefaultFailedMessageWaitingInterval; + SucceedMessageExpiredAfter = DefaultSucceedMessageExpirationAfter; + FailedRetryInterval = DefaultFailedMessageWaitingInterval; + FailedRetryCount = DefaultFailedRetryCount; Extensions = new List(); } + internal IList Extensions { get; } + /// - /// Productor job polling delay time. + /// Producer job polling delay time. /// Default is 15 sec. /// public int PollingDelay { get; set; } @@ -52,21 +59,27 @@ namespace DotNetCore.CAP public int QueueProcessorCount { get; set; } /// - /// Sent or received successed message after timespan of due, then the message will be deleted at due time. - /// Dafault is 3600 seconds. + /// Sent or received succeed message after time span of due, then the message will be deleted at due time. + /// Default is 24*3600 seconds. /// - public int SuccessedMessageExpiredAfter { get; set; } + public int SucceedMessageExpiredAfter { get; set; } /// /// Failed messages polling delay time. - /// Default is 180 seconds. + /// Default is 600 seconds. /// - public int FailedMessageWaitingInterval { get; set; } + public int FailedRetryInterval { get; set; } /// /// We’ll invoke this call-back with message type,name,content when requeue failed message. /// - public Action FailedCallback { get; set; } + public Action FailedCallback { get; set; } + + /// + /// The number of message retries, the retry will stop when the threshold is reached. + /// Default is 100 times. + /// + public int FailedRetryCount { get; set; } /// /// Registers an extension that will be executed when building services. diff --git a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs index 51f645b..cfb1fad 100644 --- a/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs +++ b/src/DotNetCore.CAP/CAP.ServiceCollectionExtensions.cs @@ -1,27 +1,26 @@ using System; using System.Collections.Generic; -using System.Reflection; using DotNetCore.CAP; using DotNetCore.CAP.Abstractions; -using DotNetCore.CAP.Infrastructure; using DotNetCore.CAP.Internal; using DotNetCore.CAP.Processor; using DotNetCore.CAP.Processor.States; using Microsoft.Extensions.DependencyInjection.Extensions; +// ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection { /// - /// Contains extension methods to for configuring consistence services. + /// Contains extension methods to for configuring consistence services. /// public static class ServiceCollectionExtensions { /// - /// Adds and configures the consistence services for the consitence. + /// Adds and configures the consistence services for the consistency. /// /// The services available in the application. - /// An action to configure the . - /// An for application services. + /// An action to configure the . + /// An for application services. public static CapBuilder AddCap( this IServiceCollection services, Action setupAction) @@ -33,33 +32,38 @@ namespace Microsoft.Extensions.DependencyInjection AddSubscribeServices(services); + //Serializer and model binder + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + //Bootstrapper and Processors services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - //Processors + //Queue's message processor services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); services.AddTransient(); //Executors services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.TryAddSingleton(); //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 +73,13 @@ namespace Microsoft.Extensions.DependencyInjection { var consumerListenerServices = new List>(); foreach (var rejectedServices in services) - { if (rejectedServices.ImplementationType != null && typeof(ICapSubscribe).IsAssignableFrom(rejectedServices.ImplementationType)) - { consumerListenerServices.Add(new KeyValuePair(typeof(ICapSubscribe), rejectedServices.ImplementationType)); - } - } foreach (var service in consumerListenerServices) - { services.AddTransient(service.Key, service.Value); - } } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs b/src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs new file mode 100644 index 0000000..ca707ee --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/BatchCommandDispatcher.cs @@ -0,0 +1,34 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace DotNetCore.CAP.Dashboard +{ + internal class BatchCommandDispatcher : IDashboardDispatcher + { + private readonly Action _command; + + public BatchCommandDispatcher(Action command) + { + _command = command; + } + + public async Task Dispatch(DashboardContext context) + { + var messageIds = await context.Request.GetFormValuesAsync("messages[]"); + if (messageIds.Count == 0) + { + context.Response.StatusCode = 422; + return; + } + + foreach (var messageId in messageIds) + { + var id = int.Parse(messageId); + _command(context, id); + } + + context.Response.StatusCode = (int) HttpStatusCode.NoContent; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs b/src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs new file mode 100644 index 0000000..9997280 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/CAP.DashboardMiddleware.cs @@ -0,0 +1,68 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using DotNetCore.CAP.Dashboard; +using Microsoft.AspNetCore.Http; + +// ReSharper disable once CheckNamespace +namespace DotNetCore.CAP +{ + public class DashboardMiddleware + { + private readonly RequestDelegate _next; + private readonly DashboardOptions _options; + private readonly RouteCollection _routes; + private readonly IStorage _storage; + + public DashboardMiddleware(RequestDelegate next, DashboardOptions options, IStorage storage, + RouteCollection routes) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + _routes = routes ?? throw new ArgumentNullException(nameof(routes)); + } + + public Task Invoke(HttpContext context) + { + if (!context.Request.Path.StartsWithSegments(_options.PathMatch, + out var matchedPath, out var remainingPath)) return _next(context); + + // Update the path + var path = context.Request.Path; + var pathBase = context.Request.PathBase; + context.Request.PathBase = pathBase.Add(matchedPath); + context.Request.Path = remainingPath; + + try + { + var dashboardContext = new CapDashboardContext(_storage, _options, context); + 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; + + return Task.CompletedTask; + } + + dashboardContext.UriMatch = findResult.Item2; + + return findResult.Item1.Dispatch(dashboardContext); + } + finally + { + context.Request.PathBase = pathBase; + context.Request.Path = path; + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs b/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs new file mode 100644 index 0000000..b144996 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using DotNetCore.CAP.Dashboard; + +// ReSharper disable once CheckNamespace +namespace DotNetCore.CAP +{ + public class DashboardOptions + { + public DashboardOptions() + { + AppPath = "/"; + PathMatch = "/cap"; + Authorization = new[] {new LocalRequestsOnlyAuthorizationFilter()}; + StatsPollingInterval = 2000; + } + + /// + /// The path for the Back To Site link. Set to in order to hide the Back To Site link. + /// + public string AppPath { get; set; } + + public string PathMatch { get; set; } + + public IEnumerable Authorization { get; set; } + + /// + /// The interval the /stats endpoint should be polled with. + /// + public int StatsPollingInterval { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs b/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs new file mode 100644 index 0000000..c2ef243 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/CAP.DashboardOptionsExtensions.cs @@ -0,0 +1,50 @@ +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 +{ + internal sealed class DashboardOptionsExtension : ICapOptionsExtension + { + private readonly Action _options; + + public DashboardOptionsExtension(Action option) + { + _options = option; + } + + public void AddServices(IServiceCollection services) + { + var dashboardOptions = new DashboardOptions(); + _options?.Invoke(dashboardOptions); + services.AddSingleton(dashboardOptions); + services.AddSingleton(DashboardRoutes.Routes); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + } + } +} + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class CapOptionsExtensions + { + public static CapOptions UseDashboard(this CapOptions capOptions) + { + return capOptions.UseDashboard(opt => { }); + } + + public static CapOptions UseDashboard(this CapOptions capOptions, Action options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + capOptions.RegisterExtension(new DashboardOptionsExtension(options)); + + return capOptions; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs b/src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs new file mode 100644 index 0000000..94c0d99 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/CombinedResourceDispatcher.cs @@ -0,0 +1,31 @@ +using System.Reflection; + +namespace DotNetCore.CAP.Dashboard +{ + internal class CombinedResourceDispatcher : EmbeddedResourceDispatcher + { + private readonly Assembly _assembly; + private readonly string _baseNamespace; + private readonly string[] _resourceNames; + + public CombinedResourceDispatcher( + string contentType, + Assembly assembly, + string baseNamespace, + params string[] resourceNames) : base(contentType, assembly, null) + { + _assembly = assembly; + _baseNamespace = baseNamespace; + _resourceNames = resourceNames; + } + + protected override void WriteResponse(DashboardResponse response) + { + foreach (var resourceName in _resourceNames) + WriteResource( + response, + _assembly, + $"{_baseNamespace}.{resourceName}"); + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs b/src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs new file mode 100644 index 0000000..75ac2b4 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/CommandDispatcher.cs @@ -0,0 +1,35 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace DotNetCore.CAP.Dashboard +{ + internal class CommandDispatcher : IDashboardDispatcher + { + private readonly Func _command; + + public CommandDispatcher(Func command) + { + _command = command; + } + + public Task Dispatch(DashboardContext context) + { + var request = context.Request; + var response = context.Response; + + if (!"POST".Equals(request.Method, StringComparison.OrdinalIgnoreCase)) + { + response.StatusCode = (int) HttpStatusCode.MethodNotAllowed; + return Task.FromResult(false); + } + + if (_command(context)) + response.StatusCode = (int) HttpStatusCode.NoContent; + else + response.StatusCode = 422; + + return Task.FromResult(true); + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Content/css/bootstrap.min.css b/src/DotNetCore.CAP/Dashboard/Content/css/bootstrap.min.css new file mode 100644 index 0000000..e67a842 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/Content/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(fonts/glyphicons-halflings-regular/eot);src:url(fonts/glyphicons-halflings-regular/eot?#iefix) format('embedded-opentype'),url(fonts/glyphicons-halflings-regular/woff2) format('woff2'),url(fonts/glyphicons-halflings-regular/woff) format('woff'),url(fonts/glyphicons-halflings-regular/ttf) format('truetype'),url(fonts/glyphicons-halflings-regular/svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid 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)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Content/css/cap.css b/src/DotNetCore.CAP/Dashboard/Content/css/cap.css new file mode 100644 index 0000000..dbcc0a5 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/Content/css/cap.css @@ -0,0 +1,460 @@ +/* Sticky footer styles +-------------------------------------------------- */ + +html, body { + height: 100%; + /* The html and body elements cannot have any padding or margin. */ +} + +body { + /* 75px to make the container go all the way to the bottom of the topbar */ + padding-top: 75px; +} + +/* Wrapper for page content to push down footer */ + +#wrap { + 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; } + + +/* Custom page CSS +-------------------------------------------------- */ + +.container .credit { margin: 20px 0; } + +.page-header { + margin-top: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.btn-death { + background-color: #777; + border-color: #666; + color: #fff; +} + +.btn-death:hover { + background-color: #666; + border-color: #555; + color: #fff; +} + +.list-group .list-group-item .glyphicon { margin-right: 3px; } + +.breadcrumb { + background-color: inherit; + margin-bottom: 10px; + padding: 0; +} + +.btn-toolbar-label { + display: inline-block; + margin-left: 5px; + padding: 7px 0; + vertical-align: middle; +} + +.btn-toolbar-label-sm { padding: 5px 0; } + +.btn-toolbar-spacer { + display: inline-block; + height: 1px; + width: 5px; +} + +a:hover .label-hover { + background-color: #2a6496 !important; + color: #fff !important; +} + +.expander { cursor: pointer; } + +.expandable { display: none; } + +.table-inner { + font-size: 90%; + margin-bottom: 7px; +} + +.min-width { + white-space: nowrap; + width: 1%; +} + +.align-right { text-align: right; } + +.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; + border-color: #fbeed5; +} + +.table > tbody > tr.highlight:hover > td, .table > tbody > tr.highlight:hover > th { + background-color: #f6f2dd; + border-color: #f5e8ce; +} + +.word-break { word-break: break-all; } + +/* Statistics widget +-------------------------------------------------- */ + +#stats .list-group-item { + background-color: #f8f8f8; + border-color: #e7e7e7; +} + +#stats a.list-group-item { color: #777; } + +#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 { + background-color: #e7e7e7; + border-color: #e7e7e7; + color: #555; +} + +.table td.failed-job-details { + 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 pre, .obsolete-data .label { background-color: #f5f5f5; } + +.obsolete-data .glyphicon-question-sign { + color: #999; + font-size: 80%; +} + +.stack-trace { + border: none; + padding: 10px; +} + +.st-type { font-weight: bold; } + +.st-param-name { color: #666; } + +.st-file { color: #999; } + +.st-method { + color: #00008B; + font-weight: bold; +} + +.st-line { color: #8B008B; } + +.width-200 { width: 200px; } + +.btn-toolbar-top { margin-bottom: 10px; } + +.paginator .btn { color: #428bca; } + +.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; + width: 100%; +} + +.job-snippet > * { + display: table-cell; + vertical-align: top; +} + +.job-snippet-code { vertical-align: top; } + +.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 code { + background-color: #f5f5f5; + color: black; + display: block; +} + +.job-snippet-code pre .comment { color: rgb(0, 128, 0); } + +.job-snippet-code pre .keyword { color: rgb(0, 0, 255); } + +.job-snippet-code pre .string { color: rgb(163, 21, 21); } + +.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 dt { + color: #999; + font-weight: normal; + text-shadow: 0 1px white; +} + +.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 { + -ms-border-radius: 3px; + background-color: #fff; + border: 1px solid #e5e5e5; + border-radius: 3px; + display: block; + margin-bottom: 7px; + padding: 12px; + position: relative; +} + +.state-card-title { margin-bottom: 0; } + +.state-card-title .pull-right { margin-top: 3px; } + +.state-card-text { + margin-bottom: 0; + margin-top: 5px; +} + +.state-card h4 { margin-top: 0; } + +.state-card-body { + -ms-border-bottom-left-radius: 3px; + -ms-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-bottom: 0; + margin-top: 5px; +} + +.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; + margin-bottom: 0; + padding: 0 20px; +} + +.state-card-body .exception-type { margin-top: 0; } + +/* Job History styles */ + +.job-history { + margin-bottom: 10px; + opacity: 0.8; +} + +.job-history.job-history-current { opacity: 1.0; } + +.job-history-heading { + -ms-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 { + background-color: #f5f5f5; + padding: 10px; +} + +.job-history-title { + margin-bottom: 2px; + margin-top: 0; +} + +.job-history dl { + margin-bottom: 5px; + margin-top: 5px; +} + +.job-history .stack-trace { + background-color: transparent; + margin-bottom: 5px; + padding: 0 20px; +} + +.job-history .exception-type { margin-top: 0; } + +.job-history-current .job-history-heading, +.job-history-current small { color: white; } + +a.job-method { color: inherit; } + +.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; + font-size: 12px; + line-height: 1; + min-width: 10px; + padding: 2px 6px; + text-align: center; + transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; + vertical-align: baseline; + white-space: nowrap; +} + +span.metric.highlighted { + color: #fff !important; + font-weight: bold; +} + +span.metric-default { + border-color: #777; + color: #777; +} + +span.metric-default.highlighted { background-color: #777; } + +div.metric { + -ms-border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + border: solid 1px transparent; + border-radius: 4px; + 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 { + font-size: 26px; + padding: 15px 15px 0; + text-align: center; +} + +div.metric .metric-description { + padding: 0 15px 15px; + text-align: center; +} + +div.metric.metric-default { border-color: #ddd; } + +div.metric-info, +span.metric-info { + border-color: #5bc0de; + color: #5bc0de; +} + +span.metric-info.highlighted { background-color: #5bc0de; } + +div.metric-warning, +span.metric-warning { + border-color: #f0ad4e; + color: #f0ad4e; +} + +span.metric-warning.highlighted { background-color: #f0ad4e; } + +div.metric-success, +span.metric-success { + border-color: #5cb85c; + color: #5cb85c; +} + +span.metric-success.highlighted { background-color: #5cb85c; } + +div.metric-danger, +span.metric-danger { + border-color: #d9534f; + color: #d9534f; +} + +span.metric-danger.highlighted { background-color: #d9534f; } + +span.metric-null, +div.metric-null { display: none; } + +@media (min-width: 992px) { + #stats { + position: fixed; + width: 220px + } +} + +@media (min-width: 1200px) { + #stats { width: 262.5px; } +} + +.subscribe-table td { vertical-align: middle !important; } + +.subscribe-table td[rowspan] { font-weight: bold; } + +#legend { + background: rgba(173, 169, 169, 0.13); + color: #000; + position: absolute; + right: 20px; + top: 110px; +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Content/css/jsonview.min.css b/src/DotNetCore.CAP/Dashboard/Content/css/jsonview.min.css new file mode 100644 index 0000000..97805e3 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/Content/css/jsonview.min.css @@ -0,0 +1 @@ +@charset "UTF-8";.jsonview{font-family:monospace;font-size:1.1em;white-space:pre-wrap}.jsonview .prop{font-weight:700;text-decoration:none;color:#000}.jsonview .null,.jsonview .undefined{color:red}.jsonview .bool,.jsonview .num{color:#00f}.jsonview .string{color:green;white-space:pre-wrap}.jsonview .string.multiline{display:inline-block;vertical-align:text-top}.jsonview .collapser{position:absolute;left:-1em;cursor:pointer}.jsonview .collapsible{transition:height 1.2s;transition:width 1.2s}.jsonview .collapsible.collapsed{height:.8em;width:1em;display:inline-block;overflow:hidden;margin:0}.jsonview .collapsible.collapsed:before{content:"…";width:1em;margin-left:.2em}.jsonview .collapser.collapsed{transform:rotate(0)}.jsonview .q{display:inline-block;width:0;color:transparent}.jsonview li{position:relative}.jsonview ul{list-style:none;margin:0 0 0 2em;padding:0}.jsonview h1{font-size:1.2em} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Content/css/rickshaw.min.css b/src/DotNetCore.CAP/Dashboard/Content/css/rickshaw.min.css new file mode 100644 index 0000000..6958078 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/Content/css/rickshaw.min.css @@ -0,0 +1 @@ +.rickshaw_graph .detail .item.left,.rickshaw_graph .detail .x_label.left{left:0}.rickshaw_graph .detail .item.right,.rickshaw_graph .detail .x_label.right{right:0}.rickshaw_graph .detail .item,.rickshaw_graph .detail .x_label,.rickshaw_graph .x_tick .title{font-family:Arial,sans-serif;font-size:12px;white-space:nowrap}.rickshaw_graph .detail{pointer-events:none;position:absolute;top:0;z-index:2;background:rgba(0,0,0,.1);bottom:0;width:1px;transition:opacity .25s linear;-moz-transition:opacity .25s linear;-o-transition:opacity .25s linear;-webkit-transition:opacity .25s linear}.rickshaw_graph .detail.inactive{opacity:0}.rickshaw_graph .detail .item.active{opacity:1}.rickshaw_graph .detail .x_label{border-radius:3px;padding:6px;opacity:.5;border:1px solid #e0e0e0;position:absolute;background:#fff}.rickshaw_graph .detail .item{position:absolute;z-index:2;border-radius:3px;padding:.25em;opacity:0;background:rgba(0,0,0,.4);color:#fff;border:1px solid rgba(0,0,0,.4);margin-left:1em;margin-right:1em;margin-top:-1em}.rickshaw_graph .detail .item.active{background:rgba(0,0,0,.8)}.rickshaw_graph .detail .item:after{position:absolute;display:block;width:0;height:0;content:"";border:5px solid transparent}.rickshaw_graph .detail .item.left:after{top:1em;left:-5px;margin-top:-5px;border-right-color:rgba(0,0,0,.8);border-left-width:0}.rickshaw_graph .detail .item.right:after{top:1em;right:-5px;margin-top:-5px;border-left-color:rgba(0,0,0,.8);border-right-width:0}.rickshaw_graph .detail .dot{width:4px;height:4px;margin-left:-3px;margin-top:-3.5px;border-radius:5px;position:absolute;box-shadow:0 0 2px rgba(0,0,0,.6);box-sizing:content-box;-moz-box-sizing:content-box;background:#fff;border-width:2px;border-style:solid;display:none;background-clip:padding-box}.rickshaw_graph .detail .dot.active{display:block}.rickshaw_graph{position:relative}.rickshaw_graph svg{display:block;overflow:hidden}.rickshaw_graph .x_tick{position:absolute;top:0;bottom:0;width:0;border-left:1px dotted rgba(0,0,0,.2);pointer-events:none}.rickshaw_graph .x_tick .title{position:absolute;opacity:.5;margin-left:3px;bottom:1px}.rickshaw_annotation_timeline{height:1px;border-top:1px solid #e0e0e0;margin-top:10px;position:relative}.rickshaw_annotation_timeline .annotation{position:absolute;height:6px;width:6px;margin-left:-2px;top:-3px;border-radius:5px;background-color:rgba(0,0,0,.25)}.rickshaw_graph .annotation_line{position:absolute;top:0;bottom:-6px;width:0;border-left:2px solid rgba(0,0,0,.3);display:none}.rickshaw_graph .annotation_line.active{display:block}.rickshaw_graph .annotation_range{background:rgba(0,0,0,.1);display:none;position:absolute;top:0;bottom:-6px}.rickshaw_graph .annotation_range.active{display:block}.rickshaw_graph .annotation_range.active.offscreen{display:none}.rickshaw_annotation_timeline .annotation .content{background:#fff;color:#000;opacity:.9;box-shadow:0 0 2px rgba(0,0,0,.8);border-radius:3px;position:relative;z-index:20;font-size:12px;padding:6px 8px 8px;top:18px;left:-11px;width:160px;display:none;cursor:pointer}.rickshaw_annotation_timeline .annotation .content:before{content:"\25b2";position:absolute;top:-11px;color:#fff;text-shadow:0 -1px 1px rgba(0,0,0,.8)}.rickshaw_annotation_timeline .annotation.active,.rickshaw_annotation_timeline .annotation:hover{background-color:rgba(0,0,0,.8);cursor:none}.rickshaw_annotation_timeline .annotation .content:hover{z-index:50}.rickshaw_annotation_timeline .annotation.active .content{display:block}.rickshaw_annotation_timeline .annotation:hover .content{display:block;z-index:50}.rickshaw_graph .x_axis_d3,.rickshaw_graph .y_axis{fill:none}.rickshaw_graph .x_ticks_d3 .tick,.rickshaw_graph .y_ticks .tick line{stroke:rgba(0,0,0,.16);stroke-width:2px;shape-rendering:crisp-edges;pointer-events:none}.rickshaw_graph .x_grid_d3 .tick,.rickshaw_graph .y_grid .tick{z-index:-1;stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:1 1}.rickshaw_graph .y_grid .tick[data-y-value="0"]{stroke-dasharray:1 0}.rickshaw_graph .x_grid_d3 path,.rickshaw_graph .y_grid path{fill:none;stroke:none}.rickshaw_graph .x_ticks_d3 path,.rickshaw_graph .y_ticks path{fill:none;stroke:grey}.rickshaw_graph .x_ticks_d3 text,.rickshaw_graph .y_ticks text{opacity:.5;font-size:12px;pointer-events:none}.rickshaw_graph .x_tick.glow .title,.rickshaw_graph .y_ticks.glow text{fill:#000;color:#000;text-shadow:-1px 1px 0 rgba(255,255,255,.1),1px -1px 0 rgba(255,255,255,.1),1px 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1),0 -1px 0 rgba(255,255,255,.1),1px 0 0 rgba(255,255,255,.1),-1px 0 0 rgba(255,255,255,.1),-1px -1px 0 rgba(255,255,255,.1)}.rickshaw_graph .x_tick.inverse .title,.rickshaw_graph .y_ticks.inverse text{fill:#fff;color:#fff;text-shadow:-1px 1px 0 rgba(0,0,0,.8),1px -1px 0 rgba(0,0,0,.8),1px 1px 0 rgba(0,0,0,.8),0 1px 0 rgba(0,0,0,.8),0 -1px 0 rgba(0,0,0,.8),1px 0 0 rgba(0,0,0,.8),-1px 0 0 rgba(0,0,0,.8),-1px -1px 0 rgba(0,0,0,.8)}.rickshaw_legend{font-family:Arial;font-size:12px;color:#fff;background:#404040;display:inline-block;padding:12px 5px;border-radius:2px;position:relative}.rickshaw_legend:hover{z-index:10}.rickshaw_legend .swatch{width:10px;height:10px;border:1px solid rgba(0,0,0,.2)}.rickshaw_legend .line{clear:both;line-height:140%;padding-right:15px}.rickshaw_legend .line .swatch{display:inline-block;margin-right:3px;border-radius:2px}.rickshaw_legend .label{margin:0;white-space:nowrap;display:inline;font-size:inherit;background-color:transparent;color:inherit;font-weight:400;line-height:normal;padding:0;text-shadow:none}.rickshaw_legend .action:hover{opacity:.6}.rickshaw_legend .action{margin-right:.2em;opacity:.2;cursor:pointer;font-size:14px}.rickshaw_legend .line.disabled{opacity:.4}.rickshaw_legend ul{list-style-type:none;padding:0;margin:2px;cursor:pointer}.rickshaw_legend li{padding:0 0 0 2px;min-width:80px;white-space:nowrap}.rickshaw_legend li:hover{background:rgba(255,255,255,.08);border-radius:3px}.rickshaw_legend li:active{background:rgba(255,255,255,.2);border-radius:3px} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.eot b/src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..b93a4953fff68df523aa7656497ee339d6026d64 GIT binary patch literal 20127 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.ttf b/src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1413fc609ab6f21774de0cb7e01360095584f65b GIT binary patch literal 45404 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 G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.woff2 b/src/DotNetCore.CAP/Dashboard/Content/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 diff --git a/src/DotNetCore.CAP/Dashboard/Content/js/bootstrap.min.js b/src/DotNetCore.CAP/Dashboard/Content/js/bootstrap.min.js new file mode 100644 index 0000000..133aeec --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/Content/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Content/js/cap.js b/src/DotNetCore.CAP/Dashboard/Content/js/cap.js new file mode 100644 index 0000000..be6a83d --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/Content/js/cap.js @@ -0,0 +1,655 @@ +(function(cap) { + cap.config = { + pollInterval: $("#capConfig").data("pollinterval"), + pollUrl: $("#capConfig").data("pollurl"), + locale: document.documentElement.lang + }; + + cap.Metrics = (function() { + function Metrics() { + this._metrics = {}; + } + + Metrics.prototype.addElement = function(name, element) { + if (!(name in this._metrics)) { + this._metrics[name] = []; + } + + this._metrics[name].push(element); + }; + + Metrics.prototype.getElements = function(name) { + if (!(name in this._metrics)) { + return []; + } + + return this._metrics[name]; + }; + + Metrics.prototype.getNames = function() { + const result = []; + const metrics = this._metrics; + + for (let name in metrics) { + if (metrics.hasOwnProperty(name)) { + result.push(name); + } + } + + return result; + }; + + return Metrics; + })(); + + var BaseGraph = function() { + this.height = 200; + }; + + BaseGraph.prototype.update = function() { + const graph = this._graph; + + const width = $(graph.element).innerWidth(); + if (width !== graph.width) { + graph.configure({ + width: width, + height: this.height + }); + } + + graph.update(); + }; + + BaseGraph.prototype._initGraph = function(element, settings, xSettings, ySettings) { + + 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"); } + }); + + if (xSettings) { + this._xAxis = new Rickshaw.Graph.Axis.Time($.extend({ + graph: graph, + timeFixture: new Rickshaw.Fixtures.Time.Local() + }, + xSettings)); + + 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.render(); + }; + + cap.RealtimeGraph = (function() { + function RealtimeGraph(element, + pubSucceeded, + pubFailed, + pubSucceededStr, + pubFailedStr, + recSucceeded, + recFailed, + recSucceededStr, + recFailedStr + ) { + this._pubSucceeded = pubSucceeded; + this._pubSucceededStr = pubSucceededStr; + + this._pubFailed = pubFailed; + this._pubFailedStr = pubFailedStr; + + this._recSucceeded = recSucceeded; + this._recSucceededStr = recSucceededStr; + + 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, + {}); + } + + RealtimeGraph.prototype = Object.create(BaseGraph.prototype); + + RealtimeGraph.prototype.appendHistory = function(statistics) { + const newPubSucceeded = parseInt(statistics["published_succeeded:count"].intValue); + const newPubFailed = parseInt(statistics["published_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 + ) { + const pubSucceeded = newPubSucceeded - this._pubSucceeded; + const pubFailed = newPubFailed - this._pubFailed; + + const recSucceeded = newRecSucceeded - this._recSucceeded; + const recFailed = newRecFailed - this._recFailed; + + const dataObj = {}; + dataObj[this._pubFailedStr] = pubFailed; + dataObj[this._pubSucceededStr] = pubSucceeded; + dataObj[this._recFailedStr] = recFailed; + dataObj[this._recSucceededStr] = recSucceeded; + + this._graph.series.addData(dataObj); + this._graph.render(); + } + + this._pubSucceeded = newPubSucceeded; + this._pubFailed = newPubFailed; + + this._recSucceeded = newRecSucceeded; + this._recFailed = newRecFailed; + }; + + 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" }); + } + + HistoryGraph.prototype = Object.create(BaseGraph.prototype); + + return HistoryGraph; + })(); + + cap.StatisticsPoller = (function() { + function StatisticsPoller(metricsCallback, statisticsUrl, pollInterval) { + this._metricsCallback = metricsCallback; + this._listeners = []; + this._statisticsUrl = statisticsUrl; + this._pollInterval = pollInterval; + this._intervalId = null; + } + + StatisticsPoller.prototype.start = function() { + var self = this; + + const intervalFunc = function() { + try { + $.post(self._statisticsUrl, + { metrics: self._metricsCallback() }, + function(data) { + self._notifyListeners(data); + }); + } catch (e) { + console.log(e); + } + }; + + this._intervalId = setInterval(intervalFunc, this._pollInterval); + }; + + StatisticsPoller.prototype.stop = function() { + if (this._intervalId !== null) { + clearInterval(this._intervalId); + this._intervalId = null; + } + }; + + StatisticsPoller.prototype.addListener = function(listener) { + this._listeners.push(listener); + }; + + StatisticsPoller.prototype._notifyListeners = function(statistics) { + const length = this._listeners.length; + var i; + + for (i = 0; i < length; i++) { + this._listeners[i](statistics); + } + }; + + return StatisticsPoller; + })(); + + cap.Page = (function() { + function Page(config) { + this._metrics = new cap.Metrics(); + + var self = this; + this._poller = new cap.StatisticsPoller( + function() { return self._metrics.getNames(); }, + config.pollUrl, + config.pollInterval); + + this._initialize(config.locale); + + this.realtimeGraph = this._createRealtimeGraph("realtimeGraph"); + this.historyGraph = this._createHistoryGraph("historyGraph"); + + this._poller.start(); + }; + + Page.prototype._createRealtimeGraph = function(elementId) { + const realtimeElement = document.getElementById(elementId); + if (realtimeElement) { + 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"); + + 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, + pubFailed, + pubSucceededStr, + pubFailedStr, + recSucceeded, + recFailed, + recSucceededStr, + recFailedStr + ); + + this._poller.addListener(function(data) { + realtimeGraph.appendHistory(data); + }); + + $(window).resize(function() { + realtimeGraph.update(); + }); + + return realtimeGraph; + } + + return null; + }; + + Page.prototype._createHistoryGraph = function(elementId) { + const historyElement = document.getElementById(elementId); + if (historyElement) { + const createSeries = function(obj) { + const series = []; + for (let date in obj) { + if (obj.hasOwnProperty(date)) { + const value = obj[date]; + const point = { x: Date.parse(date) / 1000, y: value }; + series.unshift(point); + } + } + return series; + }; + + 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"); + + 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, + publishedFailed, + publishedSucceededStr, + publishedFailedStr, + receivedSucceeded, + receivedFailed, + receivedSucceededStr, + receivedFailedStr, + ); + + $(window).resize(function() { + historyGraph.update(); + }); + + return historyGraph; + } + + return null; + }; + + Page.prototype._initialize = function(locale) { + moment.locale(locale); + const updateRelativeDates = function() { + $("*[data-moment]").each(function() { + const $this = $(this); + const timestamp = $this.data("moment"); + + if (timestamp) { + const time = moment(timestamp, "X"); + $this.html(time.fromNow()) + .attr("title", time.format("llll")) + .attr("data-container", "body"); + } + }); + + $("*[data-moment-title]").each(function() { + const $this = $(this); + const timestamp = $this.data("moment-title"); + + if (timestamp) { + const time = moment(timestamp, "X"); + $this.prop("title", time.format("llll")) + .attr("data-container", "body"); + } + }); + + $("*[data-moment-local]").each(function() { + const $this = $(this); + const timestamp = $this.data("moment-local"); + + if (timestamp) { + const time = moment(timestamp, "X"); + $this.html(time.format("l LTS")); + } + }); + }; + + updateRelativeDates(); + setInterval(updateRelativeDates, 30 * 1000); + + $("*[title]").tooltip(); + + var self = this; + $("*[data-metric]").each(function() { + const name = $(this).data("metric"); + self._metrics.addElement(name, this); + }); + + 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") + .removeClass() + .addClass(["metric", metricClass, highlighted].join(" ")); + } + } + }); + + $(document).on("click", + "*[data-ajax]", + function(e) { + var $this = $(this); + const confirmText = $this.data("confirm"); + + 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(); + }); + + $(document).on("click", + ".expander", + function(e) { + var $expander = $(this), + $expandable = $expander.closest("tr").next().find(".expandable"); + + if (!$expandable.is(":visible")) { + $expander.text("Less details..."); + } + + $expandable.slideToggle( + 150, + function() { + if (!$expandable.is(":visible")) { + $expander.text("More details..."); + } + }); + e.preventDefault(); + }); + + $(".js-jobs-list").each(function() { + var container = this; + + var selectRow = function(row, isSelected) { + const $checkbox = $(".js-jobs-list-checkbox", row); + if ($checkbox.length > 0) { + $checkbox.prop("checked", isSelected); + $(row).toggleClass("highlight", isSelected); + } + }; + + var toggleRowSelection = function(row) { + const $checkbox = $(".js-jobs-list-checkbox", row); + if ($checkbox.length > 0) { + 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"); + + $(".js-jobs-list-command", container) + .prop("disabled", state === "none-selected"); + }; + + var updateListState = function() { + const selectedRows = $(".js-jobs-list-checkbox", container).map(function() { + return $(this).prop("checked"); + }).get(); + + var state = "none-selected"; + + if (selectedRows.length > 0) { + state = "some-selected"; + + if ($.inArray(false, selectedRows) === -1) { + state = "all-selected"; + } else if ($.inArray(true, selectedRows) === -1) { + 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(); + + e.stopPropagation(); + }); + + $(this).on("click", + ".js-jobs-list-row", + function(e) { + if ($(e.target).is("a")) return; + + toggleRowSelection(this); + updateListState(); + }); + + $(this).on("click", + ".js-jobs-list-select-all", + function() { + var selectRows = $(this).is(":checked"); + + $(".js-jobs-list-row", container).each(function() { + selectRow(this, selectRows); + }); + + updateListState(); + }); + + $(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(); + }); + + updateListState(); + }); + }; + + return Page; + })(); +})(window.Cap = window.Cap || {}); + +$(function() { + Cap.page = new Cap.Page(Cap.config); +}); + +(function() { + + var json = null; + + $(".openModal").click(function() { + const url = $(this).data("url"); + $.ajax({ + url: url, + dataType: "json", + success: function(data) { + json = data; + $("#formatBtn").click(); + $(".modal").modal("show"); + } + }); + }); + + $("#formatBtn").click(function() { + $("#jsonContent").JSONView(json); + }); + + $("#rawBtn").click(function() { + $("#jsonContent").text(JSON.stringify(json)); + }); + + $("#expandBtn").click(function() { + $("#jsonContent").JSONView("expand"); + }); + + $("#collapseBtn").click(function() { + $("#jsonContent").JSONView("collapse"); + }); + +})(); + +function nodeSwitch(id) { + console.log(`id:${id}`); + document.cookie = `cap.node=${escape(id)};`; +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Dashboard/Content/js/d3.layout.min.js b/src/DotNetCore.CAP/Dashboard/Content/js/d3.layout.min.js new file mode 100644 index 0000000..6704ca9 --- /dev/null +++ b/src/DotNetCore.CAP/Dashboard/Content/js/d3.layout.min.js @@ -0,0 +1 @@ +(function(){function a(a){var b=a.source,d=a.target,e=c(b,d),f=[b];while(b!==e)b=b.parent,f.push(b);var g=f.length;while(d!==e)f.splice(g,0,d),d=d.parent;return f}function b(a){var b=[],c=a.parent;while(c!=null)b.push(a),a=c,c=c.parent;return b.push(a),b}function c(a,c){if(a===c)return a;var d=b(a),e=b(c),f=d.pop(),g=e.pop(),h=null;while(f===g)h=f,f=d.pop(),g=e.pop();return h}function g(a){a.fixed|=2}function h(a){a!==f&&(a.fixed&=1)}function i(){j(),f.fixed&=1,e=f=null}function j(){f.px+=d3.event.dx,f.py+=d3.event.dy,e.resume()}function k(a,b,c){var d=0,e=0;a.charge=0;if(!a.leaf){var f=a.nodes,g=f.length,h=-1,i;while(++hd&&(c=b,d=e);return c}function u(a){return a.reduce(v,0)}function v(a,b){return a+b[1]}function w(a,b){return x(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function x(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function y(a){return[d3.min(a),d3.max(a)]}function z(a,b){return a.sort=d3.rebind(a,b.sort),a.children=d3.rebind(a,b.children),a.links=D,a.value=d3.rebind(a,b.value),a.nodes=function(b){return E=!0,(a.nodes=a)(b)},a}function A(a){return a.children}function B(a){return a.value}function C(a,b){return b.value-a.value}function D(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function F(a,b){return a.value-b.value}function G(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function H(a,b){a._pack_next=b,b._pack_prev=a}function I(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function J(a){function l(a){b=Math.min(a.x-a.r,b),c=Math.max(a.x+a.r,c),d=Math.min(a.y-a.r,d),e=Math.max(a.y+a.r,e)}var b=Infinity,c=-Infinity,d=Infinity,e=-Infinity,f=a.length,g,h,i,j,k;a.forEach(K),g=a[0],g.x=-g.r,g.y=0,l(g);if(f>1){h=a[1],h.x=h.r,h.y=0,l(h);if(f>2){i=a[2],O(g,h,i),l(i),G(g,i),g._pack_prev=i,G(i,h),h=g._pack_next;for(var m=3;m0?(H(g,j),h=j,m--):(H(j,h),g=j,m--)}}}var q=(b+c)/2,r=(d+e)/2,s=0;for(var m=0;m0&&(a=d)}return a}function X(a,b){return a.x-b.x}function Y(a,b){return b.x-a.x}function Z(a,b){return a.depth-b.depth}function $(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function ba(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function bb(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function bc(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function bd(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}d3.layout={},d3.layout.bundle=function(){return function(b){var c=[],d=-1,e=b.length;while(++de&&(e=h),d.push(h)}for(g=0;g=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]));return g}var a=!0,b=Number,c=y,d=w;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=d3.functor(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return x(b,a)}:d3.functor(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function e(f,h,i){var j=b.call(g,f,h),k=E?f:{data:f};k.depth=h,i.push(k);if(j&&(m=j.length)){var l=-1,m,n=k.children=[],o=0,p=h+1;while(++l0&&(ba(bb(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!V(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!U(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];$(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=W(g,Y),l=W(g,X),m=W(g,Z),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return $(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=T,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},z(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++ge&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=j?d.dy:0;while(++fd.dx)j=j?d.dx:0;while(++f=0?a.substring(b):(b=a.length,""),d=[];while(b>0)d.push(a.substring(b-=3,b+3));return d.reverse().join(",")+c}function w(a,b){return{scale:Math.pow(10,(8-b)*3),symbol:a}}function B(a){return function(b){return b<=0?0:b>=1?1:a(b)}}function C(a){return function(b){return 1-a(1-b)}}function D(a){return function(b){return.5*(b<.5?a(2*b):2-a(2-2*b))}}function E(a){return a}function F(a){return function(b){return Math.pow(b,a)}}function G(a){return 1-Math.cos(a*Math.PI/2)}function H(a){return Math.pow(2,10*(a-1))}function I(a){return 1-Math.sqrt(1-a*a)}function J(a,b){var c;return arguments.length<2&&(b=.45),arguments.length<1?(a=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/a),function(d){return 1+a*Math.pow(2,10*-d)*Math.sin((d-c)*2*Math.PI/b)}}function K(a){return a||(a=1.70158),function(b){return b*b*((a+1)*b-a)}}function L(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375}function M(){d3.event.stopPropagation(),d3.event.preventDefault()}function O(a){return a=="transform"?d3.interpolateTransform:d3.interpolate}function P(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return(c-a)*b}}function Q(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return Math.max(0,Math.min(1,(c-a)*b))}}function R(a,b,c){return new S(a,b,c)}function S(a,b,c){this.r=a,this.g=b,this.b=c}function T(a){return a<16?"0"+Math.max(0,a).toString(16):Math.min(255,a).toString(16)}function U(a,b,c){var d=0,e=0,f=0,g,h,i;g=/([a-z]+)\((.*)\)/i.exec(a);if(g){h=g[2].split(",");switch(g[1]){case"hsl":return c(parseFloat(h[0]),parseFloat(h[1])/100,parseFloat(h[2])/100);case"rgb":return b(W(h[0]),W(h[1]),W(h[2]))}}return(i=X[a])?b(i.r,i.g,i.b):(a!=null&&a.charAt(0)==="#"&&(a.length===4?(d=a.charAt(1),d+=d,e=a.charAt(2),e+=e,f=a.charAt(3),f+=f):a.length===7&&(d=a.substring(1,3),e=a.substring(3,5),f=a.substring(5,7)),d=parseInt(d,16),e=parseInt(e,16),f=parseInt(f,16)),b(d,e,f))}function V(a,b,c){var d=Math.min(a/=255,b/=255,c/=255),e=Math.max(a,b,c),f=e-d,g,h,i=(e+d)/2;return f?(h=i<.5?f/(e+d):f/(2-e-d),a==e?g=(b-c)/f+(b360?a-=360:a<0&&(a+=360),a<60?d+(e-d)*a/60:a<180?e:a<240?d+(e-d)*(240-a)/60:d}function g(a){return Math.round(f(a)*255)}var d,e;return a%=360,a<0&&(a+=360),b=b<0?0:b>1?1:b,c=c<0?0:c>1?1:c,e=c<=.5?c*(1+b):c+b-c*b,d=2*c-e,R(g(a+120),g(a),g(a-120))}function ba(a){return h(a,bd),a}function be(a){return function(){return bb(a,this)}}function bf(a){return function(){return bc(a,this)}}function bh(a,b){function f(){if(b=this.classList)return b.add(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;c.lastIndex=0,c.test(e)||(e=m(e+" "+a),d?b.baseVal=e:this.className=e)}function g(){if(b=this.classList)return b.remove(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;e=m(e.replace(c," ")),d?b.baseVal=e:this.className=e}function h(){(b.apply(this,arguments)?f:g).call(this)}var c=new RegExp("(^|\\s+)"+d3.requote(a)+"(\\s+|$)","g");if(arguments.length<2){var d=this.node();if(e=d.classList)return e.contains(a);var e=d.className;return c.lastIndex=0,c.test(e.baseVal!=null?e.baseVal:e)}return this.each(typeof b=="function"?h:b?f:g)}function bi(a){return{__data__:a}}function bj(a){return arguments.length||(a=d3.ascending),function(b,c){return a(b&&b.__data__,c&&c.__data__)}}function bl(a){return h(a,bm),a}function bn(a,b,c){h(a,br);var d={},e=d3.dispatch("start","end"),f=bu;return a.id=b,a.time=c,a.tween=function(b,c){return arguments.length<2?d[b]:(c==null?delete d[b]:d[b]=c,a)},a.ease=function(b){return arguments.length?(f=typeof b=="function"?b:d3.ease.apply(d3,arguments),a):f},a.each=function(b,c){return arguments.length<2?bv.call(a,b):(e.on(b,c),a)},d3.timer(function(g){return a.each(function(h,i,j){function p(a){if(o.active>b)return r();o.active=b;for(var f in d)(f=d[f].call(l,h,i))&&k.push(f);return e.start.call(l,h,i),q(a)||d3.timer(q,0,c),1}function q(a){if(o.active!==b)return r();var c=(a-m)/n,d=f(c),g=k.length;while(g>0)k[--g].call(l,d);if(c>=1)return r(),bt=b,e.end.call(l,h,i),bt=0,1}function r(){return--o.count||delete l.__transition__,1}var k=[],l=this,m=a[j][i].delay,n=a[j][i].duration,o=l.__transition__||(l.__transition__={active:0,count:0});++o.count,m<=g?p(g):d3.timer(p,m,c)}),1},0,c),a}function bp(a,b,c){return c!=""&&bo}function bq(a,b){function d(a,d,e){var f=b.call(this,a,d);return f==null?e!=""&&bo:e!=f&&c(e,f)}function e(a,d,e){return e!=b&&c(e,b)}var c=O(a);return typeof b=="function"?d:b==null?bp:(b+="",e)}function bv(a){for(var b=0,c=this.length;b=c.delay&&(c.flush=c.callback(a)),c=c.next;var d=bA()-b;d>24?(isFinite(d)&&(clearTimeout(by),by=setTimeout(bz,d)),bx=0):(bx=1,bB(bz))}function bA(){var a=null,b=bw,c=Infinity;while(b)b.flush?b=a?a.next=b.next:bw=b.next:(c=Math.min(c,b.then+b.delay),b=(a=b).next);return c}function bC(a){var b=[a.a,a.b],c=[a.c,a.d],d=bE(b),e=bD(b,c),f=bE(bF(c,b,-e));this.translate=[a.e,a.f],this.rotate=Math.atan2(a.b,a.a)*bH,this.scale=[d,f||0],this.skew=f?e/f*bH:0}function bD(a,b){return a[0]*b[0]+a[1]*b[1]}function bE(a){var b=Math.sqrt(bD(a,a));return a[0]/=b,a[1]/=b,b}function bF(a,b,c){return a[0]+=c*b[0],a[1]+=c*b[1],a}function bI(){}function bJ(a){var b=a[0],c=a[a.length-1];return b0;j--)e.push(c(f)*j)}else{for(;fi;g--);e=e.slice(f,g)}return e},d.tickFormat=function(a,e){arguments.length<2&&(e=bV);if(arguments.length<1)return e;var f=a/d.ticks().length,g=b===bX?(h=-1e-15,Math.floor):(h=1e-15,Math.ceil),h;return function(a){return a/c(g(b(a)+h))1){h=b[1],f=a[i],i++,d+="C"+(e[0]+g[0])+","+(e[1]+g[1])+","+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1];for(var j=2;j9&&(f=c*3/Math.sqrt(f),g[h]=f*d,g[h+1]=f*e));h=-1;while(++h<=i)f=(a[Math.min(i,h+1)][0]-a[Math.max(0,h-1)][0])/(6*(1+g[h]*g[h])),b.push([f||0,g[h]*f||0]);return b}function cK(a){return a.length<3?cq(a):a[0]+cw(a,cJ(a))}function cL(a){var b,c=-1,d=a.length,e,f;while(++c1){var d=bJ(a.domain()),e,f=-1,g=b.length,h=(b[1]-b[0])/++c,i,j;while(++f0;)(j=+b[f]-i*h)>=d[0]&&e.push(j);for(--f,i=0;++ib?1:a>=b?0:NaN},d3.descending=function(a,b){return ba?1:b>=a?0:NaN},d3.mean=function(a,b){var c=a.length,d,e=0,f=-1,g=0;if(arguments.length===1)while(++f1&&(a=a.map(b)),a=a.filter(j),a.length?d3.quantile(a.sort(d3.ascending),.5):undefined},d3.min=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++cf&&(e=f)}else{while(++cf&&(e=f)}return e},d3.max=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++ce&&(e=f)}else{while(++ce&&(e=f)}return e},d3.extent=function(a,b){var c=-1,d=a.length,e,f,g;if(arguments.length===1){while(++cf&&(e=f),gf&&(e=f),g1);return a+b*c*Math.sqrt(-2*Math.log(e)/e)}}},d3.sum=function(a,b){var c=0,d=a.length,e,f=-1;if(arguments.length===1)while(++f>1;a[e]>1;b0&&(e=f);return e},d3.last=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c=b.length)return e?e.call(a,c):d?c.sort(d):c;var h=-1,i=c.length,j=b[g++],k,l,m={};while(++h=b.length)return a;var e=[],f=c[d++],h;for(h in a)e.push({key:h,values:g(a[h],d)});return f&&e.sort(function(a,b){return f(a.key,b.key)}),e}var a={},b=[],c=[],d,e;return a.map=function(a){return f(a,0)},a.entries=function(a){return g(f(a,0),0)},a.key=function(c){return b.push(c),a},a.sortKeys=function(d){return c[b.length-1]=d,a},a.sortValues=function(b){return d=b,a},a.rollup=function(b){return e=b,a},a},d3.keys=function(a){var b=[];for(var c in a)b.push(c);return b},d3.values=function(a){var b=[];for(var c in a)b.push(a[c]);return b},d3.entries=function(a){var b=[];for(var c in a)b.push({key:c,value:a[c]});return b},d3.permute=function(a,b){var c=[],d=-1,e=b.length;while(++db)d.push(f);else while((f=a+c*++e)0&&(d=a.substring(c+1),a=a.substring(0,c)),this[a].on(d,b)},d3.format=function(a){var b=q.exec(a),c=b[1]||" ",d=b[3]||"",e=b[5],f=+b[6],g=b[7],h=b[8],i=b[9],j=1,k="",l=!1;h&&(h=+h.substring(1)),e&&(c="0",g&&(f-=Math.floor((f-1)/4)));switch(i){case"n":g=!0,i="g";break;case"%":j=100,k="%",i="f";break;case"p":j=100,k="%",i="r";break;case"d":l=!0,h=0;break;case"s":j=-1,i="r"}return i=="r"&&!h&&(i="g"),i=r[i]||t,function(a){if(l&&a%1)return"";var b=a<0&&(a=-a)?"−":d;if(j<0){var m=d3.formatPrefix(a,h);a*=m.scale,k=m.symbol}else a*=j;a=i(a,h);if(e){var n=a.length+b.length;n=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,r={g:function(a,b){return a.toPrecision(b)},e:function(a,b){return a.toExponential(b)},f:function(a,b){return a.toFixed(b)},r:function(a,b){return d3.round(a,b=s(a,b)).toFixed(Math.max(0,Math.min(20,b)))}},v=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(w);d3.formatPrefix=function(a,b){var c=0;return a&&(a<0&&(a*=-1),b&&(a=d3.round(a,s(a,b))),c=1+Math.floor(1e-12+Math.log(a)/Math.LN10),c=Math.max(-24,Math.min(24,Math.floor((c<=0?c+1:c-1)/3)*3))),v[8+c/3]};var x=F(2),y=F(3),z={linear:function(){return E},poly:F,quad:function(){return x},cubic:function(){return y},sin:function(){return G},exp:function(){return H},circle:function(){return I},elastic:J,back:K,bounce:function(){return L}},A={"in":function(a){return a},out:C,"in-out":D,"out-in":function(a){return D(C(a))}};d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a,d=b>=0?a.substring(b+1):"in";return B(A[d](z[c].apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.interpolate=function(a,b){var c=d3.interpolators.length,d;while(--c>=0&&!(d=d3.interpolators[c](a,b)));return d},d3.interpolateNumber=function(a,b){return b-=a,function(c){return a+b*c}},d3.interpolateRound=function(a,b){return b-=a,function(c){return Math.round(a+b*c)}},d3.interpolateString=function(a,b){var c,d,e,f=0,g=0,h=[],i=[],j,k;N.lastIndex=0;for(d=0;c=N.exec(b);++d)c.index&&h.push(b.substring(f,g=c.index)),i.push({i:h.length,x:c[0]}),h.push(null),f=N.lastIndex;f1){while(++e0&&(a=a.substring(0,e)),arguments.length<2?(e=this.node()[d])&&e._:this.each(function(e,f){function h(a){var c=d3.event;d3.event=a;try{b.call(g,g.__data__,f)}finally{d3.event=c}}var g=this;g[d]&&g.removeEventListener(a,g[d],c),b&&g.addEventListener(a,g[d]=h,c),h._=b})},bd.each=function(a){for(var b=-1,c=this.length;++b=cg?e?"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":e?"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L"+e*m+","+e*n+"A"+e+","+e+" 0 "+j+",0 "+e*k+","+e*l+"Z":"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L0,0"+"Z"}var a=ch,b=ci,c=cj,d=ck;return e.innerRadius=function(b){return arguments.length?(a=d3.functor(b),e):a},e.outerRadius=function(a){return arguments.length?(b=d3.functor(a),e):b},e.startAngle=function(a){return arguments.length?(c=d3.functor(a),e):c},e.endAngle=function(a){return arguments.length?(d=d3.functor(a),e):d},e.centroid=function(){var e=(a.apply(this,arguments)+b.apply(this,arguments))/2,f=(c.apply(this,arguments)+d.apply(this,arguments))/2+cf;return[Math.cos(f)*e,Math.sin(f)*e]},e};var cf=-Math.PI/2,cg=2*Math.PI-1e-6;d3.svg.line=function(){return cl(Object)};var cp={linear:cq,"step-before":cr,"step-after":cs,basis:cy,"basis-open":cz,"basis-closed":cA,bundle:cB,cardinal:cv,"cardinal-open":ct,"cardinal-closed":cu,monotone:cK},cD=[0,2/3,1/3,0],cE=[0,1/3,2/3,0],cF=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var a=cl(cL);return a.radius=a.x,delete a.x,a.angle=a.y,delete a.y,a},cr.reverse=cs,cs.reverse=cr,d3.svg.area=function(){return cM(Object)},d3.svg.area.radial=function(){var a=cM(cL);return a.radius=a.x,delete a.x,a.innerRadius=a.x0,delete a.x0,a.outerRadius=a.x1,delete a.x1,a.angle=a.y,delete a.y,a.startAngle=a.y0,delete a.y0,a.endAngle=a.y1,delete a.y1,a},d3.svg.chord=function(){function f(c,d){var e=g(this,a,c,d),f=g(this,b,c,d);return"M"+e.p0+i(e.r,e.p1)+(h(e,f)?j(e.r,e.p1,e.r,e.p0):j(e.r,e.p1,f.r,f.p0)+i(f.r,f.p1)+j(f.r,f.p1,e.r,e.p0))+"Z"}function g(a,b,f,g){var h=b.call(a,f,g),i=c.call(a,h,g),j=d.call(a,h,g)+cf,k=e.call(a,h,g)+cf;return{r:i,a0:j,a1:k,p0:[i*Math.cos(j),i*Math.sin(j)],p1:[i*Math.cos(k),i*Math.sin(k)]}}function h(a,b){return a.a0==b.a0&&a.a1==b.a1}function i(a,b){return"A"+a+","+a+" 0 0,1 "+b}function j(a,b,c,d){return"Q 0,0 "+d}var a=cP,b=cQ,c=cR,d=cj,e=ck;return f.radius=function(a){return arguments.length?(c=d3.functor(a),f):c},f.source=function(b){return arguments.length?(a=d3.functor(b),f):a},f.target=function(a){return arguments.length?(b=d3.functor(a),f):b},f.startAngle=function(a){return arguments.length?(d=d3.functor(a),f):d},f.endAngle=function(a){return arguments.length?(e=d3.functor(a),f):e},f},d3.svg.diagonal=function(){function d(d,e){var f=a.call(this,d,e),g=b.call(this,d,e),h=(f.y+g.y)/2,i=[f,{x:f.x,y:h},{x:g.x,y:h},g];return i=i.map(c),"M"+i[0]+"C"+i[1]+" "+i[2]+" "+i[3]}var a=cP,b=cQ,c=cU;return d.source=function(b){return arguments.length?(a=d3.functor(b),d):a},d.target=function(a){return arguments.length?(b=d3.functor(a),d):b},d.projection=function(a){return arguments.length?(c=a,d):c},d},d3.svg.diagonal.radial=function(){var a=d3.svg.diagonal(),b=cU,c=a.projection;return a.projection=function(a){return arguments.length?c(cV(b=a)):b},a},d3.svg.mouse=function(a){return cX(a,d3.event)};var cW=/WebKit/.test(navigator.userAgent)?-1:0;d3.svg.touches=function(a,b){return arguments.length<2&&(b=d3.event.touches),b?d(b).map(function(b){var c=cX(a,b);return c.identifier=b.identifier,c}):[]},d3.svg.symbol=function(){function c(c,d){return(c$[a.call(this,c,d)]||c$.circle)(b.call(this,c,d))}var a=cZ,b=cY;return c.type=function(b){return arguments.length?(a=d3.functor(b),c):a},c.size=function(a){return arguments.length?(b=d3.functor(a),c):b},c};var c$={circle:function(a){var b=Math.sqrt(a/Math.PI);return"M0,"+b+"A"+b+","+b+" 0 1,1 0,"+ -b+"A"+b+","+b+" 0 1,1 0,"+b+"Z"},cross:function(a){var b=Math.sqrt(a/5)/2;return"M"+ -3*b+","+ -b+"H"+ -b+"V"+ -3*b+"H"+b+"V"+ -b+"H"+3*b+"V"+b+"H"+b+"V"+3*b+"H"+ -b+"V"+b+"H"+ -3*b+"Z"},diamond:function(a){var b=Math.sqrt(a/(2*da)),c=b*da;return"M0,"+ -b+"L"+c+",0"+" 0,"+b+" "+ -c+",0"+"Z"},square:function(a){var b=Math.sqrt(a)/2;return"M"+ -b+","+ -b+"L"+b+","+ -b+" "+b+","+b+" "+ -b+","+b+"Z"},"triangle-down":function(a){var b=Math.sqrt(a/c_),c=b*c_/2;return"M0,"+c+"L"+b+","+ -c+" "+ -b+","+ -c+"Z"},"triangle-up":function(a){var b=Math.sqrt(a/c_),c=b*c_/2;return"M0,"+ -c+"L"+b+","+c+" "+ -b+","+c+"Z"}};d3.svg.symbolTypes=d3.keys(c$);var c_=Math.sqrt(3),da=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function j(j){j.each(function(k,l,m){var n=d3.select(this),o=j.delay?function(a){var b=bt;try{return bt=j.id,a.transition().delay(j[m][l].delay).duration(j[m][l].duration).ease(j.ease())}finally{bt=b}}:Object,p=a.ticks.apply(a,g),q=h==null?a.tickFormat.apply(a,g):h,r=dd(a,p,i),s=n.selectAll(".minor").data(r,String),t=s.enter().insert("svg:line","g").attr("class","tick minor").style("opacity",1e-6),u=o(s.exit()).style("opacity",1e-6).remove(),v=o(s).style("opacity",1),w=n.selectAll("g").data(p,String),x=w.enter().insert("svg:g","path").style("opacity",1e-6),y=o(w.exit()).style("opacity",1e-6).remove(),z=o(w).style("opacity",1),A,B=bJ(a.range()),C=n.selectAll(".domain").data([0]),D=C.enter().append("svg:path").attr("class","domain"),E=o(C),F=this.__chart__||a;this.__chart__=a.copy(),x.append("svg:line").attr("class","tick"),x.append("svg:text"),z.select("text").text(q);switch(b){case"bottom":A=db,v.attr("x2",0).attr("y2",d),z.select("line").attr("x2",0).attr("y2",c),z.select("text").attr("x",0).attr("y",Math.max(c,0)+f).attr("dy",".71em").attr("text-anchor","middle"),E.attr("d","M"+B[0]+","+e+"V0H"+B[1]+"V"+e);break;case"top":A=db,v.attr("x2",0).attr("y2",-d),z.select("line").attr("x2",0).attr("y2",-c),z.select("text").attr("x",0).attr("y",-(Math.max(c,0)+f)).attr("dy","0em").attr("text-anchor","middle"),E.attr("d","M"+B[0]+","+ -e+"V0H"+B[1]+"V"+ -e);break;case"left":A=dc,v.attr("x2",-d).attr("y2",0),z.select("line").attr("x2",-c).attr("y2",0),z.select("text").attr("x",-(Math.max(c,0)+f)).attr("y",0).attr("dy",".32em").attr("text-anchor","end"),E.attr("d","M"+ -e+","+B[0]+"H0V"+B[1]+"H"+ -e);break;case"right":A=dc,v.attr("x2",d).attr("y2",0),z.select("line").attr("x2",c).attr("y2",0),z.select("text").attr("x",Math.max(c,0)+f).attr("y",0).attr("dy",".32em").attr("text-anchor","start"),E.attr("d","M"+e+","+B[0]+"H0V"+B[1]+"H"+e)}x.call(A,F),z.call(A,a),y.call(A,a),t.call(A,F),v.call(A,a),u.call(A,a)})}var a=d3.scale.linear(),b="bottom",c=6,d=6,e=6,f=3,g=[10],h,i=0;return j.scale=function(b){return arguments.length?(a=b,j):a},j.orient=function(a){return arguments.length?(b=a,j):b},j.ticks=function(){return arguments.length?(g=arguments,j):g},j.tickFormat=function(a){return arguments.length?(h=a,j):h},j.tickSize=function(a,b,f){if(!arguments.length)return c;var g=arguments.length-1;return c=+a,d=g>1?+b:c,e=g>0?+arguments[g]:c,j},j.tickPadding=function(a){return arguments.length?(f=+a,j):f},j.tickSubdivide=function(a){return arguments.length?(i=+a,j):i},j},d3.svg.brush=function(){function e(a){var g=b&&c?["n","e","s","w","nw","ne","se","sw"]:b?["e","w"]:c?["n","s"]:[];a.each(function(){var a=d3.select(this).on("mousedown.brush",f),h=a.selectAll(".background").data([,]),i=a.selectAll(".extent").data([,]),j=a.selectAll(".resize").data(g,String),k;h.enter().append("svg:rect").attr("class","background").style("visibility","hidden").style("pointer-events","all").style("cursor","crosshair"),i.enter().append("svg:rect").attr("class","extent").style("cursor","move"),j.enter().append("svg:rect").attr("class",function(a){return"resize "+a}).attr("width",6).attr("height",6).style("visibility","hidden").style("pointer-events",e.empty()?"none":"all").style("cursor",function(a){return dw[a]}),j.exit().remove(),b&&(k=bJ(b.range()),h.attr("x",k[0]).attr("width",k[1]-k[0]),dp(a,d)),c&&(k=bJ(c.range()),h.attr("y",k[0]).attr("height",k[1]-k[0]),dq(a,d))})}function f(){var a=d3.select(d3.event.target);de=e,dg=this,dj=d,dn=d3.svg.mouse(dg),(dk=a.classed("extent"))?(dn[0]=d[0][0]-dn[0],dn[1]=d[0][1]-dn[1]):a.classed("resize")?(dl=d3.event.target.__data__,dn[0]=d[+/w$/.test(dl)][0],dn[1]=d[+/^n/.test(dl)][1]):d3.event.altKey&&(dm=dn.slice()),dh=!/^(n|s)$/.test(dl)&&b,di=!/^(e|w)$/.test(dl)&&c,df=g(this,arguments),df("brushstart"),dt(),M()}function g(b,c){return function(d){var f=d3.event;try{d3.event={type:d,target:e},a[d].apply(b,c)}finally{d3.event=f}}}var a=d3.dispatch("brushstart","brush","brushend"),b,c,d=[[0,0],[0,0]];return e.x=function(a){return arguments.length?(b=a,e):b},e.y=function(a){return arguments.length?(c=a,e):c},e.extent=function(a){var f,g,h,i,j;return arguments.length?(b&&(f=a[0],g=a[1],c&&(f=f[0],g=g[0]),f=b(f),g=b(g),ga?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("