From 9b26253c4a65c363d07680c5041e002286f58aca Mon Sep 17 00:00:00 2001 From: dsanz Date: Wed, 8 Mar 2017 15:44:53 +0100 Subject: [PATCH 01/80] Add rabbitMQ container --- docker-compose.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index f3baeb346..788cfc264 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,3 +61,8 @@ services: image: redis ports: - "6379:6379" + + rabbitmq: + image: rabbitmq + ports: + - "5672:5672" From a3fd171712f42d8209c8e5007fa84bbf4b0689ae Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 8 Mar 2017 16:56:26 -0500 Subject: [PATCH 02/80] inject dependencies into the WebForms application --- .../Catalog.WebForms/Catalog.WebForms.csproj | 1 + .../Catalog.WebForms/Default.aspx.cs | 18 ++-- .../Catalog.WebForms/Global.asax.cs | 24 +----- .../Modules/AutoFacHttpModule.cs | 84 +++++++++++++++++++ .../Catalog.WebForms/Web.config | 4 + 5 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 src/Web/Catalog.WebForms/Catalog.WebForms/Modules/AutoFacHttpModule.cs diff --git a/src/Web/Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj b/src/Web/Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj index 5661b2fdb..06bd309ca 100644 --- a/src/Web/Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj +++ b/src/Web/Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj @@ -221,6 +221,7 @@ + diff --git a/src/Web/Catalog.WebForms/Catalog.WebForms/Default.aspx.cs b/src/Web/Catalog.WebForms/Catalog.WebForms/Default.aspx.cs index bd222c5af..ee4efea3b 100644 --- a/src/Web/Catalog.WebForms/Catalog.WebForms/Default.aspx.cs +++ b/src/Web/Catalog.WebForms/Catalog.WebForms/Default.aspx.cs @@ -18,6 +18,13 @@ namespace Microsoft.eShopOnContainers.Catalog.WebForms private ICatalogService catalog; + protected _Default() { } + + public _Default(ICatalogService catalog) + { + this.catalog = catalog; + } + protected override void OnLoad(EventArgs e) { RegisterAsyncTask(new PageAsyncTask(LoadCatalogDataAsync)); @@ -27,14 +34,9 @@ namespace Microsoft.eShopOnContainers.Catalog.WebForms private async Task LoadCatalogDataAsync() { - var container = Application.Get("container") as IContainer; - using (scope = container?.BeginLifetimeScope()) - { - catalog = container?.Resolve(); - var collection = await catalog?.GetCatalogAsync(); - catalogList.DataSource = collection; - catalogList.DataBind(); - } + var collection = await catalog?.GetCatalogAsync(); + catalogList.DataSource = collection; + catalogList.DataBind(); } protected void Page_Load(object sender, EventArgs e) diff --git a/src/Web/Catalog.WebForms/Catalog.WebForms/Global.asax.cs b/src/Web/Catalog.WebForms/Catalog.WebForms/Global.asax.cs index 48e1d8dbc..f0e204417 100644 --- a/src/Web/Catalog.WebForms/Catalog.WebForms/Global.asax.cs +++ b/src/Web/Catalog.WebForms/Catalog.WebForms/Global.asax.cs @@ -1,14 +1,7 @@ -using Autofac; -using eShopOnContainers.Core.Services.Catalog; -using System; -using System.Collections.Generic; -using System.Linq; +using System; using System.Web; -using System.Web.Configuration; using System.Web.Optimization; using System.Web.Routing; -using System.Web.Security; -using System.Web.SessionState; namespace Microsoft.eShopOnContainers.Catalog.WebForms { @@ -21,21 +14,6 @@ namespace Microsoft.eShopOnContainers.Catalog.WebForms RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); - // Register Containers: - var settings= WebConfigurationManager.AppSettings; - var useFake = settings["usefake"]; - bool fake = useFake == "true"; - var builder = new ContainerBuilder(); - if (fake) - { - builder.RegisterType() - .As(); - } else { - builder.RegisterType() - .As(); - } - var container = builder.Build(); - Application.Add("container", container); } } } \ No newline at end of file diff --git a/src/Web/Catalog.WebForms/Catalog.WebForms/Modules/AutoFacHttpModule.cs b/src/Web/Catalog.WebForms/Catalog.WebForms/Modules/AutoFacHttpModule.cs new file mode 100644 index 000000000..d0d7a60e9 --- /dev/null +++ b/src/Web/Catalog.WebForms/Catalog.WebForms/Modules/AutoFacHttpModule.cs @@ -0,0 +1,84 @@ +using Autofac; +using eShopOnContainers.Core.Services.Catalog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Configuration; +using System.Web.UI; + +namespace Microsoft.eShopOnContainers.Catalog.WebForms.Modules +{ + // Using DI with WebForms is not yet implemented. + // This implementation has been adapted from this post: + // https://blogs.msdn.microsoft.com/webdev/2016/10/19/modern-asp-net-web-forms-development-dependency-injection/ + + public class AutoFacHttpModule : IHttpModule + { + private static IContainer Container => lazyContainer.Value; + + private static Lazy lazyContainer = new Lazy(() => CreateContainer()); + + private static IContainer CreateContainer() + { + // Configure AutoFac: + // Register Containers: + var settings = WebConfigurationManager.AppSettings; + var useFake = settings["usefake"]; + bool fake = useFake == "true"; + var builder = new ContainerBuilder(); + if (fake) + { + builder.RegisterType() + .As(); + } + else + { + builder.RegisterType() + .As(); + } + var container = builder.Build(); + return container; + } + + public void Dispose() + { + Container.Dispose(); + } + + public void Init(HttpApplication context) + { + context.PreRequestHandlerExecute += (_, __) => InjectDependencies(); + } + + private void InjectDependencies() + { + if (HttpContext.Current.CurrentHandler is Page) + { + var page = HttpContext.Current.CurrentHandler as Page; + // Get the code-behind class that we may have written + var pageType = page.GetType().BaseType; + + // Determine if there is a constructor to inject, and grab it + var ctor = (from c in pageType.GetConstructors() + where c.GetParameters().Length > 0 + select c).FirstOrDefault(); + + if (ctor != null) + { + // Resolve the parameters for the constructor + var args = (from parm in ctor.GetParameters() + select Container.Resolve(parm.ParameterType)) + .ToArray(); + + // Execute the constructor method with the arguments resolved + ctor.Invoke(page, args); + } + + // Use the Autofac method to inject any properties that can be filled by Autofac + Container.InjectProperties(page); + + } + } + } +} \ No newline at end of file diff --git a/src/Web/Catalog.WebForms/Catalog.WebForms/Web.config b/src/Web/Catalog.WebForms/Catalog.WebForms/Web.config index 9da53af59..47ad4e633 100644 --- a/src/Web/Catalog.WebForms/Catalog.WebForms/Web.config +++ b/src/Web/Catalog.WebForms/Catalog.WebForms/Web.config @@ -20,6 +20,8 @@ + + @@ -39,6 +41,8 @@ + + From 18a402044ed0f779681d5a7ddca54b2c43035f39 Mon Sep 17 00:00:00 2001 From: dsanz Date: Thu, 9 Mar 2017 15:56:34 +0100 Subject: [PATCH 03/80] Add basic first implementation of Event bus with rabbitMQ --- docker-compose.yml | 2 + eShopOnContainers-ServicesAndWebApps.sln | 54 ++++++++++ .../Catalog/Catalog.API/Catalog.API.csproj | 4 + .../Controllers/CatalogController.cs | 16 ++- src/Services/Catalog/Catalog.API/Startup.cs | 3 + .../Catalog/CatalogPriceChanged.cs | 20 ++++ .../Catalog/CatalogPriceChangedHandler.cs | 14 +++ .../Common/Infrastructure/EventBus.cs | 101 ++++++++++++++++++ .../Common/Infrastructure/IEventBus.cs | 13 +++ .../Infrastructure/IIntegrationEvent.cs | 11 ++ .../IIntegrationEventHandler.cs | 14 +++ .../Infrastructure/Infrastructure.csproj | 10 ++ 12 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs create mode 100644 src/Services/Common/Infrastructure/Catalog/CatalogPriceChangedHandler.cs create mode 100644 src/Services/Common/Infrastructure/EventBus.cs create mode 100644 src/Services/Common/Infrastructure/IEventBus.cs create mode 100644 src/Services/Common/Infrastructure/IIntegrationEvent.cs create mode 100644 src/Services/Common/Infrastructure/IIntegrationEventHandler.cs create mode 100644 src/Services/Common/Infrastructure/Infrastructure.csproj diff --git a/docker-compose.yml b/docker-compose.yml index 788cfc264..09380e299 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: depends_on: - basket.data - identity.api + - rabbitmq catalog.api: image: eshop/catalog.api @@ -17,6 +18,7 @@ services: dockerfile: Dockerfile depends_on: - sql.data + - rabbitmq identity.api: image: eshop/identity.api diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index c92dc8c7d..539aec7b0 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -48,6 +48,10 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSPA", "src\Web\WebSPA\WebSPA.csproj", "{F16E3C6A-1C94-4EAB-BE91-099618060B68}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{47857844-D05A-4C37-BFB2-AF19B7EC418D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Services\Common\Infrastructure\Infrastructure.csproj", "{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -494,6 +498,54 @@ Global {F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x64.Build.0 = Release|Any CPU {F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x86.ActiveCfg = Release|Any CPU {F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x86.Build.0 = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|ARM.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhone.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x64.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x64.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x86.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x86.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|ARM.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhone.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x64.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x64.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x86.ActiveCfg = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x86.Build.0 = Debug|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|Any CPU.Build.0 = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|ARM.ActiveCfg = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|ARM.Build.0 = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhone.ActiveCfg = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhone.Build.0 = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x64.ActiveCfg = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x64.Build.0 = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x86.ActiveCfg = Release|Any CPU + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -516,5 +568,7 @@ Global {7796F5D8-31FC-45A4-B673-19DE5BA194CF} = {EF0337F2-ED00-4643-89FD-EE10863F1870} {A579E108-5445-403D-A407-339AC4D1611B} = {24CD3B53-141E-4A07-9B0D-796641E1CF78} {F16E3C6A-1C94-4EAB-BE91-099618060B68} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} + {47857844-D05A-4C37-BFB2-AF19B7EC418D} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0} = {47857844-D05A-4C37-BFB2-AF19B7EC418D} EndGlobalSection EndGlobal diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 6811e43cb..1c0fddf92 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -52,6 +52,10 @@ + + + + Always diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 547cdfe21..bba7564e9 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -3,6 +3,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.Model; using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; using Microsoft.Extensions.Options; using System.Collections.Generic; using System.Linq; @@ -15,11 +17,13 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers { private readonly CatalogContext _context; private readonly IOptionsSnapshot _settings; + private readonly IEventBus _eventBus; - public CatalogController(CatalogContext context, IOptionsSnapshot settings) + public CatalogController(CatalogContext context, IOptionsSnapshot settings, IEventBus eventBus) { _context = context; _settings = settings; + _eventBus = eventBus; ((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } @@ -41,7 +45,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers itemsOnPage = ComposePicUri(itemsOnPage); var model = new PaginatedItemsViewModel( - pageIndex, pageSize, totalItems, itemsOnPage); + pageIndex, pageSize, totalItems, itemsOnPage); return Ok(model); } @@ -99,7 +103,13 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers var model = new PaginatedItemsViewModel( pageIndex, pageSize, totalItems, itemsOnPage); - + + //hook to run integration tests until POST methods are created + if (catalogTypeId.HasValue && catalogTypeId == 1) + { + _eventBus.Publish(new CatalogPriceChanged()); + } + return Ok(model); } diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index fb6cc3907..c8ecf72fd 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; + using Microsoft.eShopOnContainers.Services.Common.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -73,6 +74,8 @@ .AllowCredentials()); }); + services.AddSingleton(); + services.AddMvc(); } diff --git a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs new file mode 100644 index 000000000..ad22ae49f --- /dev/null +++ b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog +{ + public class CatalogPriceChanged : IIntegrationEvent + { + private readonly string _eventName = "catalogpricechanged"; + + public string Name { + get + { + return _eventName; + } + } + + public string Message { get { return "CatalogPriceChanged!!"; } } + } +} diff --git a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChangedHandler.cs b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChangedHandler.cs new file mode 100644 index 000000000..8fc045ba0 --- /dev/null +++ b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChangedHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog +{ + public class CatalogPriceChangedHandler : IIntegrationEventHandler + { + public void Handle(CatalogPriceChanged @event) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Services/Common/Infrastructure/EventBus.cs b/src/Services/Common/Infrastructure/EventBus.cs new file mode 100644 index 000000000..42ef384f1 --- /dev/null +++ b/src/Services/Common/Infrastructure/EventBus.cs @@ -0,0 +1,101 @@ + +using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +{ + public class EventBus : IEventBus + { + private readonly Dictionary> _handlers; + private readonly Dictionary> _listeners; + + public EventBus() + { + _handlers = new Dictionary>(); + _listeners = new Dictionary>(); + } + public void Publish(IIntegrationEvent @event) + { + var factory = new ConnectionFactory() { HostName = "172.20.0.1" }; + using (var connection = factory.CreateConnection()) + using (var channel = connection.CreateModel()) + { + channel.QueueDeclare(queue: @event.Name, + durable: false, + exclusive: false, + autoDelete: false, + arguments: null); + + string message = ((CatalogPriceChanged)@event).Message; + var body = Encoding.UTF8.GetBytes(message); + + channel.BasicPublish(exchange: "", + routingKey: @event.Name, + basicProperties: null, + body: body); + } + + } + + public void Subscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent + { + var eventName = typeof(T).Name; + if (_handlers.ContainsKey(eventName)) + { + _handlers[eventName].Add(handler); + } + else + { + var factory = new ConnectionFactory() { HostName = "172.18.0.1" }; + var connection = factory.CreateConnection(); + var channel = connection.CreateModel(); + + channel.QueueDeclare(queue: eventName, + durable: false, + exclusive: false, + autoDelete: false, + arguments: null); + + var consumer = new EventingBasicConsumer(channel); + consumer.Received += (model, ea) => + { + var body = ea.Body; + var message = Encoding.UTF8.GetString(body); + }; + channel.BasicConsume(queue: "hello", + noAck: true, + consumer: consumer); + ; + + _listeners.Add(eventName, new Tuple(channel, connection)); + _handlers.Add(eventName, new List()); + _handlers[eventName].Add(handler); + } + + } + + public void Unsubscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent + { + var eventName = typeof(T).Name; + if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler)) + { + _handlers[eventName].Remove(handler); + + if (_handlers[eventName].Count == 0) + { + _handlers.Remove(eventName); + + var connectionItems =_listeners[eventName]; + _listeners.Remove(eventName); + + connectionItems.Item1.Close(); + connectionItems.Item2.Close(); + } + } + } + } +} diff --git a/src/Services/Common/Infrastructure/IEventBus.cs b/src/Services/Common/Infrastructure/IEventBus.cs new file mode 100644 index 000000000..5751f1b81 --- /dev/null +++ b/src/Services/Common/Infrastructure/IEventBus.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +{ + public interface IEventBus + { + void Subscribe(IIntegrationEventHandler handler) where T: IIntegrationEvent; + void Unsubscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent; + void Publish(IIntegrationEvent @event); + } +} diff --git a/src/Services/Common/Infrastructure/IIntegrationEvent.cs b/src/Services/Common/Infrastructure/IIntegrationEvent.cs new file mode 100644 index 000000000..9e92db3a4 --- /dev/null +++ b/src/Services/Common/Infrastructure/IIntegrationEvent.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +{ + public interface IIntegrationEvent + { + string Name { get; } + } +} diff --git a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs new file mode 100644 index 000000000..b2d902ca2 --- /dev/null +++ b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +{ + public interface IIntegrationEventHandler : IIntegrationEventHandler + where TIntegrationEvent: IIntegrationEvent + { + void Handle(TIntegrationEvent @event); + } + + public interface IIntegrationEventHandler { } +} diff --git a/src/Services/Common/Infrastructure/Infrastructure.csproj b/src/Services/Common/Infrastructure/Infrastructure.csproj new file mode 100644 index 000000000..fc7038ce9 --- /dev/null +++ b/src/Services/Common/Infrastructure/Infrastructure.csproj @@ -0,0 +1,10 @@ + + + netcoreapp1.1 + 1.1.0 + Microsoft.eShopOnContainers.Services.Common.Infrastructure + + + + + \ No newline at end of file From 74fbbecf5821b9c562513a464afc8579a6e39ed5 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 9 Mar 2017 16:38:02 -0500 Subject: [PATCH 04/80] Update Compiler package This is necessary for C# 7 feature support. See the pattern match is statement in AutoFacHttpModule --- .../Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj | 4 ++-- src/Web/Catalog.WebForms/Catalog.WebForms/Default.aspx.cs | 2 -- .../Catalog.WebForms/Modules/AutoFacHttpModule.cs | 3 +-- src/Web/Catalog.WebForms/Catalog.WebForms/Web.config | 4 ++-- src/Web/Catalog.WebForms/Catalog.WebForms/packages.config | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Web/Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj b/src/Web/Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj index 06bd309ca..391983c31 100644 --- a/src/Web/Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj +++ b/src/Web/Catalog.WebForms/Catalog.WebForms/Catalog.WebForms.csproj @@ -1,7 +1,7 @@  + - Debug @@ -292,8 +292,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + @@ -21,7 +21,7 @@ - + diff --git a/src/Web/Catalog.WebForms/Catalog.WebForms/packages.config b/src/Web/Catalog.WebForms/Catalog.WebForms/packages.config index dd268d3f3..756f567ce 100644 --- a/src/Web/Catalog.WebForms/Catalog.WebForms/packages.config +++ b/src/Web/Catalog.WebForms/Catalog.WebForms/packages.config @@ -20,7 +20,7 @@ - + From 5b38a49f110b320ada36f305fd2df72ac924e4cc Mon Sep 17 00:00:00 2001 From: dsanz Date: Fri, 10 Mar 2017 13:17:16 +0100 Subject: [PATCH 05/80] Add Basket handler and subscription to events. Change in EventBus to use broker and one message queue per microservice http://www.rabbitmq.com/tutorials/tutorial-four-dotnet.html --- .../Basket/Basket.API/Basket.API.csproj | 4 + .../Events/CatalogPriceChangedHandler.cs | 25 ++++ src/Services/Basket/Basket.API/Startup.cs | 6 + .../Controllers/CatalogController.cs | 2 +- .../Catalog/CatalogPriceChanged.cs | 19 +-- .../Catalog/CatalogPriceChangedHandler.cs | 14 -- .../Common/Infrastructure/EventBus.cs | 125 ++++++++++++------ .../Infrastructure/IIntegrationEvent.cs | 3 +- .../IIntegrationEventHandler.cs | 4 +- .../Infrastructure/Infrastructure.csproj | 1 + 10 files changed, 137 insertions(+), 66 deletions(-) create mode 100644 src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs delete mode 100644 src/Services/Common/Infrastructure/Catalog/CatalogPriceChangedHandler.cs diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index fc040b3b0..1017dbd05 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -39,6 +39,10 @@ + + + + Always diff --git a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs new file mode 100644 index 000000000..67fc6616f --- /dev/null +++ b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs @@ -0,0 +1,25 @@ +using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Basket.API.Events +{ + public class CatalogPriceChangedHandler : IIntegrationEventHandler + { + private readonly IBasketRepository _repository; + public CatalogPriceChangedHandler() + { + //_repository = repository; + } + + public void Handle(CatalogPriceChanged @event) + { + + } + } +} + diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index c72d68533..2eb6a36c1 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -13,6 +13,9 @@ using Microsoft.Extensions.Options; using System.Net; using Swashbuckle.Swagger.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; +using Basket.API.Events; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -76,6 +79,9 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API }); services.AddTransient(); + var eventBus = new EventBus(); + services.AddSingleton(eventBus); + eventBus.Subscribe(new CatalogPriceChangedHandler()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index bba7564e9..3558c55f5 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -107,7 +107,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers //hook to run integration tests until POST methods are created if (catalogTypeId.HasValue && catalogTypeId == 1) { - _eventBus.Publish(new CatalogPriceChanged()); + _eventBus.Publish(new CatalogPriceChanged(2, 10.4M)); } return Ok(model); diff --git a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs index ad22ae49f..6e5715925 100644 --- a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs +++ b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs @@ -6,15 +6,16 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog { public class CatalogPriceChanged : IIntegrationEvent { - private readonly string _eventName = "catalogpricechanged"; + public string Message { get { return "CatalogPriceChanged here!!"; } } - public string Name { - get - { - return _eventName; - } + public int ItemId { get; private set; } + + public decimal NewPrice { get; private set; } + + public CatalogPriceChanged(int itemId, decimal newPrice) + { + ItemId = itemId; + NewPrice = newPrice; } - - public string Message { get { return "CatalogPriceChanged!!"; } } - } +} } diff --git a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChangedHandler.cs b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChangedHandler.cs deleted file mode 100644 index 8fc045ba0..000000000 --- a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChangedHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog -{ - public class CatalogPriceChangedHandler : IIntegrationEventHandler - { - public void Handle(CatalogPriceChanged @event) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Services/Common/Infrastructure/EventBus.cs b/src/Services/Common/Infrastructure/EventBus.cs index 42ef384f1..a606a6d33 100644 --- a/src/Services/Common/Infrastructure/EventBus.cs +++ b/src/Services/Common/Infrastructure/EventBus.cs @@ -1,40 +1,47 @@  using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; +using Newtonsoft.Json; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public class EventBus : IEventBus { - private readonly Dictionary> _handlers; - private readonly Dictionary> _listeners; + private readonly string _brokerName = "event_bus"; + private readonly Dictionary> _handlers; + private readonly List _eventTypes; + + private Tuple _connection; + private string _queueName; + public EventBus() { _handlers = new Dictionary>(); - _listeners = new Dictionary>(); + _eventTypes = new List(); } public void Publish(IIntegrationEvent @event) { + var eventName = @event.GetType().Name; var factory = new ConnectionFactory() { HostName = "172.20.0.1" }; using (var connection = factory.CreateConnection()) using (var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: @event.Name, - durable: false, - exclusive: false, - autoDelete: false, - arguments: null); + { + channel.ExchangeDeclare(exchange: _brokerName, + type: "direct"); - string message = ((CatalogPriceChanged)@event).Message; + string message = JsonConvert.SerializeObject(@event); var body = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "", - routingKey: @event.Name, + channel.BasicPublish(exchange: _brokerName, + routingKey: eventName, basicProperties: null, body: body); } @@ -50,30 +57,14 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } else { - var factory = new ConnectionFactory() { HostName = "172.18.0.1" }; - var connection = factory.CreateConnection(); - var channel = connection.CreateModel(); - - channel.QueueDeclare(queue: eventName, - durable: false, - exclusive: false, - autoDelete: false, - arguments: null); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - }; - channel.BasicConsume(queue: "hello", - noAck: true, - consumer: consumer); - ; - - _listeners.Add(eventName, new Tuple(channel, connection)); + var channel = GetChannel(); + channel.QueueBind(queue: _queueName, + exchange: _brokerName, + routingKey: eventName); + _handlers.Add(eventName, new List()); _handlers[eventName].Add(handler); + _eventTypes.Add(typeof(T)); } } @@ -88,14 +79,70 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure if (_handlers[eventName].Count == 0) { _handlers.Remove(eventName); + var eventType = _eventTypes.Single(e => e.Name == eventName); + _eventTypes.Remove(eventType); + _connection.Item1.QueueUnbind(queue: _queueName, + exchange: _brokerName, + routingKey: eventName); - var connectionItems =_listeners[eventName]; - _listeners.Remove(eventName); - - connectionItems.Item1.Close(); - connectionItems.Item2.Close(); + if (_handlers.Keys.Count == 0) + { + _queueName = string.Empty; + _connection.Item1.Close(); + _connection.Item2.Close(); + } + } } } + + private IModel GetChannel() + { + if (_connection != null) + { + return _connection.Item1; + } + else + { + var factory = new ConnectionFactory() { HostName = "172.20.0.1" }; + var connection = factory.CreateConnection(); + var channel = connection.CreateModel(); + + channel.ExchangeDeclare(exchange: _brokerName, + type: "direct"); + if (string.IsNullOrEmpty(_queueName)) + { + _queueName = channel.QueueDeclare().QueueName; + } + + var consumer = new EventingBasicConsumer(channel); + consumer.Received += (model, ea) => + { + var eventName = ea.RoutingKey; + if (_handlers.ContainsKey(eventName)) + { + var message = Encoding.UTF8.GetString(ea.Body); + Type eventType = _eventTypes.Single(t => t.Name == eventName); + + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); + var handlers = _handlers[eventName]; + + + var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); + + foreach (var handler in handlers) + { + concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); + } + } + }; + channel.BasicConsume(queue: _queueName, + noAck: true, + consumer: consumer); + _connection = new Tuple(channel, connection); + + return _connection.Item1; + } + } } } diff --git a/src/Services/Common/Infrastructure/IIntegrationEvent.cs b/src/Services/Common/Infrastructure/IIntegrationEvent.cs index 9e92db3a4..48d991283 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEvent.cs +++ b/src/Services/Common/Infrastructure/IIntegrationEvent.cs @@ -5,7 +5,6 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public interface IIntegrationEvent - { - string Name { get; } + { } } diff --git a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs index b2d902ca2..49d194ecb 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs +++ b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs @@ -10,5 +10,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure void Handle(TIntegrationEvent @event); } - public interface IIntegrationEventHandler { } + public interface IIntegrationEventHandler + { + } } diff --git a/src/Services/Common/Infrastructure/Infrastructure.csproj b/src/Services/Common/Infrastructure/Infrastructure.csproj index fc7038ce9..34f8bc5d5 100644 --- a/src/Services/Common/Infrastructure/Infrastructure.csproj +++ b/src/Services/Common/Infrastructure/Infrastructure.csproj @@ -5,6 +5,7 @@ Microsoft.eShopOnContainers.Services.Common.Infrastructure + \ No newline at end of file From 4d1269b8f2424d7629879b8819d93be8168b4a94 Mon Sep 17 00:00:00 2001 From: dsanz Date: Fri, 10 Mar 2017 18:34:58 +0100 Subject: [PATCH 06/80] Add handler logic for basket repository. Add Post to CatalogController (only for price update). --- .../Events/CatalogPriceChangedHandler.cs | 31 ++++++++++++--- .../Basket/Basket.API/Model/BasketItem.cs | 1 + .../Basket.API/Model/IBasketRepository.cs | 1 + .../Basket.API/Model/RedisBasketRepository.cs | 38 +++++++++++++++++-- src/Services/Basket/Basket.API/Startup.cs | 19 +++++++++- .../Controllers/CatalogController.cs | 26 ++++++++++++- .../Catalog/CatalogPriceChanged.cs | 9 +++-- .../Common/Infrastructure/EventBus.cs | 5 ++- .../IIntegrationEventHandler.cs | 3 +- 9 files changed, 114 insertions(+), 19 deletions(-) diff --git a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs index 67fc6616f..db116b0c4 100644 --- a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs +++ b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs @@ -11,15 +11,36 @@ namespace Basket.API.Events public class CatalogPriceChangedHandler : IIntegrationEventHandler { private readonly IBasketRepository _repository; - public CatalogPriceChangedHandler() + public CatalogPriceChangedHandler(IBasketRepository repository) { - //_repository = repository; + _repository = repository; } - public void Handle(CatalogPriceChanged @event) + public async Task Handle(CatalogPriceChanged @event) { - - } + var userIds = await _repository.GetUsers(); + foreach (var id in userIds) + { + var basket = await _repository.GetBasket(id); + await UpdateBasket(@event.ItemId, @event.NewPrice, basket); + } + } + + private async Task UpdateBasket(int itemId, decimal newPrice, CustomerBasket basket) + { + //TODO here seems to be a problem with the format + var itemsToUpdate = basket?.Items?.Where(x => int.Parse(x.Id) == itemId).ToList(); + if (itemsToUpdate != null) + { + foreach (var item in itemsToUpdate) + { + var originalPrice = item.UnitPrice; + item.UnitPrice = newPrice; + item.OldUnitPrice = originalPrice; + } + await _repository.UpdateBasket(basket); + } + } } } diff --git a/src/Services/Basket/Basket.API/Model/BasketItem.cs b/src/Services/Basket/Basket.API/Model/BasketItem.cs index c3f9b6212..76835ade9 100644 --- a/src/Services/Basket/Basket.API/Model/BasketItem.cs +++ b/src/Services/Basket/Basket.API/Model/BasketItem.cs @@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model public string ProductId { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } + public decimal OldUnitPrice { get; set; } public int Quantity { get; set; } public string PictureUrl { get; set; } } diff --git a/src/Services/Basket/Basket.API/Model/IBasketRepository.cs b/src/Services/Basket/Basket.API/Model/IBasketRepository.cs index 3f0b99acf..017de8d51 100644 --- a/src/Services/Basket/Basket.API/Model/IBasketRepository.cs +++ b/src/Services/Basket/Basket.API/Model/IBasketRepository.cs @@ -8,6 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model public interface IBasketRepository { Task GetBasket(string customerId); + Task> GetUsers(); Task UpdateBasket(CustomerBasket basket); Task DeleteBasket(string id); } diff --git a/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs b/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs index 19d8e7055..fc5c256f8 100644 --- a/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs +++ b/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs @@ -31,6 +31,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model return await database.KeyDeleteAsync(id.ToString()); } + public async Task> GetUsers() + { + var server = await GetServer(); + + IEnumerable data = server.Keys(); + if (data == null) + { + return null; + } + return data.Select(k => k.ToString()); + } + public async Task GetBasket(string customerId) { var database = await GetDatabase(); @@ -63,14 +75,32 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model { if (_redis == null) { - //TODO: Need to make this more robust. Also want to understand why the static connection method cannot accept dns names. - var ips = await Dns.GetHostAddressesAsync(_settings.ConnectionString); - _logger.LogInformation($"Connecting to database {_settings.ConnectionString} at IP {ips.First().ToString()}"); - _redis = await ConnectionMultiplexer.ConnectAsync(ips.First().ToString()); + await ConnectToRedisAsync(); } return _redis.GetDatabase(); } + + private async Task GetServer() + { + if (_redis == null) + { + await ConnectToRedisAsync(); + } + var endpoint = _redis.GetEndPoints(); + + return _redis.GetServer(endpoint.First()); + } + + private async Task ConnectToRedisAsync() + { + //TODO: Need to make this more robust. Also want to understand why the static connection method cannot accept dns names. + var ips = await Dns.GetHostAddressesAsync(_settings.ConnectionString); + _logger.LogInformation($"Connecting to database {_settings.ConnectionString} at IP {ips.First().ToString()}"); + _redis = await ConnectionMultiplexer.ConnectAsync(ips.First().ToString()); + } + + } } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 2eb6a36c1..f7fa06df8 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -79,9 +79,24 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API }); services.AddTransient(); - var eventBus = new EventBus(); + services.AddTransient, CatalogPriceChangedHandler>(); + + var eventBus = new EventBus(); services.AddSingleton(eventBus); - eventBus.Subscribe(new CatalogPriceChangedHandler()); + + var serviceProvider = services.BuildServiceProvider(); + var catalogPriceHandler = serviceProvider.GetService>(); + eventBus.Subscribe(catalogPriceHandler); + + //var container = new ContainerBuilder(); + //container.Populate(services); + //container.RegisterModule(new ApplicationModule()); + + //return new AutofacServiceProvider(container.Build()); + + //var eventBus = new EventBus(); + //services.AddSingleton(eventBus); + //eventBus.Subscribe(new CatalogPriceChangedHandler(new RedisBasketRepository()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 3558c55f5..9d8cc0b11 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -107,7 +107,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers //hook to run integration tests until POST methods are created if (catalogTypeId.HasValue && catalogTypeId == 1) { - _eventBus.Publish(new CatalogPriceChanged(2, 10.4M)); + _eventBus.Publish(new CatalogPriceChanged(2, 10.4M, 8.4M)); } return Ok(model); @@ -135,6 +135,30 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return Ok(items); } + [HttpPost] + public async Task Post([FromBody]CatalogItem value) + { + var item = await _context.CatalogItems.SingleOrDefaultAsync(i => i.Id == value.Id); + + if (item == null) + { + return NotFound(); + } + + if (item.Price != value.Price) + { + var oldPrice = item.Price; + item.Price = value.Price; + + _context.CatalogItems.Update(item); + await _context.SaveChangesAsync(); + + _eventBus.Publish(new CatalogPriceChanged(item.Id, item.Price, oldPrice)); + } + + return Ok(); + } + private List ComposePicUri(List items) { var baseUri = _settings.Value.ExternalCatalogBaseUrl; items.ForEach(x => diff --git a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs index 6e5715925..427aa69c2 100644 --- a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs +++ b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs @@ -5,17 +5,18 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog { public class CatalogPriceChanged : IIntegrationEvent - { - public string Message { get { return "CatalogPriceChanged here!!"; } } - + { public int ItemId { get; private set; } public decimal NewPrice { get; private set; } - public CatalogPriceChanged(int itemId, decimal newPrice) + public decimal OldPrice { get; set; } + + public CatalogPriceChanged(int itemId, decimal newPrice, decimal oldPrice) { ItemId = itemId; NewPrice = newPrice; + OldPrice = oldPrice; } } } diff --git a/src/Services/Common/Infrastructure/EventBus.cs b/src/Services/Common/Infrastructure/EventBus.cs index a606a6d33..dd273d571 100644 --- a/src/Services/Common/Infrastructure/EventBus.cs +++ b/src/Services/Common/Infrastructure/EventBus.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { @@ -116,7 +117,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => + consumer.Received += async (model, ea) => { var eventName = ea.RoutingKey; if (_handlers.ContainsKey(eventName)) @@ -132,7 +133,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure foreach (var handler in handlers) { - concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); + await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); } } }; diff --git a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs index 49d194ecb..6824a1665 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs +++ b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public interface IIntegrationEventHandler : IIntegrationEventHandler where TIntegrationEvent: IIntegrationEvent { - void Handle(TIntegrationEvent @event); + Task Handle(TIntegrationEvent @event); } public interface IIntegrationEventHandler From 172367d9d1788bb43042a10778a7d4b651fa9040 Mon Sep 17 00:00:00 2001 From: dsanz Date: Mon, 13 Mar 2017 11:03:35 +0100 Subject: [PATCH 07/80] Connect to the rabbitmq container using settings. --- docker-compose.override.yml | 3 +++ .../Basket/Basket.API/BasketSettings.cs | 2 ++ .../Events/CatalogPriceChangedHandler.cs | 5 ++--- src/Services/Basket/Basket.API/Startup.cs | 17 +++++------------ src/Services/Catalog/Catalog.API/Startup.cs | 5 ++++- src/Services/Catalog/Catalog.API/settings.cs | 1 + src/Services/Common/Infrastructure/EventBus.cs | 8 +++++--- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 4e24592cf..2fe32722e 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -8,6 +8,7 @@ services: - ASPNETCORE_URLS=http://0.0.0.0:5103 - ConnectionString=basket.data - identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - EventBusConnection=rabbitmq ports: - "5103:5103" @@ -17,6 +18,7 @@ services: - ASPNETCORE_URLS=http://0.0.0.0:5101 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word - ExternalCatalogBaseUrl=http://localhost:5101 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - EventBusConnection=rabbitmq ports: - "5101:5101" @@ -36,6 +38,7 @@ services: - ASPNETCORE_URLS=http://0.0.0.0:5102 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word - identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - EventBusConnection=rabbitmq ports: - "5102:5102" diff --git a/src/Services/Basket/Basket.API/BasketSettings.cs b/src/Services/Basket/Basket.API/BasketSettings.cs index d584c102f..6aae45015 100644 --- a/src/Services/Basket/Basket.API/BasketSettings.cs +++ b/src/Services/Basket/Basket.API/BasketSettings.cs @@ -8,5 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API public class BasketSettings { public string ConnectionString { get; set; } + + public string EventBusConnection { get; set; } } } diff --git a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs index db116b0c4..006c82119 100644 --- a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs +++ b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs @@ -28,8 +28,7 @@ namespace Basket.API.Events private async Task UpdateBasket(int itemId, decimal newPrice, CustomerBasket basket) { - //TODO here seems to be a problem with the format - var itemsToUpdate = basket?.Items?.Where(x => int.Parse(x.Id) == itemId).ToList(); + var itemsToUpdate = basket?.Items?.Where(x => int.Parse(x.ProductId) == itemId).ToList(); if (itemsToUpdate != null) { foreach (var item in itemsToUpdate) @@ -39,7 +38,7 @@ namespace Basket.API.Events item.OldUnitPrice = originalPrice; } await _repository.UpdateBasket(basket); - } + } } } } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index f7fa06df8..e0bc9c076 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -81,22 +81,15 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddTransient(); services.AddTransient, CatalogPriceChangedHandler>(); - var eventBus = new EventBus(); + var serviceProvider = services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService>().Value; + var eventBus = new EventBus(configuration.EventBusConnection); services.AddSingleton(eventBus); - var serviceProvider = services.BuildServiceProvider(); + var catalogPriceHandler = serviceProvider.GetService>(); eventBus.Subscribe(catalogPriceHandler); - - //var container = new ContainerBuilder(); - //container.Populate(services); - //container.RegisterModule(new ApplicationModule()); - - //return new AutofacServiceProvider(container.Build()); - - //var eventBus = new EventBus(); - //services.AddSingleton(eventBus); - //eventBus.Subscribe(new CatalogPriceChangedHandler(new RedisBasketRepository()); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index c8ecf72fd..9e0da8d00 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; using System; using System.IO; using System.Reflection; @@ -74,7 +75,9 @@ .AllowCredentials()); }); - services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService>().Value; + services.AddSingleton(new EventBus(configuration.EventBusConnection)); services.AddMvc(); } diff --git a/src/Services/Catalog/Catalog.API/settings.cs b/src/Services/Catalog/Catalog.API/settings.cs index 76091c409..a6e959552 100644 --- a/src/Services/Catalog/Catalog.API/settings.cs +++ b/src/Services/Catalog/Catalog.API/settings.cs @@ -9,5 +9,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API public class Settings { public string ExternalCatalogBaseUrl {get;set;} + public string EventBusConnection { get; set; } } } diff --git a/src/Services/Common/Infrastructure/EventBus.cs b/src/Services/Common/Infrastructure/EventBus.cs index dd273d571..47e51d28d 100644 --- a/src/Services/Common/Infrastructure/EventBus.cs +++ b/src/Services/Common/Infrastructure/EventBus.cs @@ -16,6 +16,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure public class EventBus : IEventBus { private readonly string _brokerName = "event_bus"; + private readonly string _connectionString; private readonly Dictionary> _handlers; private readonly List _eventTypes; @@ -23,15 +24,16 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure private string _queueName; - public EventBus() + public EventBus(string connectionString) { + _connectionString = connectionString; _handlers = new Dictionary>(); _eventTypes = new List(); } public void Publish(IIntegrationEvent @event) { var eventName = @event.GetType().Name; - var factory = new ConnectionFactory() { HostName = "172.20.0.1" }; + var factory = new ConnectionFactory() { HostName = _connectionString }; using (var connection = factory.CreateConnection()) using (var channel = connection.CreateModel()) { @@ -105,7 +107,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } else { - var factory = new ConnectionFactory() { HostName = "172.20.0.1" }; + var factory = new ConnectionFactory() { HostName = _connectionString }; var connection = factory.CreateConnection(); var channel = connection.CreateModel(); From 66179813035acb31ed8210870e7494e14f2198bd Mon Sep 17 00:00:00 2001 From: dsanz Date: Mon, 13 Mar 2017 11:34:25 +0100 Subject: [PATCH 08/80] Fix for #80 --- src/Services/Identity/Identity.API/Configuration/Config.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Services/Identity/Identity.API/Configuration/Config.cs b/src/Services/Identity/Identity.API/Configuration/Config.cs index 764320096..9c799ca68 100644 --- a/src/Services/Identity/Identity.API/Configuration/Config.cs +++ b/src/Services/Identity/Identity.API/Configuration/Config.cs @@ -91,8 +91,8 @@ namespace Identity.API.Configuration }, PostLogoutRedirectUris = new List { - $"{clientsUrl["Mvc"]}/", - "http://localhost:5100/" + $"{clientsUrl["Mvc"]}/signout-callback-oidc", + "http://localhost:5100/signout-callback-oidc" }, AllowedScopes = new List { From 4e5e32c6aa094d70dfed7f5bcff4d95590455a9d Mon Sep 17 00:00:00 2001 From: dsanz Date: Mon, 13 Mar 2017 12:15:41 +0100 Subject: [PATCH 09/80] Add old price property for the basket in the WebMVC --- src/Web/WebMVC/ViewModels/BasketItem.cs | 1 + .../Shared/Components/CartList/Default.cshtml | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Web/WebMVC/ViewModels/BasketItem.cs b/src/Web/WebMVC/ViewModels/BasketItem.cs index 9e38c5f8d..ab767f8f5 100644 --- a/src/Web/WebMVC/ViewModels/BasketItem.cs +++ b/src/Web/WebMVC/ViewModels/BasketItem.cs @@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels public string ProductId { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } + public decimal OldUnitPrice { get; set; } public int Quantity { get; set; } public string PictureUrl { get; set; } } diff --git a/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml b/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml index d3c2abaed..33e62bafb 100644 --- a/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml +++ b/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml @@ -18,17 +18,24 @@ var item = Model.Items[i];
- -
- -
-
@item.ProductName
-
$ @item.UnitPrice
-
- - -
-
$ @Math.Round(item.Quantity * item.UnitPrice, 2)
+
+
+ +
+
@item.ProductName
+
$ @item.UnitPrice
+
+ + +
+
$ @Math.Round(item.Quantity * item.UnitPrice, 2)
+
+ @if (item.OldUnitPrice != 0) + { +
+ The price of the item has changed. Old price was @item.OldUnitPrice $ +
+ }
} From f502c2388fde2af1c9971d64965a50a0919aefa1 Mon Sep 17 00:00:00 2001 From: dsanz Date: Tue, 14 Mar 2017 09:47:36 +0100 Subject: [PATCH 10/80] Add persistence of published integration events for Catalog (the only microservice publishing integration events by the moment). --- .../Catalog/Catalog.API/Catalog.API.csproj | 1 + .../Controllers/CatalogController.cs | 30 +++-- .../Infrastructure/CatalogContext.cs | 32 ++++- .../20170314083211_AddEventTable.Designer.cs | 123 ++++++++++++++++++ .../20170314083211_AddEventTable.cs | 34 +++++ .../Migrations/CatalogContextModelSnapshot.cs | 44 +++++-- .../Catalog/CatalogPriceChanged.cs | 2 +- .../EventStateEnum.cs} | 7 +- .../Infrastructure/Data/IntegrationEvent.cs | 26 ++++ .../Common/Infrastructure/EventBus.cs | 6 +- .../Common/Infrastructure/IEventBus.cs | 6 +- .../IIntegrationEventHandler.cs | 2 +- .../Infrastructure/IntegrationEventBase.cs | 16 +++ 13 files changed, 300 insertions(+), 29 deletions(-) create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs rename src/Services/Common/Infrastructure/{IIntegrationEvent.cs => Data/EventStateEnum.cs} (59%) create mode 100644 src/Services/Common/Infrastructure/Data/IntegrationEvent.cs create mode 100644 src/Services/Common/Infrastructure/IntegrationEventBase.cs diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 1c0fddf92..387908b90 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -45,6 +45,7 @@ +
diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 9d8cc0b11..71dcd5d1e 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -5,7 +5,9 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.Model; using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; using Microsoft.eShopOnContainers.Services.Common.Infrastructure; using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data; using Microsoft.Extensions.Options; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -104,12 +106,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers var model = new PaginatedItemsViewModel( pageIndex, pageSize, totalItems, itemsOnPage); - //hook to run integration tests until POST methods are created - if (catalogTypeId.HasValue && catalogTypeId == 1) - { - _eventBus.Publish(new CatalogPriceChanged(2, 10.4M, 8.4M)); - } - return Ok(model); } @@ -153,11 +149,12 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers _context.CatalogItems.Update(item); await _context.SaveChangesAsync(); - _eventBus.Publish(new CatalogPriceChanged(item.Id, item.Price, oldPrice)); + var @event = new CatalogPriceChanged(item.Id, item.Price, oldPrice); + await ProcessEventAsync(@event); } return Ok(); - } + } private List ComposePicUri(List items) { var baseUri = _settings.Value.ExternalCatalogBaseUrl; @@ -168,5 +165,22 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return items; } + + private async Task ProcessEventAsync(IntegrationEventBase @event) + { + _eventBus.Publish(@event); + var eventData = new IntegrationEvent(@event); + eventData.TimesSent++; + eventData.State = EventStateEnum.Sent; + try + { + _context.IntegrationEvents.Add(eventData); + await _context.SaveChangesAsync(); + } + catch (Exception ex) + { + var t = ex.Message; + } + } } } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs index 5c5498099..aac778940 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs @@ -3,6 +3,7 @@ using EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore; using Model; + using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data; public class CatalogContext : DbContext { @@ -12,12 +13,15 @@ public DbSet CatalogItems { get; set; } public DbSet CatalogBrands { get; set; } public DbSet CatalogTypes { get; set; } + public DbSet IntegrationEvents { get; set; } + protected override void OnModelCreating(ModelBuilder builder) { builder.Entity(ConfigureCatalogBrand); builder.Entity(ConfigureCatalogType); builder.Entity(ConfigureCatalogItem); - } + builder.Entity(ConfigureIntegrationEvent); + } void ConfigureCatalogItem(EntityTypeBuilder builder) { @@ -75,5 +79,31 @@ .IsRequired() .HasMaxLength(100); } + + void ConfigureIntegrationEvent(EntityTypeBuilder builder) + { + builder.ToTable("IntegrationEvent"); + + builder.HasKey(e => e.EventId); + + builder.Property(e => e.EventId) + .IsRequired(); + + builder.Property(e => e.Content) + .IsRequired(); + + builder.Property(e => e.CreationTime) + .IsRequired(); + + builder.Property(e => e.State) + .IsRequired(); + + builder.Property(e => e.TimesSent) + .IsRequired(); + + builder.Property(e => e.EventTypeName) + .IsRequired(); + + } } } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs new file mode 100644 index 000000000..126047b85 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs @@ -0,0 +1,123 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure; + +namespace Catalog.API.Infrastructure.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20170314083211_AddEventTable")] + partial class AddEventTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752") + .HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_brand_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogBrand"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("CatalogBrandId"); + + b.Property("CatalogTypeId"); + + b.Property("Description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50); + + b.Property("PictureUri"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_type_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired() + .HasMaxLength(200); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEvent"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs new file mode 100644 index 000000000..e6494d2a3 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Catalog.API.Infrastructure.Migrations +{ + public partial class AddEventTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "IntegrationEvent", + columns: table => new + { + EventId = table.Column(nullable: false), + Content = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + EventTypeName = table.Column(maxLength: 200, nullable: false), + State = table.Column(nullable: false), + TimesSent = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IntegrationEvent", x => x.EventId); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "IntegrationEvent"); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs index c0eb1db72..4ea056e0d 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure; namespace Catalog.API.Infrastructure.Migrations { @@ -13,13 +14,13 @@ namespace Catalog.API.Infrastructure.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder - .HasAnnotation("ProductVersion", "1.0.1") + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752") .HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogBrand", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -28,14 +29,14 @@ namespace Catalog.API.Infrastructure.Migrations b.Property("Brand") .IsRequired() - .HasAnnotation("MaxLength", 100); + .HasMaxLength(100); b.HasKey("Id"); b.ToTable("CatalogBrand"); }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogItem", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -50,7 +51,7 @@ namespace Catalog.API.Infrastructure.Migrations b.Property("Name") .IsRequired() - .HasAnnotation("MaxLength", 50); + .HasMaxLength(50); b.Property("PictureUri"); @@ -65,7 +66,7 @@ namespace Catalog.API.Infrastructure.Migrations b.ToTable("Catalog"); }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogType", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -74,21 +75,44 @@ namespace Catalog.API.Infrastructure.Migrations b.Property("Type") .IsRequired() - .HasAnnotation("MaxLength", 100); + .HasMaxLength(100); b.HasKey("Id"); b.ToTable("CatalogType"); }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogItem", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEvent", b => { - b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogBrand", "CatalogBrand") + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired() + .HasMaxLength(200); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEvent"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand") .WithMany() .HasForeignKey("CatalogBrandId") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogType", "CatalogType") + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType") .WithMany() .HasForeignKey("CatalogTypeId") .OnDelete(DeleteBehavior.Cascade); diff --git a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs index 427aa69c2..2c31a85b6 100644 --- a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs +++ b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs @@ -4,7 +4,7 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog { - public class CatalogPriceChanged : IIntegrationEvent + public class CatalogPriceChanged : IntegrationEventBase { public int ItemId { get; private set; } diff --git a/src/Services/Common/Infrastructure/IIntegrationEvent.cs b/src/Services/Common/Infrastructure/Data/EventStateEnum.cs similarity index 59% rename from src/Services/Common/Infrastructure/IIntegrationEvent.cs rename to src/Services/Common/Infrastructure/Data/EventStateEnum.cs index 48d991283..e952b7742 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEvent.cs +++ b/src/Services/Common/Infrastructure/Data/EventStateEnum.cs @@ -4,7 +4,10 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { - public interface IIntegrationEvent - { + public enum EventStateEnum + { + NotSend = 0, + Sent = 1, + SendingFailed = 2 } } diff --git a/src/Services/Common/Infrastructure/Data/IntegrationEvent.cs b/src/Services/Common/Infrastructure/Data/IntegrationEvent.cs new file mode 100644 index 000000000..97b313021 --- /dev/null +++ b/src/Services/Common/Infrastructure/Data/IntegrationEvent.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data +{ + public class IntegrationEvent + { + public IntegrationEvent(IntegrationEventBase @event) + { + EventId = @event.Id; + CreationTime = DateTime.UtcNow; + EventTypeName = @event.GetType().FullName; + Content = JsonConvert.SerializeObject(@event); + State = EventStateEnum.NotSend; + TimesSent = 0; + } + public Guid EventId { get; private set; } + public string EventTypeName { get; private set; } + public EventStateEnum State { get; set; } + public int TimesSent { get; set; } + public DateTime CreationTime { get; private set; } + public string Content { get; private set; } + } +} diff --git a/src/Services/Common/Infrastructure/EventBus.cs b/src/Services/Common/Infrastructure/EventBus.cs index 47e51d28d..4ff33e79b 100644 --- a/src/Services/Common/Infrastructure/EventBus.cs +++ b/src/Services/Common/Infrastructure/EventBus.cs @@ -30,7 +30,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure _handlers = new Dictionary>(); _eventTypes = new List(); } - public void Publish(IIntegrationEvent @event) + public void Publish(IntegrationEventBase @event) { var eventName = @event.GetType().Name; var factory = new ConnectionFactory() { HostName = _connectionString }; @@ -51,7 +51,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } - public void Subscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent + public void Subscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase { var eventName = typeof(T).Name; if (_handlers.ContainsKey(eventName)) @@ -72,7 +72,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } - public void Unsubscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent + public void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase { var eventName = typeof(T).Name; if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler)) diff --git a/src/Services/Common/Infrastructure/IEventBus.cs b/src/Services/Common/Infrastructure/IEventBus.cs index 5751f1b81..483e550ec 100644 --- a/src/Services/Common/Infrastructure/IEventBus.cs +++ b/src/Services/Common/Infrastructure/IEventBus.cs @@ -6,8 +6,8 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public interface IEventBus { - void Subscribe(IIntegrationEventHandler handler) where T: IIntegrationEvent; - void Unsubscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent; - void Publish(IIntegrationEvent @event); + void Subscribe(IIntegrationEventHandler handler) where T: IntegrationEventBase; + void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase; + void Publish(IntegrationEventBase @event); } } diff --git a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs index 6824a1665..f4c00fa6b 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs +++ b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public interface IIntegrationEventHandler : IIntegrationEventHandler - where TIntegrationEvent: IIntegrationEvent + where TIntegrationEvent: IntegrationEventBase { Task Handle(TIntegrationEvent @event); } diff --git a/src/Services/Common/Infrastructure/IntegrationEventBase.cs b/src/Services/Common/Infrastructure/IntegrationEventBase.cs new file mode 100644 index 000000000..b11c17815 --- /dev/null +++ b/src/Services/Common/Infrastructure/IntegrationEventBase.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +{ + public class IntegrationEventBase + { + public IntegrationEventBase() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; private set; } + } +} From 254d47958204e96d2ef3b0a1deff65cbd38f2942 Mon Sep 17 00:00:00 2001 From: dsanz Date: Tue, 14 Mar 2017 09:54:30 +0100 Subject: [PATCH 11/80] Renaming. --- .../Basket.API/Events/CatalogPriceChangedHandler.cs | 6 +++--- src/Services/Basket/Basket.API/Startup.cs | 8 ++++---- .../Catalog/Catalog.API/Controllers/CatalogController.cs | 2 +- src/Services/Catalog/Catalog.API/Startup.cs | 2 +- .../{CatalogPriceChanged.cs => ProductPriceChanged.cs} | 4 ++-- .../Infrastructure/{EventBus.cs => EventBusRabbitMQ.cs} | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) rename src/Services/Common/Infrastructure/Catalog/{CatalogPriceChanged.cs => ProductPriceChanged.cs} (78%) rename src/Services/Common/Infrastructure/{EventBus.cs => EventBusRabbitMQ.cs} (98%) diff --git a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs index 006c82119..280692c17 100644 --- a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs +++ b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs @@ -8,15 +8,15 @@ using System.Threading.Tasks; namespace Basket.API.Events { - public class CatalogPriceChangedHandler : IIntegrationEventHandler + public class ProductPriceChangedHandler : IIntegrationEventHandler { private readonly IBasketRepository _repository; - public CatalogPriceChangedHandler(IBasketRepository repository) + public ProductPriceChangedHandler(IBasketRepository repository) { _repository = repository; } - public async Task Handle(CatalogPriceChanged @event) + public async Task Handle(ProductPriceChanged @event) { var userIds = await _repository.GetUsers(); foreach (var id in userIds) diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index e0bc9c076..ec5141de3 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -79,16 +79,16 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API }); services.AddTransient(); - services.AddTransient, CatalogPriceChangedHandler>(); + services.AddTransient, ProductPriceChangedHandler>(); var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; - var eventBus = new EventBus(configuration.EventBusConnection); + var eventBus = new EventBusRabbitMQ(configuration.EventBusConnection); services.AddSingleton(eventBus); - var catalogPriceHandler = serviceProvider.GetService>(); - eventBus.Subscribe(catalogPriceHandler); + var catalogPriceHandler = serviceProvider.GetService>(); + eventBus.Subscribe(catalogPriceHandler); } diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 71dcd5d1e..2909b141c 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -149,7 +149,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers _context.CatalogItems.Update(item); await _context.SaveChangesAsync(); - var @event = new CatalogPriceChanged(item.Id, item.Price, oldPrice); + var @event = new ProductPriceChanged(item.Id, item.Price, oldPrice); await ProcessEventAsync(@event); } diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 9e0da8d00..cb4f6254c 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -77,7 +77,7 @@ var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; - services.AddSingleton(new EventBus(configuration.EventBusConnection)); + services.AddSingleton(new EventBusRabbitMQ(configuration.EventBusConnection)); services.AddMvc(); } diff --git a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs b/src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs similarity index 78% rename from src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs rename to src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs index 2c31a85b6..4f28d4b1f 100644 --- a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs +++ b/src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs @@ -4,7 +4,7 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog { - public class CatalogPriceChanged : IntegrationEventBase + public class ProductPriceChanged : IntegrationEventBase { public int ItemId { get; private set; } @@ -12,7 +12,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog public decimal OldPrice { get; set; } - public CatalogPriceChanged(int itemId, decimal newPrice, decimal oldPrice) + public ProductPriceChanged(int itemId, decimal newPrice, decimal oldPrice) { ItemId = itemId; NewPrice = newPrice; diff --git a/src/Services/Common/Infrastructure/EventBus.cs b/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs similarity index 98% rename from src/Services/Common/Infrastructure/EventBus.cs rename to src/Services/Common/Infrastructure/EventBusRabbitMQ.cs index 4ff33e79b..aa17553a7 100644 --- a/src/Services/Common/Infrastructure/EventBus.cs +++ b/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs @@ -13,7 +13,7 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { - public class EventBus : IEventBus + public class EventBusRabbitMQ : IEventBus { private readonly string _brokerName = "event_bus"; private readonly string _connectionString; @@ -24,7 +24,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure private string _queueName; - public EventBus(string connectionString) + public EventBusRabbitMQ(string connectionString) { _connectionString = connectionString; _handlers = new Dictionary>(); From 23fcfd0dc4fcbb1513fbdf977aba4859a68ef337 Mon Sep 17 00:00:00 2001 From: "DESKTOP-V1VLQ15\\dsanz" Date: Tue, 14 Mar 2017 12:17:05 +0100 Subject: [PATCH 12/80] Fix merge issue. --- eShopOnContainers-ServicesAndWebApps.sln | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 6d59a9a18..f7c3dcde6 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -54,6 +54,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{478578 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Services\Common\Infrastructure\Infrastructure.csproj", "{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{9C2566F6-3A98-4B1D-A6B2-35820CE88B73}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{7B41F409-D86D-4697-99BC-F8EBA21BB18F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -619,6 +623,8 @@ Global {F16E3C6A-1C94-4EAB-BE91-099618060B68} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} {5B810E3D-112E-4857-B197-F09D2FD41E27} = {EF0337F2-ED00-4643-89FD-EE10863F1870} {CFE2FACB-4538-4B99-8A10-306F3882952D} = {EF0337F2-ED00-4643-89FD-EE10863F1870} - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0} = {EF0337F2-ED00-4643-89FD-EE10863F1870} + {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0} = {7B41F409-D86D-4697-99BC-F8EBA21BB18F} + {9C2566F6-3A98-4B1D-A6B2-35820CE88B73} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} + {7B41F409-D86D-4697-99BC-F8EBA21BB18F} = {9C2566F6-3A98-4B1D-A6B2-35820CE88B73} EndGlobalSection EndGlobal From f6b2335518cdd469abb9a270f87b65c4cf7df177 Mon Sep 17 00:00:00 2001 From: "DESKTOP-V1VLQ15\\dsanz" Date: Tue, 14 Mar 2017 14:15:34 +0100 Subject: [PATCH 13/80] Fix issue with tests and Authorize attribute --- .../Common/Infrastructure/EventBusRabbitMQ.cs | 37 ++++++++++--------- .../Controllers/OrdersController.cs | 2 +- .../Middleware/AutoAuthorizeMiddleware.cs | 2 +- .../Middleware/AutoAuthorizeMiddleware.cs | 2 +- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs b/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs index aa17553a7..f9c3d6a57 100644 --- a/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs +++ b/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs @@ -15,7 +15,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public class EventBusRabbitMQ : IEventBus { - private readonly string _brokerName = "event_bus"; + private readonly string _brokerName = "eshop_event_bus"; private readonly string _connectionString; private readonly Dictionary> _handlers; private readonly List _eventTypes; @@ -120,24 +120,11 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure var consumer = new EventingBasicConsumer(channel); consumer.Received += async (model, ea) => - { + { var eventName = ea.RoutingKey; - if (_handlers.ContainsKey(eventName)) - { - var message = Encoding.UTF8.GetString(ea.Body); - Type eventType = _eventTypes.Single(t => t.Name == eventName); - - var integrationEvent = JsonConvert.DeserializeObject(message, eventType); - var handlers = _handlers[eventName]; + var message = Encoding.UTF8.GetString(ea.Body); - - var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); - - foreach (var handler in handlers) - { - await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); - } - } + await ProcessEvent(eventName, message); }; channel.BasicConsume(queue: _queueName, noAck: true, @@ -147,5 +134,21 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure return _connection.Item1; } } + + private async Task ProcessEvent(string eventName, string message) + { + if (_handlers.ContainsKey(eventName)) + { + Type eventType = _eventTypes.Single(t => t.Name == eventName); + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); + var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); + var handlers = _handlers[eventName]; + + foreach (var handler in handlers) + { + await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); + } + } + } } } diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index d01150b9c..ea97fc2fa 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers { [Route("api/v1/[controller]")] - //[Authorize] + [Authorize] public class OrdersController : Controller { private readonly IMediator _mediator; diff --git a/test/Services/FunctionalTests/Middleware/AutoAuthorizeMiddleware.cs b/test/Services/FunctionalTests/Middleware/AutoAuthorizeMiddleware.cs index 72248e29d..41f1dfc88 100644 --- a/test/Services/FunctionalTests/Middleware/AutoAuthorizeMiddleware.cs +++ b/test/Services/FunctionalTests/Middleware/AutoAuthorizeMiddleware.cs @@ -17,7 +17,7 @@ namespace FunctionalTests.Middleware public async Task Invoke(HttpContext httpContext) { - var identity = new ClaimsIdentity(); + var identity = new ClaimsIdentity("cookies"); identity.AddClaim(new Claim("sub", "1234")); httpContext.User.AddIdentity(identity); await _next.Invoke(httpContext); diff --git a/test/Services/IntegrationTests/Middleware/AutoAuthorizeMiddleware.cs b/test/Services/IntegrationTests/Middleware/AutoAuthorizeMiddleware.cs index ce8172afa..598e4d0dd 100644 --- a/test/Services/IntegrationTests/Middleware/AutoAuthorizeMiddleware.cs +++ b/test/Services/IntegrationTests/Middleware/AutoAuthorizeMiddleware.cs @@ -17,7 +17,7 @@ namespace IntegrationTests.Middleware public async Task Invoke(HttpContext httpContext) { - var identity = new ClaimsIdentity(); + var identity = new ClaimsIdentity("cookies"); identity.AddClaim(new Claim("sub", "1234")); httpContext.User.AddIdentity(identity); await _next.Invoke(httpContext); From 8a9492e5733a68a07cc4cfb2c9791bd1da9ff4e3 Mon Sep 17 00:00:00 2001 From: "DESKTOP-V1VLQ15\\dsanz" Date: Tue, 14 Mar 2017 16:52:33 +0100 Subject: [PATCH 14/80] Add catalog test. --- .../FunctionalTests/FunctionalTests.csproj | 1 + .../Services/Catalog/CatalogScenarios.cs | 55 +++++++++++++++++++ .../Services/Catalog/CatalogScenariosBase.cs | 38 +++++++++++++ .../Services/Catalog/CatalogTestsStartup.cs | 15 +++++ test/Services/FunctionalTests/settings.json | 3 +- 5 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs create mode 100644 test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs create mode 100644 test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs diff --git a/test/Services/FunctionalTests/FunctionalTests.csproj b/test/Services/FunctionalTests/FunctionalTests.csproj index 039a734a2..0e24f233d 100644 --- a/test/Services/FunctionalTests/FunctionalTests.csproj +++ b/test/Services/FunctionalTests/FunctionalTests.csproj @@ -13,6 +13,7 @@ + diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs new file mode 100644 index 000000000..ec4643ce7 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs @@ -0,0 +1,55 @@ +using Microsoft.eShopOnContainers.Services.Catalog.API.Model; +using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace FunctionalTests.Services.Catalog +{ + public class CatalogScenarios : CatalogScenariosBase + { + [Fact] + public async Task Post_update_a_catalogitem_price_and_catalogitem_is_returned_modified() + { + using (var server = CreateServer()) + { + var client = server.CreateClient(); + + // Arrange + var itemToModify = GetCatalogItem(); + var newPrice = new Random().Next(1, 200); + itemToModify.Price = newPrice; + + // Act + var postRes = await client.PostAsync(Post.UpdateCatalogProduct, + new StringContent(JsonConvert.SerializeObject(itemToModify), + UTF8Encoding.UTF8, "application/json")); + var response = await client.GetAsync(Get.ProductByName(itemToModify.Name)); + var result = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + var item = result.Data.First(); + + // Assert + Assert.Equal(result.Count, 1); + Assert.Equal(itemToModify.Id, item.Id); + Assert.Equal(newPrice, item.Price); + } + + } + + private CatalogItem GetCatalogItem() + { + return new CatalogItem() + { + Id = 1, + Price = 12.5M, + Name = ".NET Bot Black Sweatshirt" + }; + } + + } +} diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs new file mode 100644 index 000000000..b4107b3bc --- /dev/null +++ b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs @@ -0,0 +1,38 @@ +using FunctionalTests.Middleware; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.eShopOnContainers.Services.Catalog.API; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace FunctionalTests.Services.Catalog +{ + public class CatalogScenariosBase + { + public TestServer CreateServer() + { + var webHostBuilder = new WebHostBuilder(); + webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory()); + webHostBuilder.UseStartup(); + + return new TestServer(webHostBuilder); + } + + public static class Get + { + public static string Orders = "api/v1/orders"; + + public static string ProductByName(string name) + { + return $"api/v1/catalog/items/withname/{name}"; + } + } + + public static class Post + { + public static string UpdateCatalogProduct = "api/v1/catalog"; + } + } +} diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs new file mode 100644 index 000000000..f06f9b248 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs @@ -0,0 +1,15 @@ +using Microsoft.eShopOnContainers.Services.Catalog.API; +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Hosting; + +namespace FunctionalTests.Services.Catalog +{ + public class CatalogTestsStartup : Startup + { + public CatalogTestsStartup(IHostingEnvironment env) : base(env) + { + } + } +} diff --git a/test/Services/FunctionalTests/settings.json b/test/Services/FunctionalTests/settings.json index 3b6023956..7091ec819 100644 --- a/test/Services/FunctionalTests/settings.json +++ b/test/Services/FunctionalTests/settings.json @@ -1,5 +1,6 @@ { "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;", "IdentityUrl": "http://localhost:5105", - "isTest": "true" + "isTest": "true", + "EventBusConnection": "localhost" } From c8f0776f1fffefcb957d59d0dd1add7eb4b2d66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 14 Mar 2017 18:02:28 +0100 Subject: [PATCH 15/80] Added domain events in Ordering Api --- eShopOnContainers-ServicesAndWebApps.sln | 6 + .../Commands/CreateOrderCommand.cs | 13 +- .../Commands/CreateOrderCommandHandler.cs | 42 +-- .../EventHandlers/OrderCreatedEventHandler.cs | 53 ++++ .../PaymentMethodCheckedEventHandler.cs | 33 +++ .../Controllers/OrdersController.cs | 9 +- .../AutofacModules/MediatorModule.cs | 11 +- .../20170313100034_Domain_events.Designer.cs | 244 ++++++++++++++++++ .../20170313100034_Domain_events.cs | 75 ++++++ .../OrderingContextModelSnapshot.cs | 9 +- .../AggregatesModel/BuyerAggregate/Buyer.cs | 14 +- .../OrderAggregate/IOrderRepository.cs | 5 + .../AggregatesModel/OrderAggregate/Order.cs | 34 ++- .../Ordering.Domain/Events/OrderCreated.cs | 35 +++ .../Events/PaymentMethodChecked.cs | 23 ++ .../Ordering.Domain/Ordering.Domain.csproj | 5 + .../Ordering.Domain/SeedWork/Entity.cs | 18 +- .../Ordering.Domain/SeedWork/IUnitOfWork.cs | 3 +- .../MediatorExtension.cs | 25 ++ .../OrderingContext.cs | 36 ++- .../Repositories/OrderRepository.cs | 18 +- .../Services/Ordering/OrderingScenarios.cs | 8 +- .../IdentifierCommandHandlerTest.cs | 4 +- .../Application/NewOrderCommandHandlerTest.cs | 96 +++---- .../Ordering/Domain/BuyerAggregateTest.cs | 3 +- 25 files changed, 685 insertions(+), 137 deletions(-) create mode 100644 src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.Designer.cs create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.cs create mode 100644 src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs create mode 100644 src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs create mode 100644 src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index c92dc8c7d..49fa8b892 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -41,6 +41,12 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{621E7211-58D0-45FD-9600-1CB490BD930E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "test\Services\UnitTest\UnitTest.csproj", "{7796F5D8-31FC-45A4-B673-19DE5BA194CF}" + ProjectSection(ProjectDependencies) = postProject + {A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B} + {621E7211-58D0-45FD-9600-1CB490BD930E} = {621E7211-58D0-45FD-9600-1CB490BD930E} + {FEA0C318-FFED-4D39-8781-265718CA43DD} = {FEA0C318-FFED-4D39-8781-265718CA43DD} + {F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.API", "src\Services\Identity\Identity.API\Identity.API.csproj", "{A579E108-5445-403D-A407-339AC4D1611B}" EndProject diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs index 495d887ba..1485a536f 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs @@ -2,6 +2,7 @@ using MediatR; using System.Collections.Generic; using System.Runtime.Serialization; +using System.Collections; namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands { @@ -51,6 +52,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands [DataMember] public int CardTypeId { get; private set; } + [DataMember] + public int PaymentId { get; private set; } + + [DataMember] + public int BuyerId { get; private set; } + [DataMember] public IEnumerable OrderItems => _orderItems; @@ -66,7 +73,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands public CreateOrderCommand(string city, string street, string state, string country, string zipcode, string cardNumber, string cardHolderName, DateTime cardExpiration, - string cardSecurityNumber, int cardTypeId) : this() + string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this() { City = city; Street = street; @@ -75,12 +82,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands ZipCode = zipcode; CardNumber = cardNumber; CardHolderName = cardHolderName; + CardExpiration = cardExpiration; CardSecurityNumber = cardSecurityNumber; CardTypeId = cardTypeId; CardExpiration = cardExpiration; + PaymentId = paymentId; + BuyerId = buyerId; } - public class OrderItemDTO { public int ProductId { get; set; } diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs index bf8e90ea6..6e8ebb381 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs @@ -1,6 +1,5 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands { - using Domain.AggregatesModel.BuyerAggregate; using Domain.AggregatesModel.OrderAggregate; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; @@ -24,16 +23,16 @@ public class CreateOrderCommandHandler : IAsyncRequestHandler { - private readonly IBuyerRepository _buyerRepository; private readonly IOrderRepository _orderRepository; private readonly IIdentityService _identityService; + private readonly IMediator _mediator; // Using DI to inject infrastructure persistence Repositories - public CreateOrderCommandHandler(IBuyerRepository buyerRepository, IOrderRepository orderRepository, IIdentityService identityService) + public CreateOrderCommandHandler(IMediator mediator, IOrderRepository orderRepository, IIdentityService identityService) { - _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } public async Task Handle(CreateOrderCommand message) @@ -42,45 +41,18 @@ // DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root // methods and constructor so validations, invariants and business logic // make sure that consistency is preserved across the whole aggregate - - var cardTypeId = message.CardTypeId != 0 ? message.CardTypeId : 1; - - var buyerGuid = _identityService.GetUserIdentity(); - var buyer = await _buyerRepository.FindAsync(buyerGuid); - - if (buyer == null) - { - buyer = new Buyer(buyerGuid); - } - - var payment = buyer.AddPaymentMethod(cardTypeId, - $"Payment Method on {DateTime.UtcNow}", - message.CardNumber, - message.CardSecurityNumber, - message.CardHolderName, - message.CardExpiration); - - _buyerRepository.Add(buyer); - - await _buyerRepository.UnitOfWork - .SaveChangesAsync(); - - // Create the Order AggregateRoot - // DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root - // methods and constructor so validations, invariants and business logic - // make sure that consistency is preserved across the whole aggregate - - var order = new Order(buyer.Id, payment.Id, new Address(message.Street, message.City, message.State, message.Country, message.ZipCode)); + var address = new Address(message.Street, message.City, message.State, message.Country, message.ZipCode); + var order = new Order(address , message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration); foreach (var item in message.OrderItems) { order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units); } - _orderRepository.Add(order); + _orderRepository.Add(order); var result = await _orderRepository.UnitOfWork - .SaveChangesAsync(); + .SaveEntitiesAsync(); return result > 0; diff --git a/src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs new file mode 100644 index 000000000..422a61c9b --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs @@ -0,0 +1,53 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; +using Microsoft.Extensions.Logging; +using Ordering.Domain.Events; +using System; +using System.Threading.Tasks; + +namespace Ordering.API.Application.EventHandlers +{ + public class OrderCreatedEventHandler : IAsyncNotificationHandler + { + private readonly ILoggerFactory _logger; + private readonly IBuyerRepository _buyerRepository; + private readonly IIdentityService _identityService; + + public OrderCreatedEventHandler(ILoggerFactory logger, IBuyerRepository buyerRepository, IIdentityService identityService) + { + _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); + _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Handle(OrderCreated orderNotification) + { + var cardTypeId = orderNotification.CardTypeId != 0 ? orderNotification.CardTypeId : 1; + + var buyerGuid = _identityService.GetUserIdentity(); + var buyer = await _buyerRepository.FindAsync(buyerGuid); + + if (buyer == null) + { + buyer = new Buyer(buyerGuid); + } + + var payment = buyer.AddPaymentMethod(cardTypeId, + $"Payment Method on {DateTime.UtcNow}", + orderNotification.CardNumber, + orderNotification.CardSecurityNumber, + orderNotification.CardHolderName, + orderNotification.CardExpiration, + orderNotification.Order.Id); + + _buyerRepository.Add(buyer); + + await _buyerRepository.UnitOfWork + .SaveEntitiesAsync(); + + _logger.CreateLogger(nameof(OrderCreatedEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}."); + + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs new file mode 100644 index 000000000..b72b89363 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs @@ -0,0 +1,33 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.Extensions.Logging; +using Ordering.Domain.Events; +using System; +using System.Threading.Tasks; + +namespace Ordering.API.Application.EventHandlers +{ + public class PaymentMethodCheckedEventHandler : IAsyncNotificationHandler + { + private readonly IOrderRepository _orderRepository; + private readonly ILoggerFactory _logger; + public PaymentMethodCheckedEventHandler(IOrderRepository orderRepository, ILoggerFactory logger) + { + _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Handle(PaymentMethodChecked paymentMethodNotification) + { + var orderToUpdate = await _orderRepository.GetAsync(paymentMethodNotification.OrderId); + orderToUpdate.SetBuyerId(paymentMethodNotification.Buyer.Id); + orderToUpdate.SetPaymentId(paymentMethodNotification.Payment.Id); + + await _orderRepository.UnitOfWork + .SaveEntitiesAsync(); + + _logger.CreateLogger(nameof(PaymentMethodCheckedEventHandler)) + .LogTrace($"Order with Id: {paymentMethodNotification.OrderId} has been successfully updated with a new payment method id: { paymentMethodNotification.Payment.Id }"); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index d01150b9c..05f27eeec 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -1,5 +1,4 @@ using MediatR; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; @@ -28,19 +27,19 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers [Route("new")] [HttpPost] - public async Task CreateOrder([FromBody]CreateOrderCommand createOrderCommand, [FromHeader(Name = "x-requestid")] string requestId) + public async Task CreateOrder([FromBody]CreateOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId) { bool result = false; if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) { - var requestCreateOrder = new IdentifiedCommand(createOrderCommand, guid); + var requestCreateOrder = new IdentifiedCommand(command, guid); result = await _mediator.SendAsync(requestCreateOrder); } else { // If no x-requestid header is found we process the order anyway. This is just temporary to not break existing clients // that aren't still updated. When all clients were updated this could be removed. - result = await _mediator.SendAsync(createOrderCommand); + result = await _mediator.SendAsync(command); } if (result) @@ -82,7 +81,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers var cardTypes = await _orderQueries.GetCardTypes(); return Ok(cardTypes); - } + } } } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index e5754eeca..8585b8aa9 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -3,6 +3,8 @@ using Autofac.Core; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Decorators; +using Ordering.API.Application.EventHandlers; +using Ordering.Domain.Events; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -14,12 +16,17 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof protected override void Load(ContainerBuilder builder) { builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly) - .AsImplementedInterfaces(); + .AsImplementedInterfaces(); builder.RegisterAssemblyTypes(typeof(CreateOrderCommand).GetTypeInfo().Assembly) .As(o => o.GetInterfaces() .Where(i => i.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>))) .Select(i => new KeyedService("IAsyncRequestHandler", i))); + + builder + .RegisterAssemblyTypes(typeof(OrderCreatedEventHandler).GetTypeInfo().Assembly) + .Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>))) + .AsImplementedInterfaces(); builder.Register(context => { @@ -37,7 +44,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof builder.RegisterGenericDecorator(typeof(LogDecorator<,>), typeof(IAsyncRequestHandler<,>), - "IAsyncRequestHandler"); + "IAsyncRequestHandler"); } } } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.Designer.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.Designer.cs new file mode 100644 index 000000000..e06efc622 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.Designer.cs @@ -0,0 +1,244 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; + +namespace Ordering.API.Migrations +{ + [DbContext(typeof(OrderingContext))] + [Migration("20170313100034_Domain_events")] + partial class Domain_events + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752") + .HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq") + .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("IdentityGuid") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("IdentityGuid") + .IsUnique(); + + b.ToTable("buyers","ordering"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b => + { + b.Property("Id") + .HasDefaultValue(1); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.ToTable("cardtypes","ordering"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq") + .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Alias") + .IsRequired() + .HasMaxLength(200); + + b.Property("BuyerId"); + + b.Property("CardHolderName") + .IsRequired() + .HasMaxLength(200); + + b.Property("CardNumber") + .IsRequired() + .HasMaxLength(25); + + b.Property("CardTypeId"); + + b.Property("Expiration"); + + b.HasKey("Id"); + + b.HasIndex("BuyerId"); + + b.HasIndex("CardTypeId"); + + b.ToTable("paymentmethods","ordering"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("City"); + + b.Property("Country"); + + b.Property("State"); + + b.Property("Street"); + + b.Property("ZipCode"); + + b.HasKey("Id"); + + b.ToTable("address","ordering"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "orderseq") + .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("AddressId"); + + b.Property("BuyerId"); + + b.Property("OrderDate"); + + b.Property("OrderStatusId"); + + b.Property("PaymentMethodId"); + + b.HasKey("Id"); + + b.HasIndex("AddressId"); + + b.HasIndex("BuyerId"); + + b.HasIndex("OrderStatusId"); + + b.HasIndex("PaymentMethodId"); + + b.ToTable("orders","ordering"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Discount"); + + b.Property("OrderId"); + + b.Property("PictureUrl"); + + b.Property("ProductId"); + + b.Property("ProductName") + .IsRequired(); + + b.Property("UnitPrice"); + + b.Property("Units"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("orderItems","ordering"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b => + { + b.Property("Id") + .HasDefaultValue(1); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.ToTable("orderstatus","ordering"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name") + .IsRequired(); + + b.Property("Time"); + + b.HasKey("Id"); + + b.ToTable("requests","ordering"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer") + .WithMany("PaymentMethods") + .HasForeignKey("BuyerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType") + .WithMany() + .HasForeignKey("CardTypeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address") + .WithMany() + .HasForeignKey("AddressId"); + + b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer") + .WithMany() + .HasForeignKey("BuyerId"); + + b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus") + .WithMany() + .HasForeignKey("OrderStatusId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", "PaymentMethod") + .WithMany() + .HasForeignKey("PaymentMethodId"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.cs new file mode 100644 index 000000000..d68cedd80 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ordering.API.Migrations +{ + public partial class Domain_events : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_orders_buyers_BuyerId", + schema: "ordering", + table: "orders"); + + migrationBuilder.AlterColumn( + name: "PaymentMethodId", + schema: "ordering", + table: "orders", + nullable: true, + oldClrType: typeof(int)); + + migrationBuilder.AlterColumn( + name: "BuyerId", + schema: "ordering", + table: "orders", + nullable: true, + oldClrType: typeof(int)); + + migrationBuilder.AddForeignKey( + name: "FK_orders_buyers_BuyerId", + schema: "ordering", + table: "orders", + column: "BuyerId", + principalSchema: "ordering", + principalTable: "buyers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_orders_buyers_BuyerId", + schema: "ordering", + table: "orders"); + + migrationBuilder.AlterColumn( + name: "PaymentMethodId", + schema: "ordering", + table: "orders", + nullable: false, + oldClrType: typeof(int), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "BuyerId", + schema: "ordering", + table: "orders", + nullable: false, + oldClrType: typeof(int), + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_orders_buyers_BuyerId", + schema: "ordering", + table: "orders", + column: "BuyerId", + principalSchema: "ordering", + principalTable: "buyers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs index 654f93d6d..7af892470 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs @@ -119,13 +119,13 @@ namespace Ordering.API.Migrations b.Property("AddressId"); - b.Property("BuyerId"); + b.Property("BuyerId"); b.Property("OrderDate"); b.Property("OrderStatusId"); - b.Property("PaymentMethodId"); + b.Property("PaymentMethodId"); b.HasKey("Id"); @@ -183,7 +183,7 @@ namespace Ordering.API.Migrations b.ToTable("orderstatus","ordering"); }); - modelBuilder.Entity("Ordering.Infrastructure.ClientRequest", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b => { b.Property("Id") .ValueGeneratedOnAdd(); @@ -219,8 +219,7 @@ namespace Ordering.API.Migrations b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer") .WithMany() - .HasForeignKey("BuyerId") - .OnDelete(DeleteBehavior.Cascade); + .HasForeignKey("BuyerId"); b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus") .WithMany() diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs index 6bd6853b7..64acbf7b7 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs @@ -1,6 +1,6 @@ using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; +using Ordering.Domain.Events; using System; -using System.Collections; using System.Collections.Generic; using System.Linq; @@ -11,7 +11,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B { public string IdentityGuid { get; private set; } - private List _paymentMethods; + private List _paymentMethods; public IEnumerable PaymentMethods => _paymentMethods.AsReadOnly(); @@ -22,16 +22,18 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B public Buyer(string identity) : this() { IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity)); - } - public PaymentMethod AddPaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration) + public PaymentMethod AddPaymentMethod( + int cardTypeId, string alias, string cardNumber, + string securityNumber, string cardHolderName, DateTime expiration, int orderId) { var existingPayment = _paymentMethods.Where(p => p.IsEqualTo(cardTypeId, cardNumber, expiration)) .SingleOrDefault(); if (existingPayment != null) { + AddEvent(new PaymentMethodChecked(this, existingPayment, orderId)); return existingPayment; } else @@ -39,9 +41,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration); _paymentMethods.Add(payment); - + AddEvent(new PaymentMethodChecked(this, payment, orderId)); return payment; } - } + } } } diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/IOrderRepository.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/IOrderRepository.cs index 81869a457..dbef153db 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/IOrderRepository.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/IOrderRepository.cs @@ -1,4 +1,5 @@ using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; +using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate { @@ -8,5 +9,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O public interface IOrderRepository : IRepository where T : IAggregateRoot { Order Add(Order order); + + Task GetAsync(int orderId); + + void Update(Order order); } } diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index fecc53d44..a1bb76cee 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -1,5 +1,7 @@ -using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; +using Ordering.Domain.Events; using System; using System.Collections.Generic; using System.Linq; @@ -17,7 +19,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O public Address Address { get; private set; } public Buyer Buyer { get; private set; } - private int _buyerId; + private int? _buyerId; public OrderStatus OrderStatus { get; private set; } private int _orderStatusId; @@ -36,11 +38,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O //https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx public PaymentMethod PaymentMethod { get; private set; } - private int _paymentMethodId; + private int? _paymentMethodId; protected Order() { } - public Order(int buyerId, int paymentMethodId, Address address) + public Order(Address address, int cardTypeId, string cardNumber, string cardSecurityNumber, + string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) { _orderItems = new List(); _buyerId = buyerId; @@ -48,6 +51,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O _orderStatusId = OrderStatus.InProcess.Id; _orderDate = DateTime.UtcNow; Address = address; + AddCreatedOrderEvent(cardTypeId, cardNumber, + cardSecurityNumber, cardHolderName, cardExpiration); } // DDD Patterns comment @@ -74,9 +79,28 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O //add validated new order item var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units); - _orderItems.Add(orderItem); } } + + public void SetPaymentId(int id) + { + _paymentMethodId = id; + } + + public void SetBuyerId(int id) + { + _buyerId = id; + } + + private void AddCreatedOrderEvent(int cardTypeId, string cardNumber, + string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) + { + var @orderCreatedEvent = new OrderCreated( + this, cardTypeId, cardNumber, cardSecurityNumber, + cardHolderName, cardExpiration); + + AddEvent(@orderCreatedEvent); + } } } diff --git a/src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs b/src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs new file mode 100644 index 000000000..489cc23d0 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs @@ -0,0 +1,35 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ordering.Domain.Events +{ + /// + /// Event used when an order is created + /// + public class OrderCreated + : IAsyncNotification + { + public int CardTypeId { get; private set; } + public string CardNumber { get; private set; } + public string CardSecurityNumber { get; private set; } + public string CardHolderName { get; private set; } + public DateTime CardExpiration { get; private set; } + public Order Order { get; private set; } + + public OrderCreated(Order order, + int cardTypeId, string cardNumber, + string cardSecurityNumber, string cardHolderName, + DateTime cardExpiration) + { + Order = order; + CardTypeId = cardTypeId; + CardNumber = cardNumber; + CardSecurityNumber = cardSecurityNumber; + CardHolderName = cardHolderName; + CardExpiration = cardExpiration; + } + } +} diff --git a/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs b/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs new file mode 100644 index 000000000..25c8d8111 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs @@ -0,0 +1,23 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ordering.Domain.Events +{ + public class PaymentMethodChecked + : IAsyncNotification + { + public Buyer Buyer { get; private set; } + public PaymentMethod Payment { get; private set; } + public int OrderId { get; private set; } + + public PaymentMethodChecked(Buyer buyer, PaymentMethod payment, int orderId) + { + Buyer = buyer; + Payment = payment; + OrderId = orderId; + } + } +} diff --git a/src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj b/src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj index b96e1884a..ac887b0d4 100644 --- a/src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj +++ b/src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj @@ -11,4 +11,9 @@ false + + + + + diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs index 2530d8403..e2c602300 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs @@ -1,7 +1,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork { using System; - + using MediatR; + using System.Collections.Generic; public abstract class Entity { @@ -9,6 +10,8 @@ int? _requestedHashCode; int _Id; + private List _events; + public virtual int Id { get @@ -21,6 +24,19 @@ } } + public List Events => _events; + public void AddEvent(IAsyncNotification eventItem) + { + _events = _events ?? new List(); + _events.Add(eventItem); + } + + public void RemoveEvent(IAsyncNotification eventItem) + { + if (_events is null) return; + _events.Remove(eventItem); + } + public bool IsTransient() { return this.Id == default(Int32); diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs index 85a8efbb0..581f905a6 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs @@ -5,7 +5,8 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork { public interface IUnitOfWork : IDisposable - { + { Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs b/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs new file mode 100644 index 000000000..5949f00b4 --- /dev/null +++ b/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs @@ -0,0 +1,25 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.Infrastructure +{ + public static class MediatorExtension + { + public static async Task RaiseDomainEventsAsync(this IMediator mediator, OrderingContext ctx) + { + var domainEntities = ctx.ChangeTracker.Entries().Where(x => x.Entity.Events != null && x.Entity.Events.Any()); + var domainEvents = domainEntities.SelectMany(x => x.Entity.Events).ToList(); + domainEntities.ToList().ForEach(entity => entity.Entity.Events.Clear()); + + var tasks = domainEvents + .Select(async (domainEvent) => { + await mediator.PublishAsync(domainEvent); + }); + + await Task.WhenAll(tasks); + } + } +} diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 7a9d192e4..504ed0ab7 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -1,10 +1,14 @@ -using Microsoft.EntityFrameworkCore; +using MediatR; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; +using Ordering.Infrastructure; using System; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure { @@ -26,7 +30,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure public DbSet OrderStatus { get; set; } - public OrderingContext(DbContextOptions options) : base(options) { } + private readonly IMediator _mediator; + + public OrderingContext(DbContextOptions options, IMediator mediator) : base(options) + { + _mediator = mediator; + } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -38,7 +47,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure modelBuilder.Entity(ConfigureOrderItems); modelBuilder.Entity(ConfigureCardTypes); modelBuilder.Entity(ConfigureOrderStatus); - modelBuilder.Entity(ConfigureBuyer); + modelBuilder.Entity(ConfigureBuyer); } private void ConfigureRequests(EntityTypeBuilder requestConfiguration) @@ -65,6 +74,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure buyerConfiguration.HasKey(b => b.Id); + buyerConfiguration.Ignore(b => b.Events); + buyerConfiguration.Property(b => b.Id) .ForSqlServerUseSequenceHiLo("buyerseq", DEFAULT_SCHEMA); @@ -91,6 +102,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure paymentConfiguration.HasKey(b => b.Id); + paymentConfiguration.Ignore(b => b.Events); + paymentConfiguration.Property(b => b.Id) .ForSqlServerUseSequenceHiLo("paymentseq", DEFAULT_SCHEMA); @@ -126,13 +139,15 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure orderConfiguration.HasKey(o => o.Id); + orderConfiguration.Ignore(b => b.Events); + orderConfiguration.Property(o => o.Id) .ForSqlServerUseSequenceHiLo("orderseq", DEFAULT_SCHEMA); orderConfiguration.Property("OrderDate").IsRequired(); - orderConfiguration.Property("BuyerId").IsRequired(); + orderConfiguration.Property("BuyerId").IsRequired(false); orderConfiguration.Property("OrderStatusId").IsRequired(); - orderConfiguration.Property("PaymentMethodId").IsRequired(); + orderConfiguration.Property("PaymentMethodId").IsRequired(false); var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems)); // DDD Patterns comment: @@ -142,10 +157,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure orderConfiguration.HasOne(o => o.PaymentMethod) .WithMany() .HasForeignKey("PaymentMethodId") + .IsRequired(false) .OnDelete(DeleteBehavior.Restrict); orderConfiguration.HasOne(o => o.Buyer) .WithMany() + .IsRequired(false) .HasForeignKey("BuyerId"); orderConfiguration.HasOne(o => o.OrderStatus) @@ -159,6 +176,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure orderItemConfiguration.HasKey(o => o.Id); + orderItemConfiguration.Ignore(b => b.Events); + orderItemConfiguration.Property(o => o.Id) .ForSqlServerUseSequenceHiLo("orderitemseq"); @@ -215,5 +234,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure .HasMaxLength(200) .IsRequired(); } + + public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await base.SaveChangesAsync(); + await _mediator.RaiseDomainEventsAsync(this); + return result; + } } } diff --git a/src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs b/src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs index ecdc705dd..fec918003 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs @@ -1,6 +1,8 @@ -using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using System; +using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories { @@ -24,8 +26,18 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor public Order Add(Order order) { - return _context.Orders.Add(order) - .Entity; + return _context.Orders.Add(order).Entity; + + } + + public async Task GetAsync(int orderId) + { + return await _context.Orders.FindAsync(orderId); + } + + public void Update(Order order) + { + _context.Entry(order).State = EntityState.Modified; } } } diff --git a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs index a34e0d7c0..46b5cb2b0 100644 --- a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs @@ -75,7 +75,9 @@ country: "USA", state: "WA", street: "One way", - zipcode: "zipcode" + zipcode: "zipcode", + paymentId: 1, + buyerId: 3 ); order.AddOrderItem(new OrderItemDTO() @@ -101,7 +103,9 @@ country: "USA", state: "WA", street: "One way", - zipcode: "zipcode" + zipcode: "zipcode", + paymentId: 1, + buyerId: 3 ); return JsonConvert.SerializeObject(order); diff --git a/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs index a8474427b..63e2e01e1 100644 --- a/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs @@ -79,7 +79,9 @@ namespace UnitTest.Ordering.Application cardExpiration: args != null && args.ContainsKey("cardExpiration") ? (DateTime)args["cardExpiration"] : DateTime.MinValue, cardSecurityNumber: args != null && args.ContainsKey("cardSecurityNumber") ? (string)args["cardSecurityNumber"] : "123", cardHolderName: args != null && args.ContainsKey("cardHolderName") ? (string)args["cardHolderName"] : "XXX", - cardTypeId: args != null && args.ContainsKey("cardTypeId") ? (int)args["cardTypeId"] : 0); + cardTypeId: args != null && args.ContainsKey("cardTypeId") ? (int)args["cardTypeId"] : 0, + paymentId: args != null && args.ContainsKey("paymentId") ? (int)args["paymentId"] : 0, + buyerId: args != null && args.ContainsKey("buyerId") ? (int)args["buyerId"] : 0); } } } diff --git a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs index a048dc975..fe39ffb6c 100644 --- a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs @@ -10,21 +10,22 @@ using System.Threading.Tasks; namespace UnitTest.Ordering.Application { + using MediatR; using System.Collections; using System.Collections.Generic; using Xunit; public class NewOrderRequestHandlerTest { - private readonly Mock> _buyerRepositoryMock; private readonly Mock> _orderRepositoryMock; private readonly Mock _identityServiceMock; + private readonly Mock _mediator; public NewOrderRequestHandlerTest() { - _buyerRepositoryMock = new Mock>(); _orderRepositoryMock = new Mock>(); _identityServiceMock = new Mock(); + _mediator = new Mock(); } [Fact] @@ -37,21 +38,15 @@ namespace UnitTest.Ordering.Application { ["cardExpiration"] = DateTime.Now.AddYears(1) }); // Arrange - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId)) - .Returns(Task.FromResult(FakeBuyer())); + _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) + .Returns(Task.FromResult(FakeOrder())); - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _orderRepositoryMock.Setup(or => or.Add(FakeOrder())) - .Returns(FakeOrder()); - - _orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken))) + _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) .Returns(Task.FromResult(1)); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); //Act - var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); var result = await handler.Handle(fakeOrderCmd); //Assert @@ -66,19 +61,16 @@ namespace UnitTest.Ordering.Application var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary { ["cardExpiration"] = DateTime.Now.AddYears(1) }); - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId)) - .Returns(Task.FromResult(FakeBuyer())); + _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) + .Returns(Task.FromResult(FakeOrder())); - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) + _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) .Returns(Task.FromResult(1)); - _orderRepositoryMock.Setup(or => or.Add(FakeOrder())).Returns(FakeOrder()); - _orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(0)); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); //Act - var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); var result = await handler.Handle(fakeOrderCmd); //Assert @@ -95,21 +87,15 @@ namespace UnitTest.Ordering.Application { ["cardExpiration"] = DateTime.Now.AddYears(-1) }); // Arrange - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId)) - .Returns(Task.FromResult(FakeBuyer())); + _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) + .Returns(Task.FromResult(FakeOrder())); - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _orderRepositoryMock.Setup(or => or.Add(FakeOrder())) - .Returns(FakeOrder()); - - _orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken))) + _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) .Returns(Task.FromResult(1)); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); //Act - var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); //Assert await Assert.ThrowsAsync(async () => await handler.Handle(fakeOrderCmd)); @@ -128,21 +114,15 @@ namespace UnitTest.Ordering.Application }); // Arrange - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId)) - .Returns(Task.FromResult(FakeBuyer())); + _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) + .Returns(Task.FromResult(FakeOrder())); - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _orderRepositoryMock.Setup(or => or.Add(FakeOrder())) - .Returns(FakeOrder()); - - _orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken))) + _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) .Returns(Task.FromResult(1)); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); //Act - var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); //Assert await Assert.ThrowsAsync(async () => await handler.Handle(fakeOrderCmd)); @@ -161,21 +141,15 @@ namespace UnitTest.Ordering.Application }); // Arrange - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId)) - .Returns(Task.FromResult(FakeBuyer())); + _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) + .Returns(Task.FromResult(FakeOrder())); - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _orderRepositoryMock.Setup(or => or.Add(FakeOrder())) - .Returns(FakeOrder()); - - _orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken))) + _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) .Returns(Task.FromResult(1)); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); //Act - var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); //Assert await Assert.ThrowsAsync(async () => await handler.Handle(fakeOrderCmd)); @@ -185,7 +159,7 @@ namespace UnitTest.Ordering.Application public async Task Handle_throws_exception_when_no_cardNumber() { - var buyerId = "1234"; + var orderId = "1234"; var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary { @@ -194,21 +168,15 @@ namespace UnitTest.Ordering.Application }); // Arrange - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId)) - .Returns(Task.FromResult(FakeBuyer())); + _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) + .Returns(Task.FromResult(FakeOrder())); - _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) + _orderRepositoryMock.Setup(orderRepo => orderRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) .Returns(Task.FromResult(1)); - _orderRepositoryMock.Setup(or => or.Add(FakeOrder())) - .Returns(FakeOrder()); - - _orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); + _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(orderId); //Act - var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); //Assert await Assert.ThrowsAsync(async () => await handler.Handle(fakeOrderCmd)); @@ -228,7 +196,7 @@ namespace UnitTest.Ordering.Application private Order FakeOrder() { - return new Order(1, 1, new Address("street", "city", "state", "country", "zipcode")); + return new Order(new Address("street", "city", "state", "country", "zipcode"), 1, "12", "111", "fakeName", DateTime.Now.AddYears(1)); } private CreateOrderCommand FakeOrderRequestWithBuyer(Dictionary args = null) @@ -243,7 +211,9 @@ namespace UnitTest.Ordering.Application cardExpiration: args != null && args.ContainsKey("cardExpiration") ? (DateTime)args["cardExpiration"] : DateTime.MinValue, cardSecurityNumber: args != null && args.ContainsKey("cardSecurityNumber") ? (string)args["cardSecurityNumber"] : "123", cardHolderName: args != null && args.ContainsKey("cardHolderName") ? (string)args["cardHolderName"] : "XXX", - cardTypeId: args != null && args.ContainsKey("cardTypeId") ? (int)args["cardTypeId"] : 0); + cardTypeId: args != null && args.ContainsKey("cardTypeId") ? (int)args["cardTypeId"] : 0, + paymentId: args != null && args.ContainsKey("paymentId") ? (int)args["paymentId"] : 0, + buyerId: args != null && args.ContainsKey("buyerId") ? (int)args["buyerId"] : 0); } } } diff --git a/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs index 861ff378a..1ffb8f6ca 100644 --- a/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs @@ -40,11 +40,12 @@ public class BuyerAggregateTest var securityNumber = "1234"; var cardHolderName = "FakeHolderNAme"; var expiration = DateTime.Now.AddYears(1); + var orderId = 1; var identity = new Guid().ToString(); var fakeBuyerItem = new Buyer(identity); //Act - var result = fakeBuyerItem.AddPaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration); + var result = fakeBuyerItem.AddPaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration, orderId); //Assert Assert.NotNull(result); From 9669d433a674c35384f5bd6245ef1238338cbee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 14 Mar 2017 18:58:27 +0100 Subject: [PATCH 16/80] Added unit tests for entity domain events --- .../Ordering/Domain/BuyerAggregateTest.cs | 21 ++++++ .../Ordering/Domain/OrderAggregateTest.cs | 72 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs index 1ffb8f6ca..2cf035205 100644 --- a/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs @@ -103,4 +103,25 @@ public class BuyerAggregateTest //Assert Assert.True(result); } + + [Fact] + public void Add_new_PaymentMethod_raises_new_event() + { + //Arrange + var alias = "fakeAlias"; + var orderId = 1; + var cardTypeId = 5; + var cardNumber = "12"; + var cardSecurityNumber = "123"; + var cardHolderName = "FakeName"; + var cardExpiration = DateTime.Now.AddYears(1); + var expectedResult = 1; + + //Act + var fakeBuyer = new Buyer(Guid.NewGuid().ToString()); + fakeBuyer.AddPaymentMethod(cardTypeId, alias, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration, orderId); + + //Assert + Assert.Equal(fakeBuyer.Events.Count, expectedResult); + } } \ No newline at end of file diff --git a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs index 5aefbf0c6..dab34f528 100644 --- a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs @@ -1,4 +1,5 @@ using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Ordering.Domain.Events; using System; using Xunit; @@ -90,4 +91,75 @@ public class OrderAggregateTest //Assert Assert.Throws(() => fakeOrderItem.AddUnits(-1)); } + + [Fact] + public void Add_new_Order_raises_new_event() + { + //Arrange + var street = "fakeStreet"; + var city = "FakeCity"; + var state = "fakeState"; + var country = "fakeCountry"; + var zipcode = "FakeZipCode"; + var cardTypeId = 5; + var cardNumber = "12"; + var cardSecurityNumber = "123"; + var cardHolderName = "FakeName"; + var cardExpiration = DateTime.Now.AddYears(1); + var expectedResult = 1; + + //Act + var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); + + //Assert + Assert.Equal(fakeOrder.Events.Count, expectedResult); + } + + [Fact] + public void Add_event_Order_explicitly_raises_new_event() + { + //Arrange + var street = "fakeStreet"; + var city = "FakeCity"; + var state = "fakeState"; + var country = "fakeCountry"; + var zipcode = "FakeZipCode"; + var cardTypeId = 5; + var cardNumber = "12"; + var cardSecurityNumber = "123"; + var cardHolderName = "FakeName"; + var cardExpiration = DateTime.Now.AddYears(1); + var expectedResult = 2; + + //Act + var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); + fakeOrder.AddEvent(new OrderCreated(fakeOrder,cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration)); + //Assert + Assert.Equal(fakeOrder.Events.Count, expectedResult); + } + + [Fact] + public void Remove_event_Order_explicitly() + { + //Arrange + var street = "fakeStreet"; + var city = "FakeCity"; + var state = "fakeState"; + var country = "fakeCountry"; + var zipcode = "FakeZipCode"; + var cardTypeId = 5; + var cardNumber = "12"; + var cardSecurityNumber = "123"; + var cardHolderName = "FakeName"; + var cardExpiration = DateTime.Now.AddYears(1); + var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); + var @fakeEvent = new OrderCreated(fakeOrder, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); + var expectedResult = 1; + + //Act + fakeOrder.AddEvent(@fakeEvent); + fakeOrder.RemoveEvent(@fakeEvent); + //Assert + Assert.Equal(fakeOrder.Events.Count, expectedResult); + } } \ No newline at end of file From dbb4be8e4780d9a84e4636bf9c3430a31e2cf7b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 14 Mar 2017 19:05:22 +0100 Subject: [PATCH 17/80] Update ordering scenarios --- .../Services/Ordering/OrderingScenarios.cs | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs index b1b52a098..f15797793 100644 --- a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs @@ -70,26 +70,6 @@ namespace FunctionalTests.Services.Ordering }); return JsonConvert.SerializeObject(order); - } - - string BuildOrderWithInvalidExperationTime() - { - var order = new CreateOrderCommand( - cardExpiration: DateTime.UtcNow.AddYears(-1), - cardNumber: "5145-555-5555", - cardHolderName: "Jhon Senna", - cardSecurityNumber: "232", - cardTypeId: 1, - city: "Redmon", - country: "USA", - state: "WA", - street: "One way", - zipcode: "zipcode", - paymentId: 1, - buyerId: 3 - ); - - return JsonConvert.SerializeObject(order); - } + } } } From 0c4f8a49dd246bf37d763f7feab531024d7b2bd6 Mon Sep 17 00:00:00 2001 From: "DESKTOP-V1VLQ15\\dsanz" Date: Wed, 15 Mar 2017 14:57:08 +0100 Subject: [PATCH 18/80] WebMVC and WebSPA show now a changed price in the basket list --- .../Shared/Components/CartList/Default.cshtml | 16 +++++--- .../wwwroot/css/basket/basket.component.css | 12 ++++-- .../modules/basket/basket.component.html | 37 +++++++++++-------- .../modules/basket/basket.component.scss | 11 ++++++ .../modules/shared/models/basketItem.model.ts | 1 + .../shared/services/basket.wrapper.service.ts | 3 +- 6 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml b/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml index 33e62bafb..6ee70b035 100644 --- a/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml +++ b/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml @@ -17,7 +17,7 @@ { var item = Model.Items[i]; -
+
@@ -30,13 +30,19 @@
$ @Math.Round(item.Quantity * item.UnitPrice, 2)
+
+ +
+
+ +
@if (item.OldUnitPrice != 0) { -
- The price of the item has changed. Old price was @item.OldUnitPrice $ -
+ } -
+ +
+ } diff --git a/src/Web/WebMVC/wwwroot/css/basket/basket.component.css b/src/Web/WebMVC/wwwroot/css/basket/basket.component.css index fa3a8ad0e..5649ed5b0 100644 --- a/src/Web/WebMVC/wwwroot/css/basket/basket.component.css +++ b/src/Web/WebMVC/wwwroot/css/basket/basket.component.css @@ -68,7 +68,11 @@ transition: all 0.35s; } - .esh-basket-checkout:hover { - background-color: #4a760f; - transition: all 0.35s; - } +.esh-basket-checkout:hover { + background-color: #4a760f; + transition: all 0.35s; +} + +.esh-basket-margin12{ + margin-left: 12px; +} diff --git a/src/Web/WebSPA/Client/modules/basket/basket.component.html b/src/Web/WebSPA/Client/modules/basket/basket.component.html index 3dece543b..7cab7221a 100644 --- a/src/Web/WebSPA/Client/modules/basket/basket.component.html +++ b/src/Web/WebSPA/Client/modules/basket/basket.component.html @@ -10,23 +10,28 @@
Cost
-
+
+
-
- -
-
{{item.productName}}
-
$ {{item.unitPrice}}
-
- -
-
$ {{item.unitPrice * item.quantity}}
-
+
+ +
+
{{item.productName}}
+
$ {{item.unitPrice}}
+
+ +
+
$ {{item.unitPrice * item.quantity}}
+
+
+
+ +
+
diff --git a/src/Web/WebSPA/Client/modules/basket/basket.component.scss b/src/Web/WebSPA/Client/modules/basket/basket.component.scss index c338183dd..c0e6c61fd 100644 --- a/src/Web/WebSPA/Client/modules/basket/basket.component.scss +++ b/src/Web/WebSPA/Client/modules/basket/basket.component.scss @@ -1,5 +1,9 @@ @import '../variables'; +@mixin margin-left($distance) { + margin-left: $distance; +} + .esh-basket { min-height: 80vh; @@ -26,6 +30,10 @@ border-color: transparent; } } + + &-margin-left1 { + @include margin-left(1px); + } } $item-height: 8rem; @@ -76,3 +84,6 @@ } } } + + + diff --git a/src/Web/WebSPA/Client/modules/shared/models/basketItem.model.ts b/src/Web/WebSPA/Client/modules/shared/models/basketItem.model.ts index 8701ae1d6..44c5716a5 100644 --- a/src/Web/WebSPA/Client/modules/shared/models/basketItem.model.ts +++ b/src/Web/WebSPA/Client/modules/shared/models/basketItem.model.ts @@ -3,6 +3,7 @@ export interface IBasketItem { productId: string; productName: string; unitPrice: number; + oldUnitPrice: number; quantity: number; pictureUrl: string; } diff --git a/src/Web/WebSPA/Client/modules/shared/services/basket.wrapper.service.ts b/src/Web/WebSPA/Client/modules/shared/services/basket.wrapper.service.ts index 05924c0f5..3017a30f3 100644 --- a/src/Web/WebSPA/Client/modules/shared/services/basket.wrapper.service.ts +++ b/src/Web/WebSPA/Client/modules/shared/services/basket.wrapper.service.ts @@ -27,7 +27,8 @@ export class BasketWrapperService { productName: item.name, quantity: 1, unitPrice: item.price, - id: '' + id: '', + oldUnitPrice: 0 }; this.addItemToBasketSource.next(basket); From d35cefb887c87988edf976bdef315df277560b3b Mon Sep 17 00:00:00 2001 From: dsanz Date: Wed, 15 Mar 2017 15:04:13 +0100 Subject: [PATCH 19/80] Fix merge issue. --- .../Ordering/Ordering.API/Controllers/OrdersController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index 07009cafd..4121e3214 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; From f12f0c4ffa99be287813d74ae539263eaf8b506a Mon Sep 17 00:00:00 2001 From: dsanz Date: Wed, 15 Mar 2017 15:18:06 +0100 Subject: [PATCH 20/80] Fix merge issue --- .../Services/Ordering/OrderingScenarios.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs index 5fe9d29db..ba77a3e7c 100644 --- a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs @@ -51,6 +51,10 @@ } } + //public CreateOrderCommand(string city, string street, string state, string country, string zipcode, + // string cardNumber, string cardHolderName, DateTime cardExpiration, + // string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this() + string BuildOrder() { var order = new CreateOrderCommand( @@ -63,7 +67,9 @@ country: "USA", state: "WA", street: "One way", - zipcode: "zipcode" + zipcode: "zipcode", + paymentId: 1, + buyerId: 1 ); order.AddOrderItem(new OrderItemDTO() @@ -89,7 +95,9 @@ country: "USA", state: "WA", street: "One way", - zipcode: "zipcode" + zipcode: "zipcode", + buyerId: 1, + paymentId:1 ); return JsonConvert.SerializeObject(order); From faf4ada8ac8c35f21830e68073b7a14dcb2d017a Mon Sep 17 00:00:00 2001 From: dsanz Date: Wed, 15 Mar 2017 15:32:13 +0100 Subject: [PATCH 21/80] Fix unit tests --- .../Controllers/CatalogController.cs | 13 +- .../Application/NewOrderCommandHandlerTest.cs | 130 ------------------ 2 files changed, 4 insertions(+), 139 deletions(-) diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 2909b141c..73fae7d5f 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -172,15 +172,10 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers var eventData = new IntegrationEvent(@event); eventData.TimesSent++; eventData.State = EventStateEnum.Sent; - try - { - _context.IntegrationEvents.Add(eventData); - await _context.SaveChangesAsync(); - } - catch (Exception ex) - { - var t = ex.Message; - } + + _context.IntegrationEvents.Add(eventData); + await _context.SaveChangesAsync(); + } } } diff --git a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs index fe39ffb6c..e0ac0fb78 100644 --- a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs @@ -28,31 +28,6 @@ namespace UnitTest.Ordering.Application _mediator = new Mock(); } - [Fact] - public async Task Handle_returns_true_when_order_is_persisted_succesfully() - { - - var buyerId = "1234"; - - var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary - { ["cardExpiration"] = DateTime.Now.AddYears(1) }); - - // Arrange - _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) - .Returns(Task.FromResult(FakeOrder())); - - _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); - //Act - var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); - var result = await handler.Handle(fakeOrderCmd); - - //Assert - Assert.True(result); - } - [Fact] public async Task Handle_return_false_if_order_is_not_persisted() { @@ -77,111 +52,6 @@ namespace UnitTest.Ordering.Application Assert.False(result); } - [Fact] - public async Task Handle_throws_exception_when_order_expired() - { - - var buyerId = "1234"; - - var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary - { ["cardExpiration"] = DateTime.Now.AddYears(-1) }); - - // Arrange - _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) - .Returns(Task.FromResult(FakeOrder())); - - _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); - //Act - var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); - - //Assert - await Assert.ThrowsAsync(async () => await handler.Handle(fakeOrderCmd)); - } - - [Fact] - public async Task Handle_throws_exception_when_no_holdername() - { - - var buyerId = "1234"; - - var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary - { - ["cardExpiration"] = DateTime.Now.AddYears(1), - ["cardHolderName"] = string.Empty, - }); - - // Arrange - _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) - .Returns(Task.FromResult(FakeOrder())); - - _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); - //Act - var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); - - //Assert - await Assert.ThrowsAsync(async () => await handler.Handle(fakeOrderCmd)); - } - - [Fact] - public async Task Handle_throws_exception_when_no_securityNumber() - { - - var buyerId = "1234"; - - var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary - { - ["cardExpiration"] = DateTime.Now.AddYears(1), - ["cardSecurityNumber"] = string.Empty, - }); - - // Arrange - _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) - .Returns(Task.FromResult(FakeOrder())); - - _orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); - //Act - var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); - - //Assert - await Assert.ThrowsAsync(async () => await handler.Handle(fakeOrderCmd)); - } - - [Fact] - public async Task Handle_throws_exception_when_no_cardNumber() - { - - var orderId = "1234"; - - var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary - { - ["cardExpiration"] = DateTime.Now.AddYears(1), - ["cardNumber"] = string.Empty, - }); - - // Arrange - _orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny())) - .Returns(Task.FromResult(FakeOrder())); - - _orderRepositoryMock.Setup(orderRepo => orderRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) - .Returns(Task.FromResult(1)); - - _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(orderId); - //Act - var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); - - //Assert - await Assert.ThrowsAsync(async () => await handler.Handle(fakeOrderCmd)); - } - [Fact] public void Handle_throws_exception_when_no_buyerId() { From e41ce96f819f54a259f256eb9c66856feaee5db1 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Wed, 15 Mar 2017 14:50:10 -0700 Subject: [PATCH 22/80] Minor refactoring related to the ProductPriceChanged integration event --- .../Basket.API/Events/CatalogPriceChangedHandler.cs | 6 +++--- .../Common/Infrastructure/Catalog/ProductPriceChanged.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs index 280692c17..678f04ab4 100644 --- a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs +++ b/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs @@ -22,13 +22,13 @@ namespace Basket.API.Events foreach (var id in userIds) { var basket = await _repository.GetBasket(id); - await UpdateBasket(@event.ItemId, @event.NewPrice, basket); + await UpdateBasket(@event.ProductId, @event.NewPrice, basket); } } - private async Task UpdateBasket(int itemId, decimal newPrice, CustomerBasket basket) + private async Task UpdateBasket(int productId, decimal newPrice, CustomerBasket basket) { - var itemsToUpdate = basket?.Items?.Where(x => int.Parse(x.ProductId) == itemId).ToList(); + var itemsToUpdate = basket?.Items?.Where(x => int.Parse(x.ProductId) == productId).ToList(); if (itemsToUpdate != null) { foreach (var item in itemsToUpdate) diff --git a/src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs b/src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs index 4f28d4b1f..0610a0e59 100644 --- a/src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs +++ b/src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs @@ -6,15 +6,15 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog { public class ProductPriceChanged : IntegrationEventBase { - public int ItemId { get; private set; } + public int ProductId { get; private set; } public decimal NewPrice { get; private set; } - public decimal OldPrice { get; set; } + public decimal OldPrice { get; private set; } - public ProductPriceChanged(int itemId, decimal newPrice, decimal oldPrice) + public ProductPriceChanged(int productId, decimal newPrice, decimal oldPrice) { - ItemId = itemId; + ProductId = productId; NewPrice = newPrice; OldPrice = oldPrice; } From 8423c8bb636824cc74f543b8443c69211631f09d Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Wed, 15 Mar 2017 18:42:47 -0700 Subject: [PATCH 23/80] Larger Refactoring of IntegrationEvents --- .../Basket/Basket.API/Basket.API.csproj | 24 ++-- .../ProductPriceChangedEventHandler.cs} | 8 +- .../Events/ProductPriceChangedEvent.cs.txt | 3 + src/Services/Basket/Basket.API/Startup.cs | 8 +- .../Catalog/Catalog.API/Catalog.API.csproj | 37 +++--- .../Controllers/CatalogController.cs | 12 +- .../Infrastructure/CatalogContext.cs | 8 +- ...factoringToIntegrationEventLog.Designer.cs | 122 ++++++++++++++++++ ...012921_RefactoringToIntegrationEventLog.cs | 53 ++++++++ .../Migrations/CatalogContextModelSnapshot.cs | 9 +- .../AnyFutureIntegrationEventHandler.cs.txt | 3 + .../Events/ProductPriceChangedEvent.cs.txt | 3 + ...Changed.cs => ProductPriceChangedEvent.cs} | 9 +- .../Infrastructure/Data/EventStateEnum.cs | 6 +- ...onEvent.cs => IntegrationEventLogEntry.cs} | 6 +- .../Common/Infrastructure/EventBusRabbitMQ.cs | 6 +- .../Common/Infrastructure/IEventBus.cs | 6 +- .../IIntegrationEventHandler.cs | 2 +- .../Infrastructure/Infrastructure.csproj | 1 + ...rationEventBase.cs => IntegrationEvent.cs} | 4 +- 20 files changed, 262 insertions(+), 68 deletions(-) rename src/Services/Basket/Basket.API/{Events/CatalogPriceChangedHandler.cs => IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs} (81%) create mode 100644 src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.cs create mode 100644 src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/AnyFutureIntegrationEventHandler.cs.txt create mode 100644 src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt rename src/Services/Common/Infrastructure/Catalog/{ProductPriceChanged.cs => ProductPriceChangedEvent.cs} (51%) rename src/Services/Common/Infrastructure/Data/{IntegrationEvent.cs => IntegrationEventLogEntry.cs} (82%) rename src/Services/Common/Infrastructure/{IntegrationEventBase.cs => IntegrationEvent.cs} (77%) diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index 1017dbd05..4cea21d4d 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -12,6 +12,10 @@ ..\..\..\..\docker-compose.dcproj + + + + PreserveNewest @@ -23,16 +27,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs similarity index 81% rename from src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs rename to src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs index 678f04ab4..35737e492 100644 --- a/src/Services/Basket/Basket.API/Events/CatalogPriceChangedHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs @@ -6,17 +6,17 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace Basket.API.Events +namespace Basket.API.IntegrationEvents.EventHandling { - public class ProductPriceChangedHandler : IIntegrationEventHandler + public class ProductPriceChangedEventHandler : IIntegrationEventHandler { private readonly IBasketRepository _repository; - public ProductPriceChangedHandler(IBasketRepository repository) + public ProductPriceChangedEventHandler(IBasketRepository repository) { _repository = repository; } - public async Task Handle(ProductPriceChanged @event) + public async Task Handle(ProductPriceChangedEvent @event) { var userIds = await _repository.GetUsers(); foreach (var id in userIds) diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt b/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt new file mode 100644 index 000000000..d51583d96 --- /dev/null +++ b/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt @@ -0,0 +1,3 @@ + + +// To implement ProductPriceChangedEvent.cs here diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index ec5141de3..21a307d21 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -15,7 +15,7 @@ using Swashbuckle.Swagger.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; using Microsoft.eShopOnContainers.Services.Common.Infrastructure; using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; -using Basket.API.Events; +using Basket.API.IntegrationEvents.EventHandling; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -79,7 +79,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API }); services.AddTransient(); - services.AddTransient, ProductPriceChangedHandler>(); + services.AddTransient, ProductPriceChangedEventHandler>(); var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; @@ -87,8 +87,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddSingleton(eventBus); - var catalogPriceHandler = serviceProvider.GetService>(); - eventBus.Subscribe(catalogPriceHandler); + var catalogPriceHandler = serviceProvider.GetService>(); + eventBus.Subscribe(catalogPriceHandler); } diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 387908b90..28bf56d93 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -21,6 +21,8 @@ PreserveNewest + + PreserveNewest @@ -28,23 +30,24 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 73fae7d5f..bf227d58d 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -149,7 +149,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers _context.CatalogItems.Update(item); await _context.SaveChangesAsync(); - var @event = new ProductPriceChanged(item.Id, item.Price, oldPrice); + var @event = new ProductPriceChangedEvent(item.Id, item.Price, oldPrice); await ProcessEventAsync(@event); } @@ -166,14 +166,14 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return items; } - private async Task ProcessEventAsync(IntegrationEventBase @event) + private async Task ProcessEventAsync(IntegrationEvent @event) { _eventBus.Publish(@event); - var eventData = new IntegrationEvent(@event); - eventData.TimesSent++; - eventData.State = EventStateEnum.Sent; + var eventLogEntry = new IntegrationEventLogEntry(@event); + eventLogEntry.TimesSent++; + eventLogEntry.State = EventStateEnum.Published; - _context.IntegrationEvents.Add(eventData); + _context.IntegrationEventLog.Add(eventLogEntry); await _context.SaveChangesAsync(); } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs index aac778940..3d1b6716f 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs @@ -13,14 +13,14 @@ public DbSet CatalogItems { get; set; } public DbSet CatalogBrands { get; set; } public DbSet CatalogTypes { get; set; } - public DbSet IntegrationEvents { get; set; } + public DbSet IntegrationEventLog { get; set; } protected override void OnModelCreating(ModelBuilder builder) { builder.Entity(ConfigureCatalogBrand); builder.Entity(ConfigureCatalogType); builder.Entity(ConfigureCatalogItem); - builder.Entity(ConfigureIntegrationEvent); + builder.Entity(ConfigureIntegrationEventLogEntry); } void ConfigureCatalogItem(EntityTypeBuilder builder) @@ -80,9 +80,9 @@ .HasMaxLength(100); } - void ConfigureIntegrationEvent(EntityTypeBuilder builder) + void ConfigureIntegrationEventLogEntry(EntityTypeBuilder builder) { - builder.ToTable("IntegrationEvent"); + builder.ToTable("IntegrationEventLog"); builder.HasKey(e => e.EventId); diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs new file mode 100644 index 000000000..1d9280cfa --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs @@ -0,0 +1,122 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure; + +namespace Catalog.API.Infrastructure.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20170316012921_RefactoringToIntegrationEventLog")] + partial class RefactoringToIntegrationEventLog + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_brand_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogBrand"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("CatalogBrandId"); + + b.Property("CatalogTypeId"); + + b.Property("Description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50); + + b.Property("PictureUri"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_type_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEventLogEntry", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired(); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEventLog"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.cs new file mode 100644 index 000000000..17c4b2bbc --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Catalog.API.Infrastructure.Migrations +{ + public partial class RefactoringToIntegrationEventLog : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "IntegrationEvent"); + + migrationBuilder.CreateTable( + name: "IntegrationEventLog", + columns: table => new + { + EventId = table.Column(nullable: false), + Content = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + EventTypeName = table.Column(nullable: false), + State = table.Column(nullable: false), + TimesSent = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IntegrationEventLog", x => x.EventId); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "IntegrationEventLog"); + + migrationBuilder.CreateTable( + name: "IntegrationEvent", + columns: table => new + { + EventId = table.Column(nullable: false), + Content = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + EventTypeName = table.Column(maxLength: 200, nullable: false), + State = table.Column(nullable: false), + TimesSent = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IntegrationEvent", x => x.EventId); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs index 4ea056e0d..f6cdb763a 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace Catalog.API.Infrastructure.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder - .HasAnnotation("ProductVersion", "1.1.0-rtm-22752") + .HasAnnotation("ProductVersion", "1.1.1") .HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") @@ -82,7 +82,7 @@ namespace Catalog.API.Infrastructure.Migrations b.ToTable("CatalogType"); }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEvent", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEventLogEntry", b => { b.Property("EventId") .ValueGeneratedOnAdd(); @@ -93,8 +93,7 @@ namespace Catalog.API.Infrastructure.Migrations b.Property("CreationTime"); b.Property("EventTypeName") - .IsRequired() - .HasMaxLength(200); + .IsRequired(); b.Property("State"); @@ -102,7 +101,7 @@ namespace Catalog.API.Infrastructure.Migrations b.HasKey("EventId"); - b.ToTable("IntegrationEvent"); + b.ToTable("IntegrationEventLog"); }); modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/AnyFutureIntegrationEventHandler.cs.txt b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/AnyFutureIntegrationEventHandler.cs.txt new file mode 100644 index 000000000..d51583d96 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/AnyFutureIntegrationEventHandler.cs.txt @@ -0,0 +1,3 @@ + + +// To implement ProductPriceChangedEvent.cs here diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt b/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt new file mode 100644 index 000000000..4ace69180 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt @@ -0,0 +1,3 @@ + + +// To implement any future integration event handler here diff --git a/src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs b/src/Services/Common/Infrastructure/Catalog/ProductPriceChangedEvent.cs similarity index 51% rename from src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs rename to src/Services/Common/Infrastructure/Catalog/ProductPriceChangedEvent.cs index 0610a0e59..70907bc16 100644 --- a/src/Services/Common/Infrastructure/Catalog/ProductPriceChanged.cs +++ b/src/Services/Common/Infrastructure/Catalog/ProductPriceChangedEvent.cs @@ -4,7 +4,10 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog { - public class ProductPriceChanged : IntegrationEventBase + // Integration Events notes: + // An Event is “something that has happened in the past”, therefore its name has to be + // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. + public class ProductPriceChangedEvent : IntegrationEvent { public int ProductId { get; private set; } @@ -12,11 +15,11 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog public decimal OldPrice { get; private set; } - public ProductPriceChanged(int productId, decimal newPrice, decimal oldPrice) + public ProductPriceChangedEvent(int productId, decimal newPrice, decimal oldPrice) { ProductId = productId; NewPrice = newPrice; OldPrice = oldPrice; } -} + } } diff --git a/src/Services/Common/Infrastructure/Data/EventStateEnum.cs b/src/Services/Common/Infrastructure/Data/EventStateEnum.cs index e952b7742..47142dc07 100644 --- a/src/Services/Common/Infrastructure/Data/EventStateEnum.cs +++ b/src/Services/Common/Infrastructure/Data/EventStateEnum.cs @@ -6,8 +6,8 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public enum EventStateEnum { - NotSend = 0, - Sent = 1, - SendingFailed = 2 + NotPublished = 0, + Published = 1, + PublishedFailed = 2 } } diff --git a/src/Services/Common/Infrastructure/Data/IntegrationEvent.cs b/src/Services/Common/Infrastructure/Data/IntegrationEventLogEntry.cs similarity index 82% rename from src/Services/Common/Infrastructure/Data/IntegrationEvent.cs rename to src/Services/Common/Infrastructure/Data/IntegrationEventLogEntry.cs index 97b313021..ff4c190d0 100644 --- a/src/Services/Common/Infrastructure/Data/IntegrationEvent.cs +++ b/src/Services/Common/Infrastructure/Data/IntegrationEventLogEntry.cs @@ -5,15 +5,15 @@ using Newtonsoft.Json; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data { - public class IntegrationEvent + public class IntegrationEventLogEntry { - public IntegrationEvent(IntegrationEventBase @event) + public IntegrationEventLogEntry(IntegrationEvent @event) { EventId = @event.Id; CreationTime = DateTime.UtcNow; EventTypeName = @event.GetType().FullName; Content = JsonConvert.SerializeObject(@event); - State = EventStateEnum.NotSend; + State = EventStateEnum.NotPublished; TimesSent = 0; } public Guid EventId { get; private set; } diff --git a/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs b/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs index f9c3d6a57..4a6cdb0c2 100644 --- a/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs +++ b/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs @@ -30,7 +30,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure _handlers = new Dictionary>(); _eventTypes = new List(); } - public void Publish(IntegrationEventBase @event) + public void Publish(IntegrationEvent @event) { var eventName = @event.GetType().Name; var factory = new ConnectionFactory() { HostName = _connectionString }; @@ -51,7 +51,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } - public void Subscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase + public void Subscribe(IIntegrationEventHandler handler) where T : IntegrationEvent { var eventName = typeof(T).Name; if (_handlers.ContainsKey(eventName)) @@ -72,7 +72,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } - public void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase + public void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEvent { var eventName = typeof(T).Name; if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler)) diff --git a/src/Services/Common/Infrastructure/IEventBus.cs b/src/Services/Common/Infrastructure/IEventBus.cs index 483e550ec..4a8e75a2c 100644 --- a/src/Services/Common/Infrastructure/IEventBus.cs +++ b/src/Services/Common/Infrastructure/IEventBus.cs @@ -6,8 +6,8 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public interface IEventBus { - void Subscribe(IIntegrationEventHandler handler) where T: IntegrationEventBase; - void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase; - void Publish(IntegrationEventBase @event); + void Subscribe(IIntegrationEventHandler handler) where T: IntegrationEvent; + void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEvent; + void Publish(IntegrationEvent @event); } } diff --git a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs index f4c00fa6b..72dd2d04b 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs +++ b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public interface IIntegrationEventHandler : IIntegrationEventHandler - where TIntegrationEvent: IntegrationEventBase + where TIntegrationEvent: IntegrationEvent { Task Handle(TIntegrationEvent @event); } diff --git a/src/Services/Common/Infrastructure/Infrastructure.csproj b/src/Services/Common/Infrastructure/Infrastructure.csproj index 34f8bc5d5..a776b1a13 100644 --- a/src/Services/Common/Infrastructure/Infrastructure.csproj +++ b/src/Services/Common/Infrastructure/Infrastructure.csproj @@ -5,6 +5,7 @@ Microsoft.eShopOnContainers.Services.Common.Infrastructure + diff --git a/src/Services/Common/Infrastructure/IntegrationEventBase.cs b/src/Services/Common/Infrastructure/IntegrationEvent.cs similarity index 77% rename from src/Services/Common/Infrastructure/IntegrationEventBase.cs rename to src/Services/Common/Infrastructure/IntegrationEvent.cs index b11c17815..2ac021784 100644 --- a/src/Services/Common/Infrastructure/IntegrationEventBase.cs +++ b/src/Services/Common/Infrastructure/IntegrationEvent.cs @@ -4,9 +4,9 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { - public class IntegrationEventBase + public class IntegrationEvent { - public IntegrationEventBase() + public IntegrationEvent() { Id = Guid.NewGuid(); } From c8df9a232a26459095e1e21d523b7b5f47cf1b80 Mon Sep 17 00:00:00 2001 From: dsanz Date: Thu, 16 Mar 2017 13:30:01 +0100 Subject: [PATCH 24/80] Refactoring integration events: move event bus implementation to building block projects. --- eShopOnContainers-ServicesAndWebApps.sln | 160 ++++++++++++------ .../EventBus/Abstractions}/IEventBus.cs | 5 +- .../Abstractions}/IIntegrationEventHandler.cs | 5 +- .../EventBus/EventBus/EventBus.csproj | 18 ++ .../EventBus/Events}/EventStateEnum.cs | 2 +- .../EventBus/Events}/IntegrationEvent.cs | 2 +- .../Events}/IntegrationEventLogEntry.cs | 2 +- .../EventBusRabbitMQ}/EventBusRabbitMQ.cs | 5 +- .../EventBusRabbitMQ/EventBusRabbitMQ.csproj} | 12 +- .../Basket/Basket.API/Basket.API.csproj | 7 +- .../ProductPriceChangedEventHandler.cs | 10 +- .../Events}/ProductPriceChangedEvent.cs | 5 +- .../Events/ProductPriceChangedEvent.cs.txt | 3 - src/Services/Basket/Basket.API/Startup.cs | 13 +- .../Catalog/Catalog.API/Catalog.API.csproj | 3 +- .../Controllers/CatalogController.cs | 7 +- .../Infrastructure/CatalogContext.cs | 4 +- .../20170314083211_AddEventTable.Designer.cs | 1 - ...factoringToIntegrationEventLog.Designer.cs | 1 - ..._RefactoringEventBusNamespaces.Designer.cs | 122 +++++++++++++ ...316120022_RefactoringEventBusNamespaces.cs | 19 +++ .../Migrations/CatalogContextModelSnapshot.cs | 46 ++--- .../Events/ProductPriceChangedEvent.cs | 26 +++ src/Services/Catalog/Catalog.API/Startup.cs | 9 +- 24 files changed, 357 insertions(+), 130 deletions(-) rename src/{Services/Common/Infrastructure => BuildingBlocks/EventBus/EventBus/Abstractions}/IEventBus.cs (67%) rename src/{Services/Common/Infrastructure => BuildingBlocks/EventBus/EventBus/Abstractions}/IIntegrationEventHandler.cs (69%) create mode 100644 src/BuildingBlocks/EventBus/EventBus/EventBus.csproj rename src/{Services/Common/Infrastructure/Data => BuildingBlocks/EventBus/EventBus/Events}/EventStateEnum.cs (73%) rename src/{Services/Common/Infrastructure => BuildingBlocks/EventBus/EventBus/Events}/IntegrationEvent.cs (78%) rename src/{Services/Common/Infrastructure/Data => BuildingBlocks/EventBus/EventBus/Events}/IntegrationEventLogEntry.cs (91%) rename src/{Services/Common/Infrastructure => BuildingBlocks/EventBus/EventBusRabbitMQ}/EventBusRabbitMQ.cs (96%) rename src/{Services/Common/Infrastructure/Infrastructure.csproj => BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj} (55%) rename src/Services/{Common/Infrastructure/Catalog => Basket/Basket.API/IntegrationEvents/Events}/ProductPriceChangedEvent.cs (82%) delete mode 100644 src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.Designer.cs create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.cs create mode 100644 src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 426ddd5b1..0c4d939a5 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -41,7 +41,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "test\Services\UnitTest\UnitTest.csproj", "{7796F5D8-31FC-45A4-B673-19DE5BA194CF}" ProjectSection(ProjectDependencies) = postProject {A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B} - {621E7211-58D0-45FD-9600-1CB490BD930E} = {621E7211-58D0-45FD-9600-1CB490BD930E} {FEA0C318-FFED-4D39-8781-265718CA43DD} = {FEA0C318-FFED-4D39-8781-265718CA43DD} {F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68} EndProjectSection @@ -58,11 +57,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Ser EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{47857844-D05A-4C37-BFB2-AF19B7EC418D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Services\Common\Infrastructure\Infrastructure.csproj", "{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{DB0EFB20-B024-4E5E-A75C-52143C131D25}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{9C2566F6-3A98-4B1D-A6B2-35820CE88B73}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventBus", "EventBus", "{807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{7B41F409-D86D-4697-99BC-F8EBA21BB18F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus", "src\BuildingBlocks\EventBus\EventBus\EventBus.csproj", "{0044B293-1DCC-4224-B948-00CF6DC7F510}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBusRabbitMQ", "src\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj", "{8088F3FC-6787-45FA-A924-816EC81CBFAC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -558,54 +559,102 @@ Global {CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x64.Build.0 = Release|Any CPU {CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x86.ActiveCfg = Release|Any CPU {CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x86.Build.0 = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|ARM.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhone.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x64.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x64.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x86.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x86.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|ARM.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|ARM.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhone.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x64.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x64.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x86.ActiveCfg = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x86.Build.0 = Debug|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|Any CPU.Build.0 = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|ARM.ActiveCfg = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|ARM.Build.0 = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhone.ActiveCfg = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhone.Build.0 = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x64.ActiveCfg = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x64.Build.0 = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x86.ActiveCfg = Release|Any CPU - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x86.Build.0 = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|ARM.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|iPhone.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|x64.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|x64.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|x86.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.AppStore|x86.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|ARM.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|ARM.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|iPhone.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|x64.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|x64.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|x86.ActiveCfg = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Debug|x86.Build.0 = Debug|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|Any CPU.Build.0 = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|ARM.ActiveCfg = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|ARM.Build.0 = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|iPhone.ActiveCfg = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|iPhone.Build.0 = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|x64.ActiveCfg = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|x64.Build.0 = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|x86.ActiveCfg = Release|Any CPU + {0044B293-1DCC-4224-B948-00CF6DC7F510}.Release|x86.Build.0 = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|ARM.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|iPhone.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|x64.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|x64.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|x86.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.AppStore|x86.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|ARM.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|ARM.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|iPhone.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|x64.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|x64.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|x86.ActiveCfg = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Debug|x86.Build.0 = Debug|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|Any CPU.Build.0 = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|ARM.ActiveCfg = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|ARM.Build.0 = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|iPhone.ActiveCfg = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|iPhone.Build.0 = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x64.ActiveCfg = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x64.Build.0 = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x86.ActiveCfg = Release|Any CPU + {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -629,8 +678,9 @@ Global {F16E3C6A-1C94-4EAB-BE91-099618060B68} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} {5B810E3D-112E-4857-B197-F09D2FD41E27} = {EF0337F2-ED00-4643-89FD-EE10863F1870} {CFE2FACB-4538-4B99-8A10-306F3882952D} = {EF0337F2-ED00-4643-89FD-EE10863F1870} - {B08BA891-ABA2-4BD5-80E8-40B7546C3BE0} = {7B41F409-D86D-4697-99BC-F8EBA21BB18F} - {9C2566F6-3A98-4B1D-A6B2-35820CE88B73} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} - {7B41F409-D86D-4697-99BC-F8EBA21BB18F} = {9C2566F6-3A98-4B1D-A6B2-35820CE88B73} + {DB0EFB20-B024-4E5E-A75C-52143C131D25} = {932D8224-11F6-4D07-B109-DA28AD288A63} + {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} + {0044B293-1DCC-4224-B948-00CF6DC7F510} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} + {8088F3FC-6787-45FA-A924-816EC81CBFAC} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} EndGlobalSection EndGlobal diff --git a/src/Services/Common/Infrastructure/IEventBus.cs b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs similarity index 67% rename from src/Services/Common/Infrastructure/IEventBus.cs rename to src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs index 4a8e75a2c..af6ee1aa7 100644 --- a/src/Services/Common/Infrastructure/IEventBus.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs @@ -1,8 +1,9 @@ -using System; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; using System.Collections.Generic; using System.Text; -namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions { public interface IEventBus { diff --git a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IIntegrationEventHandler.cs similarity index 69% rename from src/Services/Common/Infrastructure/IIntegrationEventHandler.cs rename to src/BuildingBlocks/EventBus/EventBus/Abstractions/IIntegrationEventHandler.cs index 72dd2d04b..d755bc066 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Abstractions/IIntegrationEventHandler.cs @@ -1,9 +1,10 @@ -using System; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; -namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions { public interface IIntegrationEventHandler : IIntegrationEventHandler where TIntegrationEvent: IntegrationEvent diff --git a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj new file mode 100644 index 000000000..65510f096 --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp1.1 + 1.1.0 + Microsoft.eShopOnContainers.BuildingBlocks.EventBus + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Services/Common/Infrastructure/Data/EventStateEnum.cs b/src/BuildingBlocks/EventBus/EventBus/Events/EventStateEnum.cs similarity index 73% rename from src/Services/Common/Infrastructure/Data/EventStateEnum.cs rename to src/BuildingBlocks/EventBus/EventBus/Events/EventStateEnum.cs index 47142dc07..49be3cc4e 100644 --- a/src/Services/Common/Infrastructure/Data/EventStateEnum.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/EventStateEnum.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events { public enum EventStateEnum { diff --git a/src/Services/Common/Infrastructure/IntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs similarity index 78% rename from src/Services/Common/Infrastructure/IntegrationEvent.cs rename to src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs index 2ac021784..11185764e 100644 --- a/src/Services/Common/Infrastructure/IntegrationEvent.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events { public class IntegrationEvent { diff --git a/src/Services/Common/Infrastructure/Data/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLogEntry.cs similarity index 91% rename from src/Services/Common/Infrastructure/Data/IntegrationEventLogEntry.cs rename to src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLogEntry.cs index ff4c190d0..3feb33210 100644 --- a/src/Services/Common/Infrastructure/Data/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLogEntry.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Text; using Newtonsoft.Json; -namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events { public class IntegrationEventLogEntry { diff --git a/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs similarity index 96% rename from src/Services/Common/Infrastructure/EventBusRabbitMQ.cs rename to src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 4a6cdb0c2..3582e8cc4 100644 --- a/src/Services/Common/Infrastructure/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -1,5 +1,6 @@  -using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Newtonsoft.Json; using RabbitMQ.Client; using RabbitMQ.Client.Events; @@ -11,7 +12,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; -namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { public class EventBusRabbitMQ : IEventBus { diff --git a/src/Services/Common/Infrastructure/Infrastructure.csproj b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj similarity index 55% rename from src/Services/Common/Infrastructure/Infrastructure.csproj rename to src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj index a776b1a13..6e399a627 100644 --- a/src/Services/Common/Infrastructure/Infrastructure.csproj +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj @@ -1,12 +1,18 @@ - + + netcoreapp1.1 1.1.0 - Microsoft.eShopOnContainers.Services.Common.Infrastructure + Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ + - + + + + + \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index 4cea21d4d..bbc596d8f 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -12,10 +12,6 @@ ..\..\..\..\docker-compose.dcproj - - - - PreserveNewest @@ -44,7 +40,8 @@ - + + diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs index 35737e492..1a7599162 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs @@ -1,12 +1,10 @@ -using Microsoft.eShopOnContainers.Services.Basket.API.Model; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; -using System; -using System.Collections.Generic; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; using System.Linq; using System.Threading.Tasks; -namespace Basket.API.IntegrationEvents.EventHandling +namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling { public class ProductPriceChangedEventHandler : IIntegrationEventHandler { diff --git a/src/Services/Common/Infrastructure/Catalog/ProductPriceChangedEvent.cs b/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs similarity index 82% rename from src/Services/Common/Infrastructure/Catalog/ProductPriceChangedEvent.cs rename to src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs index 70907bc16..b3cc5796f 100644 --- a/src/Services/Common/Infrastructure/Catalog/ProductPriceChangedEvent.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs @@ -1,8 +1,9 @@ -using System; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; using System.Collections.Generic; using System.Text; -namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog +namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events { // Integration Events notes: // An Event is “something that has happened in the past”, therefore its name has to be diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt b/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt deleted file mode 100644 index d51583d96..000000000 --- a/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt +++ /dev/null @@ -1,3 +0,0 @@ - - -// To implement ProductPriceChangedEvent.cs here diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 21a307d21..b96029718 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -11,11 +8,11 @@ using Microsoft.eShopOnContainers.Services.Basket.API.Model; using StackExchange.Redis; using Microsoft.Extensions.Options; using System.Net; -using Swashbuckle.Swagger.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; -using Basket.API.IntegrationEvents.EventHandling; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; +using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; namespace Microsoft.eShopOnContainers.Services.Basket.API { diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 28bf56d93..fe8ea6ef9 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -57,7 +57,8 @@ - + + diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index bf227d58d..56629550b 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -1,13 +1,12 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Catalog.API.Model; using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data; using Microsoft.Extensions.Options; -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs index 3d1b6716f..43ace7e6e 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs @@ -2,8 +2,8 @@ { using EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore; - using Model; - using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data; + using Model; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; public class CatalogContext : DbContext { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs index 126047b85..16bff5a2c 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure; namespace Catalog.API.Infrastructure.Migrations { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs index 1d9280cfa..68d00f32f 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure; namespace Catalog.API.Infrastructure.Migrations { diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.Designer.cs new file mode 100644 index 000000000..52c5d7879 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.Designer.cs @@ -0,0 +1,122 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + +namespace Catalog.API.Infrastructure.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20170316120022_RefactoringEventBusNamespaces")] + partial class RefactoringEventBusNamespaces + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLogEntry", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired(); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEventLog"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_brand_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogBrand"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("CatalogBrandId"); + + b.Property("CatalogTypeId"); + + b.Property("Description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50); + + b.Property("PictureUri"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_type_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.cs new file mode 100644 index 000000000..ff012cd60 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Catalog.API.Infrastructure.Migrations +{ + public partial class RefactoringEventBusNamespaces : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs index f6cdb763a..77c820212 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; -using Microsoft.eShopOnContainers.Services.Common.Infrastructure; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; namespace Catalog.API.Infrastructure.Migrations { @@ -20,6 +20,28 @@ namespace Catalog.API.Infrastructure.Migrations .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLogEntry", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired(); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEventLog"); + }); + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => { b.Property("Id") @@ -82,28 +104,6 @@ namespace Catalog.API.Infrastructure.Migrations b.ToTable("CatalogType"); }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEventLogEntry", b => - { - b.Property("EventId") - .ValueGeneratedOnAdd(); - - b.Property("Content") - .IsRequired(); - - b.Property("CreationTime"); - - b.Property("EventTypeName") - .IsRequired(); - - b.Property("State"); - - b.Property("TimesSent"); - - b.HasKey("EventId"); - - b.ToTable("IntegrationEventLog"); - }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => { b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand") diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs new file mode 100644 index 000000000..c757259f9 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs @@ -0,0 +1,26 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events +{ + // Integration Events notes: + // An Event is “something that has happened in the past”, therefore its name has to be + // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. + public class ProductPriceChangedEvent : IntegrationEvent + { + public int ProductId { get; private set; } + + public decimal NewPrice { get; private set; } + + public decimal OldPrice { get; private set; } + + public ProductPriceChangedEvent(int productId, decimal newPrice, decimal oldPrice) + { + ProductId = productId; + NewPrice = newPrice; + OldPrice = oldPrice; + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index cb4f6254c..818855d69 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -1,22 +1,17 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API { - using AspNetCore.Http; - using Extensions.FileProviders; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; - using Microsoft.eShopOnContainers.Services.Common.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; - using System; - using System.IO; using System.Reflection; - using System.Threading; - using System.Threading.Tasks; public class Startup { From 33335eb2309fd14d25c453f6f7362d69d4740eb4 Mon Sep 17 00:00:00 2001 From: dsanz Date: Thu, 16 Mar 2017 16:08:55 +0100 Subject: [PATCH 25/80] Change in integration event publication fow as agreed. Flow is 1- start transaction 2- modify catalog iin db 3- add event to db 4- close transaction 5- publish event 6- update event state in db --- .../Controllers/CatalogController.cs | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 56629550b..9c3bd170e 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -144,12 +144,20 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers { var oldPrice = item.Price; item.Price = value.Price; - _context.CatalogItems.Update(item); - await _context.SaveChangesAsync(); var @event = new ProductPriceChangedEvent(item.Id, item.Price, oldPrice); - await ProcessEventAsync(@event); + var eventLogEntry = new IntegrationEventLogEntry(@event); + _context.IntegrationEventLog.Add(eventLogEntry); + + await _context.SaveChangesAsync(); + + _eventBus.Publish(@event); + + eventLogEntry.TimesSent++; + eventLogEntry.State = EventStateEnum.Published; + _context.IntegrationEventLog.Update(eventLogEntry); + await _context.SaveChangesAsync(); } return Ok(); @@ -164,17 +172,5 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return items; } - - private async Task ProcessEventAsync(IntegrationEvent @event) - { - _eventBus.Publish(@event); - var eventLogEntry = new IntegrationEventLogEntry(@event); - eventLogEntry.TimesSent++; - eventLogEntry.State = EventStateEnum.Published; - - _context.IntegrationEventLog.Add(eventLogEntry); - await _context.SaveChangesAsync(); - - } } } From 6f6f09e11e84cd09e7615e10f4040980378f0d56 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Thu, 16 Mar 2017 15:54:01 -0700 Subject: [PATCH 26/80] Minor refactoring on the Event Bus IntegrationEventLog --- src/BuildingBlocks/EventBus/EventBus/EventBus.csproj | 1 - .../Events/{ => IntegrationEventLog}/EventStateEnum.cs | 2 +- .../{ => IntegrationEventLog}/IntegrationEventLogEntry.cs | 2 +- .../Catalog/Catalog.API/Controllers/CatalogController.cs | 2 +- .../Catalog/Catalog.API/Infrastructure/CatalogContext.cs | 4 ++-- 5 files changed, 5 insertions(+), 6 deletions(-) rename src/BuildingBlocks/EventBus/EventBus/Events/{ => IntegrationEventLog}/EventStateEnum.cs (91%) rename src/BuildingBlocks/EventBus/EventBus/Events/{ => IntegrationEventLog}/IntegrationEventLogEntry.cs (97%) diff --git a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj index 65510f096..ad6867741 100644 --- a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj @@ -8,7 +8,6 @@ - diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/EventStateEnum.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/EventStateEnum.cs similarity index 91% rename from src/BuildingBlocks/EventBus/EventBus/Events/EventStateEnum.cs rename to src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/EventStateEnum.cs index 49be3cc4e..41ddc119c 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/EventStateEnum.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/EventStateEnum.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog { public enum EventStateEnum { diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/IntegrationEventLogEntry.cs similarity index 97% rename from src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLogEntry.cs rename to src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/IntegrationEventLogEntry.cs index 3feb33210..a74c600e4 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/IntegrationEventLogEntry.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Text; using Newtonsoft.Json; -namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog { public class IntegrationEventLogEntry { diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 9c3bd170e..736ae4d76 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Catalog.API.Model; diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs index 43ace7e6e..846b1dbb5 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs @@ -2,8 +2,8 @@ { using EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore; - using Model; - using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + using Model; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog; public class CatalogContext : DbContext { From b9c1778d9d8444d2a6dbb09619888b55fbdd4546 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Thu, 16 Mar 2017 18:52:02 -0700 Subject: [PATCH 27/80] Refactoring Domain Events so it is clear and differenciated versus Integration Events --- .../OrderCreatedDomainEventHandler.cs} | 10 +++++----- .../PaymentMethodCheckedDomainEventHandler.cs} | 10 +++++----- .../AutofacModules/MediatorModule.cs | 4 ++-- .../AggregatesModel/BuyerAggregate/Buyer.cs | 4 ++-- .../AggregatesModel/OrderAggregate/Order.cs | 11 +++++++---- ...rderCreated.cs => OrderCreatedDomainEvent.cs} | 4 ++-- ...ked.cs => PaymentMethodCheckedDomainEvent.cs} | 4 ++-- .../Ordering/Ordering.Domain/SeedWork/Entity.cs | 16 ++++++++-------- .../Ordering.Infrastructure/MediatorExtension.cs | 6 +++--- .../Ordering.Infrastructure/OrderingContext.cs | 10 ++++++---- .../Ordering/Domain/BuyerAggregateTest.cs | 2 +- .../Ordering/Domain/OrderAggregateTest.cs | 14 +++++++------- 12 files changed, 50 insertions(+), 45 deletions(-) rename src/Services/Ordering/Ordering.API/Application/{EventHandlers/OrderCreatedEventHandler.cs => DomainEventHandlers/OrderCreatedDomainEventHandler.cs} (74%) rename src/Services/Ordering/Ordering.API/Application/{EventHandlers/PaymentMethodCheckedEventHandler.cs => DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs} (71%) rename src/Services/Ordering/Ordering.Domain/Events/{OrderCreated.cs => OrderCreatedDomainEvent.cs} (92%) rename src/Services/Ordering/Ordering.Domain/Events/{PaymentMethodChecked.cs => PaymentMethodCheckedDomainEvent.cs} (78%) diff --git a/src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCreatedDomainEventHandler.cs similarity index 74% rename from src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCreatedDomainEventHandler.cs index 422a61c9b..8aed7f1b2 100644 --- a/src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCreatedDomainEventHandler.cs @@ -6,22 +6,22 @@ using Ordering.Domain.Events; using System; using System.Threading.Tasks; -namespace Ordering.API.Application.EventHandlers +namespace Ordering.API.Application.DomainEventHandlers { - public class OrderCreatedEventHandler : IAsyncNotificationHandler + public class OrderCreatedDomainEventHandler : IAsyncNotificationHandler { private readonly ILoggerFactory _logger; private readonly IBuyerRepository _buyerRepository; private readonly IIdentityService _identityService; - public OrderCreatedEventHandler(ILoggerFactory logger, IBuyerRepository buyerRepository, IIdentityService identityService) + public OrderCreatedDomainEventHandler(ILoggerFactory logger, IBuyerRepository buyerRepository, IIdentityService identityService) { _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task Handle(OrderCreated orderNotification) + public async Task Handle(OrderCreatedDomainEvent orderNotification) { var cardTypeId = orderNotification.CardTypeId != 0 ? orderNotification.CardTypeId : 1; @@ -46,7 +46,7 @@ namespace Ordering.API.Application.EventHandlers await _buyerRepository.UnitOfWork .SaveEntitiesAsync(); - _logger.CreateLogger(nameof(OrderCreatedEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}."); + _logger.CreateLogger(nameof(OrderCreatedDomainEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}."); } } diff --git a/src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs similarity index 71% rename from src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs index b72b89363..eb833f70e 100644 --- a/src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs @@ -5,19 +5,19 @@ using Ordering.Domain.Events; using System; using System.Threading.Tasks; -namespace Ordering.API.Application.EventHandlers +namespace Ordering.API.Application.DomainEventHandlers { - public class PaymentMethodCheckedEventHandler : IAsyncNotificationHandler + public class PaymentMethodCheckedDomainEventHandler : IAsyncNotificationHandler { private readonly IOrderRepository _orderRepository; private readonly ILoggerFactory _logger; - public PaymentMethodCheckedEventHandler(IOrderRepository orderRepository, ILoggerFactory logger) + public PaymentMethodCheckedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task Handle(PaymentMethodChecked paymentMethodNotification) + public async Task Handle(PaymentMethodCheckedDomainEvent paymentMethodNotification) { var orderToUpdate = await _orderRepository.GetAsync(paymentMethodNotification.OrderId); orderToUpdate.SetBuyerId(paymentMethodNotification.Buyer.Id); @@ -26,7 +26,7 @@ namespace Ordering.API.Application.EventHandlers await _orderRepository.UnitOfWork .SaveEntitiesAsync(); - _logger.CreateLogger(nameof(PaymentMethodCheckedEventHandler)) + _logger.CreateLogger(nameof(PaymentMethodCheckedDomainEventHandler)) .LogTrace($"Order with Id: {paymentMethodNotification.OrderId} has been successfully updated with a new payment method id: { paymentMethodNotification.Payment.Id }"); } } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index 8585b8aa9..8d76fd9f5 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -3,7 +3,7 @@ using Autofac.Core; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Decorators; -using Ordering.API.Application.EventHandlers; +using Ordering.API.Application.DomainEventHandlers; using Ordering.Domain.Events; using System.Collections.Generic; using System.Linq; @@ -24,7 +24,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof .Select(i => new KeyedService("IAsyncRequestHandler", i))); builder - .RegisterAssemblyTypes(typeof(OrderCreatedEventHandler).GetTypeInfo().Assembly) + .RegisterAssemblyTypes(typeof(OrderCreatedDomainEventHandler).GetTypeInfo().Assembly) .Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>))) .AsImplementedInterfaces(); diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs index 64acbf7b7..cedb42c06 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs @@ -33,7 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B if (existingPayment != null) { - AddEvent(new PaymentMethodChecked(this, existingPayment, orderId)); + AddDomainEvent(new PaymentMethodCheckedDomainEvent(this, existingPayment, orderId)); return existingPayment; } else @@ -41,7 +41,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration); _paymentMethods.Add(payment); - AddEvent(new PaymentMethodChecked(this, payment, orderId)); + AddDomainEvent(new PaymentMethodCheckedDomainEvent(this, payment, orderId)); return payment; } } diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index a1bb76cee..6ccece658 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -51,7 +51,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O _orderStatusId = OrderStatus.InProcess.Id; _orderDate = DateTime.UtcNow; Address = address; - AddCreatedOrderEvent(cardTypeId, cardNumber, + + // Add the OrderCreatedEvent to the domain events collection + // to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ] + AddOrderCreatedDomainEvent(cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); } @@ -93,14 +96,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O _buyerId = id; } - private void AddCreatedOrderEvent(int cardTypeId, string cardNumber, + private void AddOrderCreatedDomainEvent(int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) { - var @orderCreatedEvent = new OrderCreated( + var orderCreatedDomainEvent = new OrderCreatedDomainEvent( this, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - AddEvent(@orderCreatedEvent); + AddDomainEvent(orderCreatedDomainEvent); } } } diff --git a/src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs b/src/Services/Ordering/Ordering.Domain/Events/OrderCreatedDomainEvent.cs similarity index 92% rename from src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs rename to src/Services/Ordering/Ordering.Domain/Events/OrderCreatedDomainEvent.cs index 489cc23d0..04fdb41db 100644 --- a/src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs +++ b/src/Services/Ordering/Ordering.Domain/Events/OrderCreatedDomainEvent.cs @@ -9,7 +9,7 @@ namespace Ordering.Domain.Events /// /// Event used when an order is created /// - public class OrderCreated + public class OrderCreatedDomainEvent : IAsyncNotification { public int CardTypeId { get; private set; } @@ -19,7 +19,7 @@ namespace Ordering.Domain.Events public DateTime CardExpiration { get; private set; } public Order Order { get; private set; } - public OrderCreated(Order order, + public OrderCreatedDomainEvent(Order order, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) diff --git a/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs b/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodCheckedDomainEvent.cs similarity index 78% rename from src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs rename to src/Services/Ordering/Ordering.Domain/Events/PaymentMethodCheckedDomainEvent.cs index 25c8d8111..a729361c7 100644 --- a/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs +++ b/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodCheckedDomainEvent.cs @@ -6,14 +6,14 @@ using System.Text; namespace Ordering.Domain.Events { - public class PaymentMethodChecked + public class PaymentMethodCheckedDomainEvent : IAsyncNotification { public Buyer Buyer { get; private set; } public PaymentMethod Payment { get; private set; } public int OrderId { get; private set; } - public PaymentMethodChecked(Buyer buyer, PaymentMethod payment, int orderId) + public PaymentMethodCheckedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId) { Buyer = buyer; Payment = payment; diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs index e2c602300..7616a3230 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs @@ -10,7 +10,7 @@ int? _requestedHashCode; int _Id; - private List _events; + private List _domainEvents; public virtual int Id { @@ -24,17 +24,17 @@ } } - public List Events => _events; - public void AddEvent(IAsyncNotification eventItem) + public List DomainEvents => _domainEvents; + public void AddDomainEvent(IAsyncNotification eventItem) { - _events = _events ?? new List(); - _events.Add(eventItem); + _domainEvents = _domainEvents ?? new List(); + _domainEvents.Add(eventItem); } - public void RemoveEvent(IAsyncNotification eventItem) + public void RemoveDomainEvent(IAsyncNotification eventItem) { - if (_events is null) return; - _events.Remove(eventItem); + if (_domainEvents is null) return; + _domainEvents.Remove(eventItem); } public bool IsTransient() diff --git a/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs b/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs index 5949f00b4..21fc5e21f 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs @@ -10,9 +10,9 @@ namespace Ordering.Infrastructure { public static async Task RaiseDomainEventsAsync(this IMediator mediator, OrderingContext ctx) { - var domainEntities = ctx.ChangeTracker.Entries().Where(x => x.Entity.Events != null && x.Entity.Events.Any()); - var domainEvents = domainEntities.SelectMany(x => x.Entity.Events).ToList(); - domainEntities.ToList().ForEach(entity => entity.Entity.Events.Clear()); + var domainEntities = ctx.ChangeTracker.Entries().Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any()); + var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList(); + domainEntities.ToList().ForEach(entity => entity.Entity.DomainEvents.Clear()); var tasks = domainEvents .Select(async (domainEvent) => { diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 504ed0ab7..56790185e 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -74,7 +74,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure buyerConfiguration.HasKey(b => b.Id); - buyerConfiguration.Ignore(b => b.Events); + buyerConfiguration.Ignore(b => b.DomainEvents); buyerConfiguration.Property(b => b.Id) .ForSqlServerUseSequenceHiLo("buyerseq", DEFAULT_SCHEMA); @@ -102,7 +102,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure paymentConfiguration.HasKey(b => b.Id); - paymentConfiguration.Ignore(b => b.Events); + paymentConfiguration.Ignore(b => b.DomainEvents); paymentConfiguration.Property(b => b.Id) .ForSqlServerUseSequenceHiLo("paymentseq", DEFAULT_SCHEMA); @@ -139,7 +139,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure orderConfiguration.HasKey(o => o.Id); - orderConfiguration.Ignore(b => b.Events); + orderConfiguration.Ignore(b => b.DomainEvents); orderConfiguration.Property(o => o.Id) .ForSqlServerUseSequenceHiLo("orderseq", DEFAULT_SCHEMA); @@ -176,7 +176,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure orderItemConfiguration.HasKey(o => o.Id); - orderItemConfiguration.Ignore(b => b.Events); + orderItemConfiguration.Ignore(b => b.DomainEvents); orderItemConfiguration.Property(o => o.Id) .ForSqlServerUseSequenceHiLo("orderitemseq"); @@ -238,6 +238,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) { var result = await base.SaveChangesAsync(); + + // Dispatch the Domain Events collection right after saving/commiting data into the database await _mediator.RaiseDomainEventsAsync(this); return result; } diff --git a/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs index 2cf035205..0703ed197 100644 --- a/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs @@ -122,6 +122,6 @@ public class BuyerAggregateTest fakeBuyer.AddPaymentMethod(cardTypeId, alias, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration, orderId); //Assert - Assert.Equal(fakeBuyer.Events.Count, expectedResult); + Assert.Equal(fakeBuyer.DomainEvents.Count, expectedResult); } } \ No newline at end of file diff --git a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs index dab34f528..c420951da 100644 --- a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs @@ -112,7 +112,7 @@ public class OrderAggregateTest var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); //Assert - Assert.Equal(fakeOrder.Events.Count, expectedResult); + Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult); } [Fact] @@ -133,9 +133,9 @@ public class OrderAggregateTest //Act var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - fakeOrder.AddEvent(new OrderCreated(fakeOrder,cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration)); + fakeOrder.AddDomainEvent(new OrderCreatedDomainEvent(fakeOrder,cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration)); //Assert - Assert.Equal(fakeOrder.Events.Count, expectedResult); + Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult); } [Fact] @@ -153,13 +153,13 @@ public class OrderAggregateTest var cardHolderName = "FakeName"; var cardExpiration = DateTime.Now.AddYears(1); var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - var @fakeEvent = new OrderCreated(fakeOrder, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); + var @fakeEvent = new OrderCreatedDomainEvent(fakeOrder, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); var expectedResult = 1; //Act - fakeOrder.AddEvent(@fakeEvent); - fakeOrder.RemoveEvent(@fakeEvent); + fakeOrder.AddDomainEvent(@fakeEvent); + fakeOrder.RemoveDomainEvent(@fakeEvent); //Assert - Assert.Equal(fakeOrder.Events.Count, expectedResult); + Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult); } } \ No newline at end of file From 5a374e97b5cc0207d6f480bdadddb400c6668c28 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Thu, 16 Mar 2017 18:59:20 -0700 Subject: [PATCH 28/80] Refactoring Integration Events so they cannot be confused with Domain Events --- ...ler.cs => ProductPriceChangedIntegrationEventHandler.cs} | 6 +++--- ...angedEvent.cs => ProductPriceChangedIntegrationEvent.cs} | 4 ++-- src/Services/Basket/Basket.API/Startup.cs | 6 +++--- src/Services/Catalog/Catalog.API/Catalog.API.csproj | 2 +- .../Catalog/Catalog.API/Controllers/CatalogController.cs | 2 +- .../Events/ProductPriceChangedEvent.cs.txt | 3 --- ...angedEvent.cs => ProductPriceChangedIntegrationEvent.cs} | 4 ++-- 7 files changed, 12 insertions(+), 15 deletions(-) rename src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/{ProductPriceChangedEventHandler.cs => ProductPriceChangedIntegrationEventHandler.cs} (82%) rename src/Services/Basket/Basket.API/IntegrationEvents/Events/{ProductPriceChangedEvent.cs => ProductPriceChangedIntegrationEvent.cs} (81%) delete mode 100644 src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt rename src/Services/Catalog/Catalog.API/IntegrationEvents/Events/{ProductPriceChangedEvent.cs => ProductPriceChangedIntegrationEvent.cs} (81%) diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs similarity index 82% rename from src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs rename to src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs index 1a7599162..0cc55fd2e 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs @@ -6,15 +6,15 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling { - public class ProductPriceChangedEventHandler : IIntegrationEventHandler + public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler { private readonly IBasketRepository _repository; - public ProductPriceChangedEventHandler(IBasketRepository repository) + public ProductPriceChangedIntegrationEventHandler(IBasketRepository repository) { _repository = repository; } - public async Task Handle(ProductPriceChangedEvent @event) + public async Task Handle(ProductPriceChangedIntegrationEvent @event) { var userIds = await _repository.GetUsers(); foreach (var id in userIds) diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs b/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs similarity index 81% rename from src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs rename to src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs index b3cc5796f..87d2e9e81 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs @@ -8,7 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even // Integration Events notes: // An Event is “something that has happened in the past”, therefore its name has to be // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. - public class ProductPriceChangedEvent : IntegrationEvent + public class ProductPriceChangedIntegrationEvent : IntegrationEvent { public int ProductId { get; private set; } @@ -16,7 +16,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even public decimal OldPrice { get; private set; } - public ProductPriceChangedEvent(int productId, decimal newPrice, decimal oldPrice) + public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice) { ProductId = productId; NewPrice = newPrice; diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index b96029718..3455630f9 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -76,7 +76,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API }); services.AddTransient(); - services.AddTransient, ProductPriceChangedEventHandler>(); + services.AddTransient, ProductPriceChangedIntegrationEventHandler>(); var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; @@ -84,8 +84,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddSingleton(eventBus); - var catalogPriceHandler = serviceProvider.GetService>(); - eventBus.Subscribe(catalogPriceHandler); + var catalogPriceHandler = serviceProvider.GetService>(); + eventBus.Subscribe(catalogPriceHandler); } diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index fe8ea6ef9..e46c19a56 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -22,7 +22,7 @@ PreserveNewest - + PreserveNewest diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 736ae4d76..431928e0b 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -146,7 +146,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers item.Price = value.Price; _context.CatalogItems.Update(item); - var @event = new ProductPriceChangedEvent(item.Id, item.Price, oldPrice); + var @event = new ProductPriceChangedIntegrationEvent(item.Id, item.Price, oldPrice); var eventLogEntry = new IntegrationEventLogEntry(@event); _context.IntegrationEventLog.Add(eventLogEntry); diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt b/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt deleted file mode 100644 index 4ace69180..000000000 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs.txt +++ /dev/null @@ -1,3 +0,0 @@ - - -// To implement any future integration event handler here diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs similarity index 81% rename from src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs rename to src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs index c757259f9..10b8317da 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedEvent.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs @@ -8,7 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Eve // Integration Events notes: // An Event is “something that has happened in the past”, therefore its name has to be // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. - public class ProductPriceChangedEvent : IntegrationEvent + public class ProductPriceChangedIntegrationEvent : IntegrationEvent { public int ProductId { get; private set; } @@ -16,7 +16,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Eve public decimal OldPrice { get; private set; } - public ProductPriceChangedEvent(int productId, decimal newPrice, decimal oldPrice) + public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice) { ProductId = productId; NewPrice = newPrice; From cb3f6828728478268dd5befad33f3c04930f74fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Fri, 17 Mar 2017 10:00:18 +0100 Subject: [PATCH 29/80] Created Retry and CircuitBreaker policies for MVC App --- src/Web/WebMVC/Controllers/OrderController.cs | 23 +++-- src/Web/WebMVC/Services/BasketService.cs | 33 +++---- src/Web/WebMVC/Services/CatalogService.cs | 31 +++--- src/Web/WebMVC/Services/OrderingService.cs | 24 ++--- .../Services/Utilities/HttpClientWrapper.cs | 99 +++++++++++++++++++ .../Utilities/RetryWithExponentialBackoff.cs | 90 ----------------- src/Web/WebMVC/Views/Order/Create.cshtml | 7 ++ src/Web/WebMVC/WebMVC.csproj | 1 + 8 files changed, 161 insertions(+), 147 deletions(-) create mode 100644 src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs delete mode 100644 src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs diff --git a/src/Web/WebMVC/Controllers/OrderController.cs b/src/Web/WebMVC/Controllers/OrderController.cs index fc435ef6b..3d9317ae6 100644 --- a/src/Web/WebMVC/Controllers/OrderController.cs +++ b/src/Web/WebMVC/Controllers/OrderController.cs @@ -7,6 +7,7 @@ using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.AspNetCore.Authorization; using System.Net.Http; +using Polly.CircuitBreaker; namespace Microsoft.eShopOnContainers.WebMVC.Controllers { @@ -37,18 +38,24 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers [HttpPost] public async Task Create(Order model, string action) { - if (ModelState.IsValid) + try { - var user = _appUserParser.Parse(HttpContext.User); - await _orderSvc.CreateOrder(model); + if (ModelState.IsValid) + { + var user = _appUserParser.Parse(HttpContext.User); + await _orderSvc.CreateOrder(model); - //Empty basket for current user. - await _basketSvc.CleanBasket(user); + //Empty basket for current user. + await _basketSvc.CleanBasket(user); - //Redirect to historic list. - return RedirectToAction("Index"); + //Redirect to historic list. + return RedirectToAction("Index"); + } + } + catch(BrokenCircuitException ex) + { + ModelState.AddModelError("Error", "Not possible to create a new order, please try later on"); } - return View(model); } diff --git a/src/Web/WebMVC/Services/BasketService.cs b/src/Web/WebMVC/Services/BasketService.cs index c5b1bf0bc..087e02626 100644 --- a/src/Web/WebMVC/Services/BasketService.cs +++ b/src/Web/WebMVC/Services/BasketService.cs @@ -1,21 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.eShopOnContainers.WebMVC.ViewModels; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; -using System.Net.Http; +using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.Extensions.Options; using Newtonsoft.Json; -using Microsoft.AspNetCore.Authentication; -using Microsoft.eShopOnContainers.WebMVC.Extensions; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using WebMVC.Services.Utilities; namespace Microsoft.eShopOnContainers.WebMVC.Services { public class BasketService : IBasketService { private readonly IOptionsSnapshot _settings; - private HttpClient _apiClient; + private HttpClientWrapper _apiClient; private readonly string _remoteServiceBaseUrl; private IHttpContextAccessor _httpContextAccesor; @@ -31,8 +30,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClient(); - _apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + var _apiClient = new HttpClientWrapper(); + _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}"; var dataString = await _apiClient.GetStringAsync(basketUrl); @@ -53,12 +52,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClient(); - _apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + _apiClient = new HttpClientWrapper(); + _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var basketUrl = _remoteServiceBaseUrl; - StringContent content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json"); - var response = await _apiClient.PostAsync(basketUrl, content); + + var response = await _apiClient.PostAsync(basketUrl, basket); return basket; } @@ -120,8 +119,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClient(); - _apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + _apiClient = new HttpClientWrapper(); + _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}"; var response = await _apiClient.DeleteAsync(basketUrl); diff --git a/src/Web/WebMVC/Services/CatalogService.cs b/src/Web/WebMVC/Services/CatalogService.cs index 2c2035873..7cafb29e0 100644 --- a/src/Web/WebMVC/Services/CatalogService.cs +++ b/src/Web/WebMVC/Services/CatalogService.cs @@ -1,22 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.eShopOnContainers.WebMVC.ViewModels; -using Microsoft.CodeAnalysis.Options; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Net.Http; -using Microsoft.AspNetCore.Mvc.Rendering; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Threading.Tasks; +using WebMVC.Services.Utilities; namespace Microsoft.eShopOnContainers.WebMVC.Services { public class CatalogService : ICatalogService { private readonly IOptionsSnapshot _settings; - private HttpClient _apiClient; + private HttpClientWrapper _apiClient; private readonly string _remoteServiceBaseUrl; public CatalogService(IOptionsSnapshot settings, ILoggerFactory loggerFactory) { @@ -29,7 +26,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task GetCatalogItems(int page,int take, int? brand, int? type) { - _apiClient = new HttpClient(); + _apiClient = new HttpClientWrapper(); var itemsQs = $"items?pageIndex={page}&pageSize={take}"; var filterQs = ""; @@ -45,16 +42,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var dataString = ""; // - // Using HttpClient with Retry and Exponential Backoff + // Using a HttpClient wrapper with Retry and Exponential Backoff // - var retry = new RetryWithExponentialBackoff(); - await retry.RunAsync(async () => - { - // work with HttpClient call - dataString = await _apiClient.GetStringAsync(catalogUrl); - }); + dataString = await _apiClient.GetStringAsync(catalogUrl); - //var dataString = await _apiClient.GetStringAsync(catalogUrl); var response = JsonConvert.DeserializeObject(dataString); return response; @@ -62,7 +53,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task> GetBrands() { - _apiClient = new HttpClient(); + _apiClient = new HttpClientWrapper(); var url = $"{_remoteServiceBaseUrl}catalogBrands"; var dataString = await _apiClient.GetStringAsync(url); @@ -81,7 +72,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task> GetTypes() { - _apiClient = new HttpClient(); + _apiClient = new HttpClientWrapper(); var url = $"{_remoteServiceBaseUrl}catalogTypes"; var dataString = await _apiClient.GetStringAsync(url); diff --git a/src/Web/WebMVC/Services/OrderingService.cs b/src/Web/WebMVC/Services/OrderingService.cs index 184592839..ff98ac4f9 100644 --- a/src/Web/WebMVC/Services/OrderingService.cs +++ b/src/Web/WebMVC/Services/OrderingService.cs @@ -8,12 +8,13 @@ using Microsoft.Extensions.Options; using System.Net.Http; using Newtonsoft.Json; using Microsoft.AspNetCore.Authentication; +using WebMVC.Services.Utilities; namespace Microsoft.eShopOnContainers.WebMVC.Services { public class OrderingService : IOrderingService { - private HttpClient _apiClient; + private HttpClientWrapper _apiClient; private readonly string _remoteServiceBaseUrl; private readonly IOptionsSnapshot _settings; private readonly IHttpContextAccessor _httpContextAccesor; @@ -30,8 +31,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClient(); - _apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + _apiClient = new HttpClientWrapper(); + _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var ordersUrl = $"{_remoteServiceBaseUrl}/{Id}"; var dataString = await _apiClient.GetStringAsync(ordersUrl); @@ -46,8 +47,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClient(); - _apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + _apiClient = new HttpClientWrapper(); + _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var ordersUrl = _remoteServiceBaseUrl; var dataString = await _apiClient.GetStringAsync(ordersUrl); @@ -77,17 +78,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClient(); - _apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - _apiClient.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString()); + _apiClient = new HttpClientWrapper(); + _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + _apiClient.Inst.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString()); + var ordersUrl = $"{_remoteServiceBaseUrl}/new"; order.CardTypeId = 1; order.CardExpirationApiFormat(); SetFakeIdToProducts(order); - - StringContent content = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json"); - - var response = await _apiClient.PostAsync(ordersUrl, content); + + var response = await _apiClient.PostAsync(ordersUrl, order); if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) throw new Exception("Error creating order, try later"); diff --git a/src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs b/src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs new file mode 100644 index 000000000..24d64ab01 --- /dev/null +++ b/src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs @@ -0,0 +1,99 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Polly; +using Polly.Wrap; +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace WebMVC.Services.Utilities +{ + public class HttpClientWrapper + { + private HttpClient _client; + private PolicyWrap _policyWrapper; + private ILogger _logger; + public HttpClient Inst => _client; + public HttpClientWrapper() + { + _client = new HttpClient(); + _logger = new LoggerFactory().CreateLogger(nameof(HttpClientWrapper)); + + // Add Policies to be applied + _policyWrapper = Policy.WrapAsync( + CreateRetryPolicy(), + CreateCircuitBreakerPolicy() + ); + } + + private Policy CreateCircuitBreakerPolicy() + { + return Policy + .Handle() + .CircuitBreakerAsync( + // number of exceptions before breaking circuit + 3, + // time circuit opened before retry + TimeSpan.FromMinutes(1), + (exception, duration) => { + // on circuit opened + _logger.LogTrace("Circuit breaker opened"); + }, + () => { + // on circuit closed + _logger.LogTrace("Circuit breaker reset"); + } + ); + } + + private Policy CreateRetryPolicy() + { + return Policy + .Handle() + .WaitAndRetryAsync( + // number of retries + 3, + // exponential backofff + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + // on retry + (exception, timeSpan, retryCount, context) => + { + _logger.LogTrace($"Retry {retryCount} " + + $"of {context.PolicyKey} " + + $"at {context.ExecutionKey}, " + + $"due to: {exception}."); + }); + } + + public async Task GetStringAsync(string uri) + { + return await HttpInvoker(async () => + await _client.GetStringAsync(uri)); + } + + public async Task PostAsync(string uri, T item) + { + // a new StringContent must be created for each retry + // as it is disposed after each call + return await HttpInvoker(async () => + await _client.PostAsync(uri, + new StringContent(JsonConvert.SerializeObject(item), + System.Text.Encoding.UTF8, "application/json"))); + } + + public async Task DeleteAsync(string uri) + { + return await HttpInvoker(async () => + await _client.DeleteAsync(uri)); + } + + private async Task HttpInvoker(Func> action) + { + // Executes the action applying all + // the policies defined in the wrapper + return await _policyWrapper + .ExecuteAsync(async () => await action()); + } + } + +} diff --git a/src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs b/src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs deleted file mode 100644 index b9569c843..000000000 --- a/src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.eShopOnContainers.WebMVC.Services -{ - /// - /// When working with cloud services and Docker containers, it's very important to always catch - /// TimeoutException, and retry the operation. - /// RetryWithExponentialBackoff makes it easy to implement such pattern. - /// Usage: - /// var retry = new RetryWithExponentialBackoff(); - /// await retry.RunAsync(async ()=> - /// { - /// // work with HttpClient - /// }); - /// - public sealed class RetryWithExponentialBackoff - { - private readonly int maxRetries, delayMilliseconds, maxDelayMilliseconds; - - public RetryWithExponentialBackoff(int maxRetries = 50, int delayMilliseconds = 200, int maxDelayMilliseconds = 2000) - { - this.maxRetries = maxRetries; - this.delayMilliseconds = delayMilliseconds; - this.maxDelayMilliseconds = maxDelayMilliseconds; - } - - public async Task RunAsync(Func func) - { - ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries, this.delayMilliseconds, this.maxDelayMilliseconds); - retry: - try - { - await func(); - } - catch (Exception ex) when (ex is TimeoutException || ex is System.Net.Http.HttpRequestException) - { - Debug.WriteLine("Exception raised is: " + ex.GetType().ToString() + " -- Message: " + ex.Message + " -- Inner Message: " + ex.InnerException.Message); - await backoff.Delay(); - goto retry; - } - } - } - - - /// - /// Usage: - /// ExponentialBackoff backoff = new ExponentialBackoff(3, 10, 100); - /// retry: - /// try { - /// // ... - /// } - /// catch (Exception ex) { - /// await backoff.Delay(cancellationToken); - /// goto retry; - /// } - /// - public struct ExponentialBackoff - { - private readonly int m_maxRetries, m_delayMilliseconds, m_maxDelayMilliseconds; - private int m_retries, m_pow; - - public ExponentialBackoff(int maxRetries, int delayMilliseconds, int maxDelayMilliseconds) - { - m_maxRetries = maxRetries; - m_delayMilliseconds = delayMilliseconds; - m_maxDelayMilliseconds = maxDelayMilliseconds; - m_retries = 0; - m_pow = 1; - } - - public Task Delay() - { - if (m_retries == m_maxRetries) - { - throw new TimeoutException("Max retry attempts exceeded."); - } - ++m_retries; - if (m_retries < 31) - { - m_pow = m_pow << 1; // m_pow = Pow(2, m_retries - 1) - } - int delay = Math.Min(m_delayMilliseconds * (m_pow - 1) / 2, m_maxDelayMilliseconds); - return Task.Delay(delay); - } - } -} diff --git a/src/Web/WebMVC/Views/Order/Create.cshtml b/src/Web/WebMVC/Views/Order/Create.cshtml index 58108b8e9..e05d3a7b7 100644 --- a/src/Web/WebMVC/Views/Order/Create.cshtml +++ b/src/Web/WebMVC/Views/Order/Create.cshtml @@ -11,6 +11,13 @@
+
+ @foreach (var error in ViewData.ModelState.Values.SelectMany(err => err.Errors)) { + + } +

Shipping address

diff --git a/src/Web/WebMVC/WebMVC.csproj b/src/Web/WebMVC/WebMVC.csproj index eb282bca3..e03f5e2c9 100644 --- a/src/Web/WebMVC/WebMVC.csproj +++ b/src/Web/WebMVC/WebMVC.csproj @@ -32,6 +32,7 @@ All + From ddb03a0aa8f41108dbd93b4ceed6530fb4b4137a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Fri, 17 Mar 2017 13:12:34 +0100 Subject: [PATCH 30/80] Refactoing HttpClientApi --- src/Web/WebMVC/Services/BasketService.cs | 10 ++--- src/Web/WebMVC/Services/CatalogService.cs | 9 ++-- src/Web/WebMVC/Services/OrderingService.cs | 9 ++-- .../Services/Utilities/HttpApiClient.cs | 45 +++++++++++++++++++ ...ientWrapper.cs => HttpApiClientWrapper.cs} | 6 +-- .../WebMVC/Services/Utilities/IHttpClient.cs | 16 +++++++ src/Web/WebMVC/Startup.cs | 15 +++++-- src/Web/WebMVC/appsettings.json | 1 + 8 files changed, 87 insertions(+), 24 deletions(-) create mode 100644 src/Web/WebMVC/Services/Utilities/HttpApiClient.cs rename src/Web/WebMVC/Services/Utilities/{HttpClientWrapper.cs => HttpApiClientWrapper.cs} (96%) create mode 100644 src/Web/WebMVC/Services/Utilities/IHttpClient.cs diff --git a/src/Web/WebMVC/Services/BasketService.cs b/src/Web/WebMVC/Services/BasketService.cs index 087e02626..bcb138b87 100644 --- a/src/Web/WebMVC/Services/BasketService.cs +++ b/src/Web/WebMVC/Services/BasketService.cs @@ -14,15 +14,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public class BasketService : IBasketService { private readonly IOptionsSnapshot _settings; - private HttpClientWrapper _apiClient; + private IHttpClient _apiClient; private readonly string _remoteServiceBaseUrl; private IHttpContextAccessor _httpContextAccesor; - public BasketService(IOptionsSnapshot settings, IHttpContextAccessor httpContextAccesor) + public BasketService(IOptionsSnapshot settings, IHttpContextAccessor httpContextAccesor, IHttpClient httpClient) { _settings = settings; _remoteServiceBaseUrl = _settings.Value.BasketUrl; _httpContextAccesor = httpContextAccesor; + _apiClient = httpClient; } public async Task GetBasket(ApplicationUser user) @@ -30,7 +31,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - var _apiClient = new HttpClientWrapper(); _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}"; @@ -51,8 +51,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services { var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - - _apiClient = new HttpClientWrapper(); + _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var basketUrl = _remoteServiceBaseUrl; @@ -119,7 +118,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClientWrapper(); _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}"; var response = await _apiClient.DeleteAsync(basketUrl); diff --git a/src/Web/WebMVC/Services/CatalogService.cs b/src/Web/WebMVC/Services/CatalogService.cs index 7cafb29e0..83b139c1f 100644 --- a/src/Web/WebMVC/Services/CatalogService.cs +++ b/src/Web/WebMVC/Services/CatalogService.cs @@ -13,20 +13,19 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public class CatalogService : ICatalogService { private readonly IOptionsSnapshot _settings; - private HttpClientWrapper _apiClient; + private IHttpClient _apiClient; private readonly string _remoteServiceBaseUrl; - public CatalogService(IOptionsSnapshot settings, ILoggerFactory loggerFactory) { + public CatalogService(IOptionsSnapshot settings, ILoggerFactory loggerFactory, IHttpClient httpClient) { _settings = settings; _remoteServiceBaseUrl = $"{_settings.Value.CatalogUrl}/api/v1/catalog/"; - + _apiClient = httpClient; var log = loggerFactory.CreateLogger("catalog service"); log.LogDebug(settings.Value.CatalogUrl); } public async Task GetCatalogItems(int page,int take, int? brand, int? type) { - _apiClient = new HttpClientWrapper(); var itemsQs = $"items?pageIndex={page}&pageSize={take}"; var filterQs = ""; @@ -53,7 +52,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task> GetBrands() { - _apiClient = new HttpClientWrapper(); var url = $"{_remoteServiceBaseUrl}catalogBrands"; var dataString = await _apiClient.GetStringAsync(url); @@ -72,7 +70,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task> GetTypes() { - _apiClient = new HttpClientWrapper(); var url = $"{_remoteServiceBaseUrl}catalogTypes"; var dataString = await _apiClient.GetStringAsync(url); diff --git a/src/Web/WebMVC/Services/OrderingService.cs b/src/Web/WebMVC/Services/OrderingService.cs index ff98ac4f9..d53db3090 100644 --- a/src/Web/WebMVC/Services/OrderingService.cs +++ b/src/Web/WebMVC/Services/OrderingService.cs @@ -14,24 +14,23 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services { public class OrderingService : IOrderingService { - private HttpClientWrapper _apiClient; + private IHttpClient _apiClient; private readonly string _remoteServiceBaseUrl; private readonly IOptionsSnapshot _settings; private readonly IHttpContextAccessor _httpContextAccesor; - public OrderingService(IOptionsSnapshot settings, IHttpContextAccessor httpContextAccesor) + public OrderingService(IOptionsSnapshot settings, IHttpContextAccessor httpContextAccesor, IHttpClient httpClient) { _remoteServiceBaseUrl = $"{settings.Value.OrderingUrl}/api/v1/orders"; _settings = settings; _httpContextAccesor = httpContextAccesor; + _apiClient = httpClient; } async public Task GetOrder(ApplicationUser user, string Id) { var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - - _apiClient = new HttpClientWrapper(); _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var ordersUrl = $"{_remoteServiceBaseUrl}/{Id}"; @@ -47,7 +46,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClientWrapper(); _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var ordersUrl = _remoteServiceBaseUrl; @@ -78,7 +76,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var context = _httpContextAccesor.HttpContext; var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient = new HttpClientWrapper(); _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); _apiClient.Inst.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString()); diff --git a/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs b/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs new file mode 100644 index 000000000..8b142f393 --- /dev/null +++ b/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace WebMVC.Services.Utilities +{ + public class HttpApiClient : IHttpClient + { + private HttpClient _client; + private ILogger _logger; + public HttpClient Inst => _client; + public HttpApiClient() + { + _client = new HttpClient(); + _logger = new LoggerFactory().CreateLogger(nameof(HttpApiClientWrapper)); + } + + public async Task GetStringAsync(string uri) + { + return await HttpInvoker(async () => + await _client.GetStringAsync(uri)); + } + + public async Task PostAsync(string uri, T item) + { + var contentString = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"); + return await HttpInvoker(async () => + await _client.PostAsync(uri, contentString)); + } + + public async Task DeleteAsync(string uri) + { + return await HttpInvoker(async () => + await _client.DeleteAsync(uri)); + } + + private async Task HttpInvoker(Func> action) + { + return await action(); + } + } +} + diff --git a/src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs similarity index 96% rename from src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs rename to src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs index 24d64ab01..dba0db9df 100644 --- a/src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs +++ b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs @@ -8,16 +8,16 @@ using System.Threading.Tasks; namespace WebMVC.Services.Utilities { - public class HttpClientWrapper + public class HttpApiClientWrapper : IHttpClient { private HttpClient _client; private PolicyWrap _policyWrapper; private ILogger _logger; public HttpClient Inst => _client; - public HttpClientWrapper() + public HttpApiClientWrapper() { _client = new HttpClient(); - _logger = new LoggerFactory().CreateLogger(nameof(HttpClientWrapper)); + _logger = new LoggerFactory().CreateLogger(nameof(HttpApiClientWrapper)); // Add Policies to be applied _policyWrapper = Policy.WrapAsync( diff --git a/src/Web/WebMVC/Services/Utilities/IHttpClient.cs b/src/Web/WebMVC/Services/Utilities/IHttpClient.cs new file mode 100644 index 000000000..2a36466bc --- /dev/null +++ b/src/Web/WebMVC/Services/Utilities/IHttpClient.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace WebMVC.Services.Utilities +{ + public interface IHttpClient + { + HttpClient Inst { get; } + Task GetStringAsync(string uri); + Task PostAsync(string uri, T item); + Task DeleteAsync(string uri); + } +} diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index 8bd7708d3..7888ae253 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -14,6 +14,7 @@ using Microsoft.IdentityModel.Tokens; using Microsoft.AspNetCore.Http; using System.Threading; using Microsoft.Extensions.Options; +using WebMVC.Services.Utilities; namespace Microsoft.eShopOnContainers.WebMVC { @@ -43,14 +44,22 @@ namespace Microsoft.eShopOnContainers.WebMVC { services.AddMvc(); services.Configure(Configuration); - + // Add application services. - services.AddSingleton(); - + services.AddSingleton(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient, IdentityParser>(); + + if(Configuration.GetValue("ActivateCircuitBreaker") == bool.TrueString) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Web/WebMVC/appsettings.json b/src/Web/WebMVC/appsettings.json index 9fe15aa25..b848c11b1 100644 --- a/src/Web/WebMVC/appsettings.json +++ b/src/Web/WebMVC/appsettings.json @@ -4,6 +4,7 @@ "BasketUrl": "http://localhost:5103", "IdentityUrl": "http://localhost:5105", "CallBackUrl": "http://localhost:5100/", + "ActivateCircuitBreaker": "True", "Logging": { "IncludeScopes": false, "LogLevel": { From bbc14819790db80e31c751786371202e2882aaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Fri, 17 Mar 2017 13:20:52 +0100 Subject: [PATCH 31/80] Removed event file from catalog api csproj --- src/Services/Catalog/Catalog.API/Catalog.API.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index e46c19a56..5cef06749 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -22,7 +22,6 @@ PreserveNewest - PreserveNewest From e996593d327dcc01b332c4bd7a0500edd8e97917 Mon Sep 17 00:00:00 2001 From: dsanz Date: Fri, 17 Mar 2017 15:57:57 +0100 Subject: [PATCH 32/80] Add functional test for integration events. Implement IDisposable for EventBusRabbitMQ. --- docker-compose-external.yml | 5 + .../EventBusRabbitMQ/EventBusRabbitMQ.cs | 18 ++- .../ProductPriceChangedEventHandler.cs | 11 +- src/Services/Basket/Basket.API/Startup.cs | 32 +++-- .../FunctionalTests/FunctionalTests.csproj | 4 + .../Services/Basket/BasketScenariosBase.cs | 34 +++++ .../Services/Basket/BasketTestsStartup.cs | 29 ++++ .../Services/Catalog/CatalogScenarios.cs | 55 -------- .../Services/Catalog/CatalogScenariosBase.cs | 2 + .../Services/Catalog/CatalogTestsStartup.cs | 15 -- .../Services/IntegrationEventsScenarios.cs | 132 ++++++++++++++++++ .../Services/FunctionalTests/appsettings.json | 6 + 12 files changed, 252 insertions(+), 91 deletions(-) create mode 100644 test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs create mode 100644 test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs delete mode 100644 test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs delete mode 100644 test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs create mode 100644 test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs create mode 100644 test/Services/FunctionalTests/appsettings.json diff --git a/docker-compose-external.yml b/docker-compose-external.yml index 6bce32a3e..ff1641cdd 100644 --- a/docker-compose-external.yml +++ b/docker-compose-external.yml @@ -8,3 +8,8 @@ services: image: redis ports: - "6379:6379" + + rabbitmq: + image: rabbitmq + ports: + - "5672:5672" diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 3582e8cc4..e31c43396 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { - public class EventBusRabbitMQ : IEventBus + public class EventBusRabbitMQ : IEventBus, IDisposable { private readonly string _brokerName = "eshop_event_bus"; private readonly string _connectionString; @@ -31,6 +31,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ _handlers = new Dictionary>(); _eventTypes = new List(); } + public void Publish(IntegrationEvent @event) { var eventName = @event.GetType().Name; @@ -92,14 +93,24 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ if (_handlers.Keys.Count == 0) { _queueName = string.Empty; - _connection.Item1.Close(); - _connection.Item2.Close(); + _connection.Item1.Dispose(); + _connection.Item2.Dispose(); } } } } + public void Dispose() + { + if (_connection != null) + { + _handlers.Clear(); + _connection.Item1.Dispose(); + _connection.Item2.Dispose(); + } + } + private IModel GetChannel() { if (_connection != null) @@ -151,5 +162,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } } } + } } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs index 1a7599162..f32b71af3 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedEventHandler.cs @@ -20,7 +20,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even foreach (var id in userIds) { var basket = await _repository.GetBasket(id); - await UpdateBasket(@event.ProductId, @event.NewPrice, basket); + await UpdateBasket(@event.ProductId, @event.NewPrice, basket); } } @@ -31,9 +31,12 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even { foreach (var item in itemsToUpdate) { - var originalPrice = item.UnitPrice; - item.UnitPrice = newPrice; - item.OldUnitPrice = originalPrice; + if(item.UnitPrice != newPrice) + { + var originalPrice = item.UnitPrice; + item.UnitPrice = newPrice; + item.OldUnitPrice = originalPrice; + } } await _repository.UpdateBasket(basket); } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index b96029718..49197782f 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -13,6 +13,7 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; +using System; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -80,13 +81,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; - var eventBus = new EventBusRabbitMQ(configuration.EventBusConnection); - services.AddSingleton(eventBus); + services.AddSingleton(provider => new EventBusRabbitMQ(configuration.EventBusConnection)); - - var catalogPriceHandler = serviceProvider.GetService>(); - eventBus.Subscribe(catalogPriceHandler); - } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -100,20 +96,28 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // Use frameworks app.UseCors("CorsPolicy"); - var identityUrl = Configuration.GetValue("IdentityUrl"); - - app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions - { - Authority = identityUrl.ToString(), - ScopeName = "basket", - RequireHttpsMetadata = false - }); + ConfigureAuth(app); app.UseMvcWithDefaultRoute(); app.UseSwagger() .UseSwaggerUi(); + var catalogPriceHandler = app.ApplicationServices.GetService>(); + var eventBus = app.ApplicationServices.GetRequiredService(); + eventBus.Subscribe(catalogPriceHandler); + } + + protected virtual void ConfigureAuth(IApplicationBuilder app) + { + var identityUrl = Configuration.GetValue("IdentityUrl"); + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + { + Authority = identityUrl.ToString(), + ScopeName = "basket", + RequireHttpsMetadata = false + }); + } } } diff --git a/test/Services/FunctionalTests/FunctionalTests.csproj b/test/Services/FunctionalTests/FunctionalTests.csproj index 0e24f233d..34fa68d42 100644 --- a/test/Services/FunctionalTests/FunctionalTests.csproj +++ b/test/Services/FunctionalTests/FunctionalTests.csproj @@ -13,12 +13,16 @@ + + + PreserveNewest + PreserveNewest diff --git a/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs b/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs new file mode 100644 index 000000000..5de55abb3 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Basket/BasketScenariosBase.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace FunctionalTests.Services.Basket +{ + public class BasketScenariosBase + { + public TestServer CreateServer() + { + var webHostBuilder = new WebHostBuilder(); + webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory()); + webHostBuilder.UseStartup(); + + return new TestServer(webHostBuilder); + } + + public static class Get + { + public static string GetBasketByCustomer(string customerId) + { + return $"/{customerId}"; + } + } + + public static class Post + { + public static string CreateBasket = "/"; + } + } +} diff --git a/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs b/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs new file mode 100644 index 000000000..a0e625d96 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Basket/BasketTestsStartup.cs @@ -0,0 +1,29 @@ +using Microsoft.eShopOnContainers.Services.Basket.API; +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; +using FunctionalTests.Middleware; + +namespace FunctionalTests.Services.Basket +{ + public class BasketTestsStartup : Startup + { + public BasketTestsStartup(IHostingEnvironment env) : base(env) + { + } + + protected override void ConfigureAuth(IApplicationBuilder app) + { + if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) + { + app.UseMiddleware(); + } + else + { + base.ConfigureAuth(app); + } + } + } +} diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs deleted file mode 100644 index ec4643ce7..000000000 --- a/test/Services/FunctionalTests/Services/Catalog/CatalogScenarios.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.eShopOnContainers.Services.Catalog.API.Model; -using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace FunctionalTests.Services.Catalog -{ - public class CatalogScenarios : CatalogScenariosBase - { - [Fact] - public async Task Post_update_a_catalogitem_price_and_catalogitem_is_returned_modified() - { - using (var server = CreateServer()) - { - var client = server.CreateClient(); - - // Arrange - var itemToModify = GetCatalogItem(); - var newPrice = new Random().Next(1, 200); - itemToModify.Price = newPrice; - - // Act - var postRes = await client.PostAsync(Post.UpdateCatalogProduct, - new StringContent(JsonConvert.SerializeObject(itemToModify), - UTF8Encoding.UTF8, "application/json")); - var response = await client.GetAsync(Get.ProductByName(itemToModify.Name)); - var result = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); - var item = result.Data.First(); - - // Assert - Assert.Equal(result.Count, 1); - Assert.Equal(itemToModify.Id, item.Id); - Assert.Equal(newPrice, item.Price); - } - - } - - private CatalogItem GetCatalogItem() - { - return new CatalogItem() - { - Id = 1, - Price = 12.5M, - Name = ".NET Bot Black Sweatshirt" - }; - } - - } -} diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs index b4107b3bc..727e58a00 100644 --- a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs +++ b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs @@ -24,6 +24,8 @@ namespace FunctionalTests.Services.Catalog { public static string Orders = "api/v1/orders"; + public static string Items = "api/v1/catalog/items"; + public static string ProductByName(string name) { return $"api/v1/catalog/items/withname/{name}"; diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs deleted file mode 100644 index f06f9b248..000000000 --- a/test/Services/FunctionalTests/Services/Catalog/CatalogTestsStartup.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.eShopOnContainers.Services.Catalog.API; -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.AspNetCore.Hosting; - -namespace FunctionalTests.Services.Catalog -{ - public class CatalogTestsStartup : Startup - { - public CatalogTestsStartup(IHostingEnvironment env) : base(env) - { - } - } -} diff --git a/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs b/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs new file mode 100644 index 000000000..57254db1f --- /dev/null +++ b/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs @@ -0,0 +1,132 @@ +using FunctionalTests.Services.Basket; +using FunctionalTests.Services.Catalog; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.eShopOnContainers.Services.Catalog.API.Model; +using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; +using Newtonsoft.Json; +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using System.Net.Http; +using System.Threading; + +namespace FunctionalTests.Services +{ + public class IntegrationEventsScenarios + { + [Fact] + public async Task Post_update_product_price_and_catalog_and_basket_list_modified() + { + decimal priceModification = 0.15M; + string userId = "JohnId"; + + using (var catalogServer = new CatalogScenariosBase().CreateServer()) + using (var basketServer = new BasketScenariosBase().CreateServer()) + { + var catalogClient = catalogServer.CreateClient(); + var basketClient = basketServer.CreateClient(); + + // GIVEN a product catalog list + var originalCatalogProducts = await GetCatalogAsync(catalogClient); + + // AND a user basket filled with products + var basket = ComposeBasket(userId, originalCatalogProducts.Data.Take(3)); + var res = await basketClient.PostAsync( + BasketScenariosBase.Post.CreateBasket, + new StringContent(JsonConvert.SerializeObject(basket), UTF8Encoding.UTF8, "application/json") + ); + + // WHEN the price of one product is modified in the catalog + var itemToModify = basket.Items[2]; + var oldPrice = itemToModify.UnitPrice; + var newPrice = oldPrice + priceModification; + var pRes = await catalogClient.PostAsync(CatalogScenariosBase.Post.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice), UTF8Encoding.UTF8, "application/json")); + + var modifiedCatalogProducts = await GetCatalogAsync(catalogClient); + + var itemUpdated = await GetUpdatedBasketItem(newPrice, itemToModify.ProductId, userId, basketClient); + + if (itemUpdated == null) + { + Assert.False(true, $"The basket service has not been updated."); + } + else + { + //THEN the product price changes in the catalog + Assert.Equal(newPrice, modifiedCatalogProducts.Data.Single(it => it.Id == int.Parse(itemToModify.ProductId)).Price); + + // AND the products in the basket reflects the changed priced and the original price + Assert.Equal(newPrice, itemUpdated.UnitPrice); + Assert.Equal(oldPrice, itemUpdated.OldUnitPrice); + } + } + } + + private async Task GetUpdatedBasketItem(decimal newPrice, string productId, string userId, HttpClient basketClient) + { + bool continueLoop = true; + var counter = 0; + BasketItem itemUpdated = null; + + while (continueLoop && counter < 20) + { + //get the basket and verify that the price of the modified product is updated + var basketGetResponse = await basketClient.GetAsync(BasketScenariosBase.Get.GetBasketByCustomer(userId)); + var basketUpdated = JsonConvert.DeserializeObject(await basketGetResponse.Content.ReadAsStringAsync()); + + itemUpdated = basketUpdated.Items.Single(pr => pr.ProductId == productId); + + if (itemUpdated.UnitPrice == newPrice) + { + continueLoop = false; + } + else + { + counter++; + await Task.Delay(100); + } + } + + return itemUpdated; + } + + private async Task> GetCatalogAsync(HttpClient catalogClient) + { + var response = await catalogClient.GetAsync(CatalogScenariosBase.Get.Items); + var items = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(items); + } + + private string ChangePrice(BasketItem itemToModify, decimal newPrice) + { + var item = new CatalogItem() + { + Id = int.Parse(itemToModify.ProductId), + Price = newPrice + }; + return JsonConvert.SerializeObject(item); + } + + private CustomerBasket ComposeBasket(string customerId, IEnumerable items) + { + var basket = new CustomerBasket(customerId); + foreach (var item in items) + { + basket.Items.Add(new BasketItem() + { + Id = Guid.NewGuid().ToString(), + UnitPrice = item.Price, + PictureUrl = item.PictureUri, + ProductId = item.Id.ToString(), + OldUnitPrice = 0, + ProductName = item.Name, + Quantity = 1 + }); + } + return basket; + } + } +} \ No newline at end of file diff --git a/test/Services/FunctionalTests/appsettings.json b/test/Services/FunctionalTests/appsettings.json new file mode 100644 index 000000000..2b1bdb46a --- /dev/null +++ b/test/Services/FunctionalTests/appsettings.json @@ -0,0 +1,6 @@ +{ + "ConnectionString": "127.0.0.1", + "IdentityUrl": "http://localhost:5105", + "isTest": "true", + "EventBusConnection": "localhost" +} From 9fdb5e6c96156019413a1761bb3d9afce6fbfecc Mon Sep 17 00:00:00 2001 From: etomas Date: Fri, 17 Mar 2017 16:28:05 +0100 Subject: [PATCH 33/80] FailingMiddleware for Ordering API --- .../Middlewares/FailingMiddleware.cs | 79 +++++++++++++++++++ .../FailingMiddlewareAppBuilderExtensions.cs | 23 ++++++ .../Middlewares/FailingOptions.cs | 12 +++ src/Services/Ordering/Ordering.API/Startup.cs | 3 + 4 files changed, 117 insertions(+) create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddleware.cs create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddlewareAppBuilderExtensions.cs create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingOptions.cs diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddleware.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddleware.cs new file mode 100644 index 000000000..0206eaa38 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddleware.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Infrastructure.Middlewares +{ + public class FailingMiddleware + { + private readonly RequestDelegate _next; + private bool _mustFail; + private readonly FailingOptions _options; + public FailingMiddleware(RequestDelegate next, FailingOptions options) + { + _next = next; + _options = options; + _mustFail = false; + } + public async Task Invoke(HttpContext context) + { + var path = context.Request.Path; + if (path.Equals(_options.ConfigPath, StringComparison.OrdinalIgnoreCase)) + { + await ProcessConfigRequest(context); + return; + } + + + if (_mustFail) + { + context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync("Failed due to FailingMiddleware enabled."); + } + else + { + await _next.Invoke(context); + } + } + + private async Task ProcessConfigRequest(HttpContext context) + { + int i = 0; + var enable = context.Request.Query.Keys.Any(k => k == "enable"); + var disable = context.Request.Query.Keys.Any(k => k == "disable"); + + if (enable && disable) + { + throw new ArgumentException("Must use enable or disable querystring values, but not both"); + } + + if (disable) + { + _mustFail = false; + await SendOkResponse(context, "FailingMiddleware disabled. Further requests will be processed."); + return; + } + if (enable) + { + _mustFail = true; + await SendOkResponse(context, "FailingMiddleware enabled. Further requests will return HTTP 500"); + return; + } + + // If reach here, that means that no valid parameter has been passed. Just output status + await SendOkResponse(context, string.Format("FailingMiddleware is {0}", _mustFail ? "enabled" : "disabled")); + return; + } + + private async Task SendOkResponse(HttpContext context, string message) + { + context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync(message); + } + + } +} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddlewareAppBuilderExtensions.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddlewareAppBuilderExtensions.cs new file mode 100644 index 000000000..15bf596cc --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddlewareAppBuilderExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Builder; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Infrastructure.Middlewares +{ + public static class FailingMiddlewareAppBuilderExtensions + { + public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder) + { + return UseFailingMiddleware(builder, null); + } + public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action action) + { + var options = new FailingOptions(); + action?.Invoke(options); + builder.UseMiddleware(options); + return builder; + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingOptions.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingOptions.cs new file mode 100644 index 000000000..a93a2b49b --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingOptions.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Infrastructure.Middlewares +{ + public class FailingOptions + { + public string ConfigPath = "/Failing"; + } +} diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index f1d58be1d..3f0840b07 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -3,6 +3,7 @@ using AspNetCore.Http; using Autofac; using Autofac.Extensions.DependencyInjection; + using global::Ordering.API.Infrastructure.Middlewares; using Infrastructure; using Infrastructure.Auth; using Infrastructure.AutofacModules; @@ -109,6 +110,8 @@ app.UseCors("CorsPolicy"); + app.UseFailingMiddleware(); + ConfigureAuth(app); app.UseMvcWithDefaultRoute(); From c7e317413ebba3cdec6633c4996a2d0e0feec778 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Fri, 17 Mar 2017 18:36:34 -0700 Subject: [PATCH 34/80] Refactoring Domain Events --- ...PaymentMethodVerifiedDomainEventHandler.cs | 36 +++++++++++++++++++ ...r.cs => OrderStartedDomainEventHandler.cs} | 10 +++--- .../PaymentMethodCheckedDomainEventHandler.cs | 33 ----------------- .../AutofacModules/MediatorModule.cs | 2 +- .../AggregatesModel/BuyerAggregate/Buyer.cs | 6 ++-- .../AggregatesModel/OrderAggregate/Order.cs | 12 +++---- ... BuyerPaymentMethodVerifiedDomainEvent.cs} | 4 +-- ...ainEvent.cs => OrderStartedDomainEvent.cs} | 4 +-- .../MediatorExtension.cs | 2 +- .../OrderingContext.cs | 4 +-- .../Ordering/Domain/BuyerAggregateTest.cs | 4 +-- .../Ordering/Domain/OrderAggregateTest.cs | 4 +-- 12 files changed, 62 insertions(+), 59 deletions(-) create mode 100644 src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerPaymentMethodVerifiedDomainEventHandler.cs rename src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/{OrderCreatedDomainEventHandler.cs => OrderStartedDomainEventHandler.cs} (83%) delete mode 100644 src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs rename src/Services/Ordering/Ordering.Domain/Events/{PaymentMethodCheckedDomainEvent.cs => BuyerPaymentMethodVerifiedDomainEvent.cs} (76%) rename src/Services/Ordering/Ordering.Domain/Events/{OrderCreatedDomainEvent.cs => OrderStartedDomainEvent.cs} (92%) diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerPaymentMethodVerifiedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerPaymentMethodVerifiedDomainEventHandler.cs new file mode 100644 index 000000000..70360ce32 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerPaymentMethodVerifiedDomainEventHandler.cs @@ -0,0 +1,36 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.Extensions.Logging; +using Ordering.Domain.Events; +using System; +using System.Threading.Tasks; + +namespace Ordering.API.Application.DomainEventHandlers +{ + public class BuyerPaymentMethodVerifiedDomainEventHandler : IAsyncNotificationHandler + { + private readonly IOrderRepository _orderRepository; + private readonly ILoggerFactory _logger; + public BuyerPaymentMethodVerifiedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger) + { + _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + // Domain Logic comment: + // When the Buyer and Buyer's payment method have been created or verified that they existed, + // then we can update the original Order with the BuyerId and PaymentId (foreign keys) + public async Task Handle(BuyerPaymentMethodVerifiedDomainEvent buyerPaymentMethodVerifiedEvent) + { + var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId); + orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id); + orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id); + + await _orderRepository.UnitOfWork + .SaveEntitiesAsync(); + + _logger.CreateLogger(nameof(BuyerPaymentMethodVerifiedDomainEventHandler)) + .LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }"); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCreatedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedDomainEventHandler.cs similarity index 83% rename from src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCreatedDomainEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedDomainEventHandler.cs index 8aed7f1b2..249976f0c 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCreatedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedDomainEventHandler.cs @@ -8,20 +8,20 @@ using System.Threading.Tasks; namespace Ordering.API.Application.DomainEventHandlers { - public class OrderCreatedDomainEventHandler : IAsyncNotificationHandler + public class OrderStartedDomainEventHandler : IAsyncNotificationHandler { private readonly ILoggerFactory _logger; private readonly IBuyerRepository _buyerRepository; private readonly IIdentityService _identityService; - public OrderCreatedDomainEventHandler(ILoggerFactory logger, IBuyerRepository buyerRepository, IIdentityService identityService) + public OrderStartedDomainEventHandler(ILoggerFactory logger, IBuyerRepository buyerRepository, IIdentityService identityService) { _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task Handle(OrderCreatedDomainEvent orderNotification) + public async Task Handle(OrderStartedDomainEvent orderNotification) { var cardTypeId = orderNotification.CardTypeId != 0 ? orderNotification.CardTypeId : 1; @@ -33,7 +33,7 @@ namespace Ordering.API.Application.DomainEventHandlers buyer = new Buyer(buyerGuid); } - var payment = buyer.AddPaymentMethod(cardTypeId, + var paymentMethod = buyer.VerifyOrAddPaymentMethod(cardTypeId, $"Payment Method on {DateTime.UtcNow}", orderNotification.CardNumber, orderNotification.CardSecurityNumber, @@ -46,7 +46,7 @@ namespace Ordering.API.Application.DomainEventHandlers await _buyerRepository.UnitOfWork .SaveEntitiesAsync(); - _logger.CreateLogger(nameof(OrderCreatedDomainEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}."); + _logger.CreateLogger(nameof(OrderStartedDomainEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}."); } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs deleted file mode 100644 index eb833f70e..000000000 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/PaymentMethodCheckedDomainEventHandler.cs +++ /dev/null @@ -1,33 +0,0 @@ -using MediatR; -using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; -using Microsoft.Extensions.Logging; -using Ordering.Domain.Events; -using System; -using System.Threading.Tasks; - -namespace Ordering.API.Application.DomainEventHandlers -{ - public class PaymentMethodCheckedDomainEventHandler : IAsyncNotificationHandler - { - private readonly IOrderRepository _orderRepository; - private readonly ILoggerFactory _logger; - public PaymentMethodCheckedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger) - { - _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle(PaymentMethodCheckedDomainEvent paymentMethodNotification) - { - var orderToUpdate = await _orderRepository.GetAsync(paymentMethodNotification.OrderId); - orderToUpdate.SetBuyerId(paymentMethodNotification.Buyer.Id); - orderToUpdate.SetPaymentId(paymentMethodNotification.Payment.Id); - - await _orderRepository.UnitOfWork - .SaveEntitiesAsync(); - - _logger.CreateLogger(nameof(PaymentMethodCheckedDomainEventHandler)) - .LogTrace($"Order with Id: {paymentMethodNotification.OrderId} has been successfully updated with a new payment method id: { paymentMethodNotification.Payment.Id }"); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index 8d76fd9f5..0f0f8b098 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -24,7 +24,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof .Select(i => new KeyedService("IAsyncRequestHandler", i))); builder - .RegisterAssemblyTypes(typeof(OrderCreatedDomainEventHandler).GetTypeInfo().Assembly) + .RegisterAssemblyTypes(typeof(OrderStartedDomainEventHandler).GetTypeInfo().Assembly) .Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>))) .AsImplementedInterfaces(); diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs index cedb42c06..689122189 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs @@ -24,7 +24,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity)); } - public PaymentMethod AddPaymentMethod( + public PaymentMethod VerifyOrAddPaymentMethod( int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration, int orderId) { @@ -33,7 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B if (existingPayment != null) { - AddDomainEvent(new PaymentMethodCheckedDomainEvent(this, existingPayment, orderId)); + AddDomainEvent(new BuyerPaymentMethodVerifiedDomainEvent(this, existingPayment, orderId)); return existingPayment; } else @@ -41,7 +41,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration); _paymentMethods.Add(payment); - AddDomainEvent(new PaymentMethodCheckedDomainEvent(this, payment, orderId)); + AddDomainEvent(new BuyerPaymentMethodVerifiedDomainEvent(this, payment, orderId)); return payment; } } diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index 6ccece658..a3df725dd 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -52,10 +52,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O _orderDate = DateTime.UtcNow; Address = address; - // Add the OrderCreatedEvent to the domain events collection + // Add the OrderStarterDomainEvent to the domain events collection // to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ] - AddOrderCreatedDomainEvent(cardTypeId, cardNumber, - cardSecurityNumber, cardHolderName, cardExpiration); + AddOrderStartedDomainEvent(cardTypeId, cardNumber, + cardSecurityNumber, cardHolderName, cardExpiration); } // DDD Patterns comment @@ -96,14 +96,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O _buyerId = id; } - private void AddOrderCreatedDomainEvent(int cardTypeId, string cardNumber, + private void AddOrderStartedDomainEvent(int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) { - var orderCreatedDomainEvent = new OrderCreatedDomainEvent( + var orderStartedDomainEvent = new OrderStartedDomainEvent( this, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - AddDomainEvent(orderCreatedDomainEvent); + this.AddDomainEvent(orderStartedDomainEvent); } } } diff --git a/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodCheckedDomainEvent.cs b/src/Services/Ordering/Ordering.Domain/Events/BuyerPaymentMethodVerifiedDomainEvent.cs similarity index 76% rename from src/Services/Ordering/Ordering.Domain/Events/PaymentMethodCheckedDomainEvent.cs rename to src/Services/Ordering/Ordering.Domain/Events/BuyerPaymentMethodVerifiedDomainEvent.cs index a729361c7..3a3a00a0a 100644 --- a/src/Services/Ordering/Ordering.Domain/Events/PaymentMethodCheckedDomainEvent.cs +++ b/src/Services/Ordering/Ordering.Domain/Events/BuyerPaymentMethodVerifiedDomainEvent.cs @@ -6,14 +6,14 @@ using System.Text; namespace Ordering.Domain.Events { - public class PaymentMethodCheckedDomainEvent + public class BuyerPaymentMethodVerifiedDomainEvent : IAsyncNotification { public Buyer Buyer { get; private set; } public PaymentMethod Payment { get; private set; } public int OrderId { get; private set; } - public PaymentMethodCheckedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId) + public BuyerPaymentMethodVerifiedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId) { Buyer = buyer; Payment = payment; diff --git a/src/Services/Ordering/Ordering.Domain/Events/OrderCreatedDomainEvent.cs b/src/Services/Ordering/Ordering.Domain/Events/OrderStartedDomainEvent.cs similarity index 92% rename from src/Services/Ordering/Ordering.Domain/Events/OrderCreatedDomainEvent.cs rename to src/Services/Ordering/Ordering.Domain/Events/OrderStartedDomainEvent.cs index 04fdb41db..439a5d9ab 100644 --- a/src/Services/Ordering/Ordering.Domain/Events/OrderCreatedDomainEvent.cs +++ b/src/Services/Ordering/Ordering.Domain/Events/OrderStartedDomainEvent.cs @@ -9,7 +9,7 @@ namespace Ordering.Domain.Events /// /// Event used when an order is created /// - public class OrderCreatedDomainEvent + public class OrderStartedDomainEvent : IAsyncNotification { public int CardTypeId { get; private set; } @@ -19,7 +19,7 @@ namespace Ordering.Domain.Events public DateTime CardExpiration { get; private set; } public Order Order { get; private set; } - public OrderCreatedDomainEvent(Order order, + public OrderStartedDomainEvent(Order order, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) diff --git a/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs b/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs index 21fc5e21f..99e94e086 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs @@ -8,7 +8,7 @@ namespace Ordering.Infrastructure { public static class MediatorExtension { - public static async Task RaiseDomainEventsAsync(this IMediator mediator, OrderingContext ctx) + public static async Task DispatchDomainEventsAsync(this IMediator mediator, OrderingContext ctx) { var domainEntities = ctx.ChangeTracker.Entries().Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any()); var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList(); diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 56790185e..de3f870da 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -239,8 +239,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure { var result = await base.SaveChangesAsync(); - // Dispatch the Domain Events collection right after saving/commiting data into the database - await _mediator.RaiseDomainEventsAsync(this); + // Dispatch the Domain Events collection right after saving/committing data into the database + await _mediator.DispatchDomainEventsAsync(this); return result; } } diff --git a/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs index 0703ed197..32e3bb807 100644 --- a/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs @@ -45,7 +45,7 @@ public class BuyerAggregateTest var fakeBuyerItem = new Buyer(identity); //Act - var result = fakeBuyerItem.AddPaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration, orderId); + var result = fakeBuyerItem.VerifyOrAddPaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration, orderId); //Assert Assert.NotNull(result); @@ -119,7 +119,7 @@ public class BuyerAggregateTest //Act var fakeBuyer = new Buyer(Guid.NewGuid().ToString()); - fakeBuyer.AddPaymentMethod(cardTypeId, alias, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration, orderId); + fakeBuyer.VerifyOrAddPaymentMethod(cardTypeId, alias, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration, orderId); //Assert Assert.Equal(fakeBuyer.DomainEvents.Count, expectedResult); diff --git a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs index c420951da..61fac6cf1 100644 --- a/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs +++ b/test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs @@ -133,7 +133,7 @@ public class OrderAggregateTest //Act var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - fakeOrder.AddDomainEvent(new OrderCreatedDomainEvent(fakeOrder,cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration)); + fakeOrder.AddDomainEvent(new OrderStartedDomainEvent(fakeOrder,cardTypeId,cardNumber,cardSecurityNumber,cardHolderName,cardExpiration)); //Assert Assert.Equal(fakeOrder.DomainEvents.Count, expectedResult); } @@ -153,7 +153,7 @@ public class OrderAggregateTest var cardHolderName = "FakeName"; var cardExpiration = DateTime.Now.AddYears(1); var fakeOrder = new Order(new Address(street, city, state, country, zipcode), cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); - var @fakeEvent = new OrderCreatedDomainEvent(fakeOrder, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); + var @fakeEvent = new OrderStartedDomainEvent(fakeOrder, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); var expectedResult = 1; //Act From 3ec61e0df64097b7c6524fbe58fee7a6173c84ed Mon Sep 17 00:00:00 2001 From: etomas Date: Sat, 18 Mar 2017 11:13:20 +0100 Subject: [PATCH 35/80] Tried patch proposed by Sandeep Bansal --- _docker/redis/Dockerfile.nanowin | 2 ++ src/Services/Basket/Basket.API/Dockerfile.nanowin | 1 + src/Services/Catalog/Catalog.API/Dockerfile.nanowin | 1 + src/Services/Identity/Identity.API/Dockerfile.nanowin | 1 + src/Services/Ordering/Ordering.API/Dockerfile.nanowin | 1 + src/Web/WebMVC/Dockerfile.nanowin | 1 + src/Web/WebSPA/Dockerfile.nanowin | 1 + 7 files changed, 8 insertions(+) diff --git a/_docker/redis/Dockerfile.nanowin b/_docker/redis/Dockerfile.nanowin index 6a36d1947..a08bcb0b6 100644 --- a/_docker/redis/Dockerfile.nanowin +++ b/_docker/redis/Dockerfile.nanowin @@ -22,6 +22,8 @@ RUN Get-Content redis.windows.conf | Where { $_ -notmatch 'bind 127.0.0.1' } | S EXPOSE 6379 +RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord + # Define our command to be run when launching the container CMD .\\redis-server.exe .\\redis.unprotected.conf --port 6379 ; \ Write-Host Redis Started... ; \ diff --git a/src/Services/Basket/Basket.API/Dockerfile.nanowin b/src/Services/Basket/Basket.API/Dockerfile.nanowin index 41127e339..0541a26cc 100644 --- a/src/Services/Basket/Basket.API/Dockerfile.nanowin +++ b/src/Services/Basket/Basket.API/Dockerfile.nanowin @@ -1,6 +1,7 @@ FROM microsoft/dotnet:1.1-runtime-nanoserver ARG source WORKDIR /app +RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord EXPOSE 80 COPY ${source:-obj/Docker/publish} . ENTRYPOINT ["dotnet", "Basket.API.dll"] diff --git a/src/Services/Catalog/Catalog.API/Dockerfile.nanowin b/src/Services/Catalog/Catalog.API/Dockerfile.nanowin index 5aad21ad1..c40d3a4d0 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile.nanowin +++ b/src/Services/Catalog/Catalog.API/Dockerfile.nanowin @@ -1,6 +1,7 @@ FROM microsoft/dotnet:1.1-runtime-nanoserver ARG source WORKDIR /app +RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord EXPOSE 80 COPY ${source:-obj/Docker/publish} . ENTRYPOINT ["dotnet", "Catalog.API.dll"] diff --git a/src/Services/Identity/Identity.API/Dockerfile.nanowin b/src/Services/Identity/Identity.API/Dockerfile.nanowin index 01f5eb8c5..b950d8334 100644 --- a/src/Services/Identity/Identity.API/Dockerfile.nanowin +++ b/src/Services/Identity/Identity.API/Dockerfile.nanowin @@ -1,6 +1,7 @@ FROM microsoft/dotnet:1.1-runtime-nanoserver ARG source WORKDIR /app +RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord EXPOSE 80 COPY ${source:-obj/Docker/publish} . ENTRYPOINT ["dotnet", "Identity.API.dll"] diff --git a/src/Services/Ordering/Ordering.API/Dockerfile.nanowin b/src/Services/Ordering/Ordering.API/Dockerfile.nanowin index 85b1f46b4..eac299fc7 100644 --- a/src/Services/Ordering/Ordering.API/Dockerfile.nanowin +++ b/src/Services/Ordering/Ordering.API/Dockerfile.nanowin @@ -1,6 +1,7 @@ FROM microsoft/dotnet:1.1-runtime-nanoserver ARG source WORKDIR /app +RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord EXPOSE 80 COPY ${source:-obj/Docker/publish} . ENTRYPOINT ["dotnet", "Ordering.API.dll"] diff --git a/src/Web/WebMVC/Dockerfile.nanowin b/src/Web/WebMVC/Dockerfile.nanowin index fe622e8de..302526330 100644 --- a/src/Web/WebMVC/Dockerfile.nanowin +++ b/src/Web/WebMVC/Dockerfile.nanowin @@ -1,6 +1,7 @@ FROM microsoft/dotnet:1.1-runtime-nanoserver ARG source WORKDIR /app +RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord EXPOSE 80 COPY ${source:-obj/Docker/publish} . ENTRYPOINT ["dotnet", "WebMVC.dll"] diff --git a/src/Web/WebSPA/Dockerfile.nanowin b/src/Web/WebSPA/Dockerfile.nanowin index 8c784ca6e..5f9a48294 100644 --- a/src/Web/WebSPA/Dockerfile.nanowin +++ b/src/Web/WebSPA/Dockerfile.nanowin @@ -1,6 +1,7 @@ FROM microsoft/dotnet:1.1-runtime-nanoserver ARG source WORKDIR /app +RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord EXPOSE 80 COPY ${source:-obj/Docker/publish} . ENTRYPOINT ["dotnet", "WebSPA.dll"] From 3ed136b00fd8ceafb6fb2a8d335a828115d91b5e Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Sat, 18 Mar 2017 11:44:18 -0700 Subject: [PATCH 36/80] Domain Event Handlers are now grouped by Domain Events, as any Domain Event could have 'n' handlers related. --- ...yerAndPaymentMethodVerifiedDomainEventHandler.cs} | 10 +++++----- ...ilToCustomerWhenOrderStartedDomainEventHandler.cs | 12 ++++++++++++ ...erAggregateWhenOrderStartedDomainEventHandler.cs} | 8 ++++---- .../Infrastructure/AutofacModules/MediatorModule.cs | 4 ++-- .../Infrastructure/Middlewares/FailingMiddleware.cs | 1 - .../Ordering/Ordering.API/Ordering.API.csproj | 4 ++++ .../AggregatesModel/BuyerAggregate/Buyer.cs | 4 ++-- .../Events/BuyerPaymentMethodVerifiedDomainEvent.cs | 4 ++-- 8 files changed, 31 insertions(+), 16 deletions(-) rename src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/{BuyerPaymentMethodVerifiedDomainEventHandler.cs => BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs} (70%) create mode 100644 src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs rename src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/{OrderStartedDomainEventHandler.cs => OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs} (74%) diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerPaymentMethodVerifiedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs similarity index 70% rename from src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerPaymentMethodVerifiedDomainEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs index 70360ce32..ffcd4fb74 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerPaymentMethodVerifiedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs @@ -5,13 +5,13 @@ using Ordering.Domain.Events; using System; using System.Threading.Tasks; -namespace Ordering.API.Application.DomainEventHandlers +namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVerified { - public class BuyerPaymentMethodVerifiedDomainEventHandler : IAsyncNotificationHandler + public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler : IAsyncNotificationHandler { private readonly IOrderRepository _orderRepository; private readonly ILoggerFactory _logger; - public BuyerPaymentMethodVerifiedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger) + public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -20,7 +20,7 @@ namespace Ordering.API.Application.DomainEventHandlers // Domain Logic comment: // When the Buyer and Buyer's payment method have been created or verified that they existed, // then we can update the original Order with the BuyerId and PaymentId (foreign keys) - public async Task Handle(BuyerPaymentMethodVerifiedDomainEvent buyerPaymentMethodVerifiedEvent) + public async Task Handle(BuyerAndPaymentMethodVerifiedDomainEvent buyerPaymentMethodVerifiedEvent) { var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId); orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id); @@ -29,7 +29,7 @@ namespace Ordering.API.Application.DomainEventHandlers await _orderRepository.UnitOfWork .SaveEntitiesAsync(); - _logger.CreateLogger(nameof(BuyerPaymentMethodVerifiedDomainEventHandler)) + _logger.CreateLogger(nameof(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler)) .LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }"); } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs new file mode 100644 index 000000000..1c6aaccd3 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent +{ + public class SendEmailToCustomerWhenOrderStartedDomainEventHandler + { + //TO DO + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs similarity index 74% rename from src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedDomainEventHandler.cs rename to src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs index 249976f0c..e59c66578 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs @@ -6,15 +6,15 @@ using Ordering.Domain.Events; using System; using System.Threading.Tasks; -namespace Ordering.API.Application.DomainEventHandlers +namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent { - public class OrderStartedDomainEventHandler : IAsyncNotificationHandler + public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler : IAsyncNotificationHandler { private readonly ILoggerFactory _logger; private readonly IBuyerRepository _buyerRepository; private readonly IIdentityService _identityService; - public OrderStartedDomainEventHandler(ILoggerFactory logger, IBuyerRepository buyerRepository, IIdentityService identityService) + public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler(ILoggerFactory logger, IBuyerRepository buyerRepository, IIdentityService identityService) { _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); @@ -46,7 +46,7 @@ namespace Ordering.API.Application.DomainEventHandlers await _buyerRepository.UnitOfWork .SaveEntitiesAsync(); - _logger.CreateLogger(nameof(OrderStartedDomainEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}."); + _logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}."); } } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index 0f0f8b098..353733df3 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -3,7 +3,7 @@ using Autofac.Core; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Decorators; -using Ordering.API.Application.DomainEventHandlers; +using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; using Ordering.Domain.Events; using System.Collections.Generic; using System.Linq; @@ -24,7 +24,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof .Select(i => new KeyedService("IAsyncRequestHandler", i))); builder - .RegisterAssemblyTypes(typeof(OrderStartedDomainEventHandler).GetTypeInfo().Assembly) + .RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly) .Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>))) .AsImplementedInterfaces(); diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddleware.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddleware.cs index 0206eaa38..2c82547e2 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddleware.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingMiddleware.cs @@ -41,7 +41,6 @@ namespace Ordering.API.Infrastructure.Middlewares private async Task ProcessConfigRequest(HttpContext context) { - int i = 0; var enable = context.Request.Query.Keys.Any(k => k == "enable"); var disable = context.Request.Query.Keys.Any(k => k == "disable"); diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index ce710c6a0..d0102c19f 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -58,6 +58,10 @@ + + + + Always diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs index 689122189..84040d8a1 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs @@ -33,7 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B if (existingPayment != null) { - AddDomainEvent(new BuyerPaymentMethodVerifiedDomainEvent(this, existingPayment, orderId)); + AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, existingPayment, orderId)); return existingPayment; } else @@ -41,7 +41,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration); _paymentMethods.Add(payment); - AddDomainEvent(new BuyerPaymentMethodVerifiedDomainEvent(this, payment, orderId)); + AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, payment, orderId)); return payment; } } diff --git a/src/Services/Ordering/Ordering.Domain/Events/BuyerPaymentMethodVerifiedDomainEvent.cs b/src/Services/Ordering/Ordering.Domain/Events/BuyerPaymentMethodVerifiedDomainEvent.cs index 3a3a00a0a..86c828f7c 100644 --- a/src/Services/Ordering/Ordering.Domain/Events/BuyerPaymentMethodVerifiedDomainEvent.cs +++ b/src/Services/Ordering/Ordering.Domain/Events/BuyerPaymentMethodVerifiedDomainEvent.cs @@ -6,14 +6,14 @@ using System.Text; namespace Ordering.Domain.Events { - public class BuyerPaymentMethodVerifiedDomainEvent + public class BuyerAndPaymentMethodVerifiedDomainEvent : IAsyncNotification { public Buyer Buyer { get; private set; } public PaymentMethod Payment { get; private set; } public int OrderId { get; private set; } - public BuyerPaymentMethodVerifiedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId) + public BuyerAndPaymentMethodVerifiedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId) { Buyer = buyer; Payment = payment; From aee1ac6a0673aec7cff98bc74214e25e40ff5450 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Sat, 18 Mar 2017 22:01:05 -0700 Subject: [PATCH 37/80] Domain Event Handlers refactored with some missing cases --- ...PaymentMethodVerifiedDomainEventHandler.cs | 3 +- ...tomerWhenOrderStartedDomainEventHandler.cs | 16 +++++++- ...egateWhenOrderStartedDomainEventHandler.cs | 38 ++++++++++--------- .../AutofacModules/MediatorModule.cs | 6 ++- .../BuyerAggregate/IBuyerRepository.cs | 2 +- .../Repositories/BuyerRepository.cs | 7 ++++ 6 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs index ffcd4fb74..fe7e11a1e 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs @@ -7,7 +7,8 @@ using System.Threading.Tasks; namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVerified { - public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler : IAsyncNotificationHandler + public class UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler + : IAsyncNotificationHandler { private readonly IOrderRepository _orderRepository; private readonly ILoggerFactory _logger; diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs index 1c6aaccd3..4da9439c2 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/SendEmailToCustomerWhenOrderStartedDomainEventHandler.cs @@ -3,10 +3,22 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MediatR; +using Ordering.Domain.Events; + namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent { public class SendEmailToCustomerWhenOrderStartedDomainEventHandler - { - //TO DO + //: IAsyncNotificationHandler + { + public SendEmailToCustomerWhenOrderStartedDomainEventHandler() + { + + } + + //public async Task Handle(OrderStartedDomainEvent orderNotification) + //{ + // //TBD - Send email logic + //} } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs index e59c66578..d243a82b6 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs @@ -8,7 +8,8 @@ using System.Threading.Tasks; namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent { - public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler : IAsyncNotificationHandler + public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler + : IAsyncNotificationHandler { private readonly ILoggerFactory _logger; private readonly IBuyerRepository _buyerRepository; @@ -21,33 +22,34 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task Handle(OrderStartedDomainEvent orderNotification) + public async Task Handle(OrderStartedDomainEvent orderStartedEvent) { - var cardTypeId = orderNotification.CardTypeId != 0 ? orderNotification.CardTypeId : 1; + var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1; - var buyerGuid = _identityService.GetUserIdentity(); - var buyer = await _buyerRepository.FindAsync(buyerGuid); + var userGuid = _identityService.GetUserIdentity(); - if (buyer == null) - { - buyer = new Buyer(buyerGuid); + var buyer = await _buyerRepository.FindAsync(userGuid); + bool buyerOriginallyExisted = (buyer == null) ? false : true; + + if (!buyerOriginallyExisted) + { + buyer = new Buyer(userGuid); } - var paymentMethod = buyer.VerifyOrAddPaymentMethod(cardTypeId, - $"Payment Method on {DateTime.UtcNow}", - orderNotification.CardNumber, - orderNotification.CardSecurityNumber, - orderNotification.CardHolderName, - orderNotification.CardExpiration, - orderNotification.Order.Id); + buyer.VerifyOrAddPaymentMethod(cardTypeId, + $"Payment Method on {DateTime.UtcNow}", + orderStartedEvent.CardNumber, + orderStartedEvent.CardSecurityNumber, + orderStartedEvent.CardHolderName, + orderStartedEvent.CardExpiration, + orderStartedEvent.Order.Id); - _buyerRepository.Add(buyer); + var buyerUpdated = buyerOriginallyExisted ? _buyerRepository.Update(buyer) : _buyerRepository.Add(buyer); await _buyerRepository.UnitOfWork .SaveEntitiesAsync(); - _logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}."); - + _logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {orderStartedEvent.Order.Id}."); } } } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index 353733df3..a6864e0ef 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -16,13 +16,15 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof protected override void Load(ContainerBuilder builder) { builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly) - .AsImplementedInterfaces(); + .AsImplementedInterfaces(); + // Register all the Command classes (they implement IAsyncRequestHandler) in assembly holding the Commands builder.RegisterAssemblyTypes(typeof(CreateOrderCommand).GetTypeInfo().Assembly) .As(o => o.GetInterfaces() .Where(i => i.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>))) .Select(i => new KeyedService("IAsyncRequestHandler", i))); - + + // Register all the Domain Event Handler classes (they implement IAsyncNotificationHandler<>) in assembly holding the Domain Events builder .RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly) .Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>))) diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/IBuyerRepository.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/IBuyerRepository.cs index 2fdaaf29f..9ab3fe1a5 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/IBuyerRepository.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/IBuyerRepository.cs @@ -9,7 +9,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B public interface IBuyerRepository : IRepository where T : IAggregateRoot { Buyer Add(Buyer buyer); - + Buyer Update(Buyer buyer); Task FindAsync(string BuyerIdentityGuid); } } diff --git a/src/Services/Ordering/Ordering.Infrastructure/Repositories/BuyerRepository.cs b/src/Services/Ordering/Ordering.Infrastructure/Repositories/BuyerRepository.cs index 693717dac..317581013 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/Repositories/BuyerRepository.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/Repositories/BuyerRepository.cs @@ -40,6 +40,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor } + public Buyer Update(Buyer buyer) + { + return _context.Buyers + .Update(buyer) + .Entity; + } + public async Task FindAsync(string identity) { var buyer = await _context.Buyers From ca029618e62a1af46f09fdd2412eb7ec9eaa0970 Mon Sep 17 00:00:00 2001 From: dsanz Date: Mon, 20 Mar 2017 10:03:55 +0100 Subject: [PATCH 38/80] Fix for issue #57 When re-building the solution sometimes the FunctionalTests.csproj fails trying to find the Catalog.API.dll --- eShopOnContainers-ServicesAndWebApps.sln | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 0c4d939a5..e6083e255 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -41,13 +41,22 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "test\Services\UnitTest\UnitTest.csproj", "{7796F5D8-31FC-45A4-B673-19DE5BA194CF}" ProjectSection(ProjectDependencies) = postProject {A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B} - {FEA0C318-FFED-4D39-8781-265718CA43DD} = {FEA0C318-FFED-4D39-8781-265718CA43DD} {F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.API", "src\Services\Identity\Identity.API\Identity.API.csproj", "{A579E108-5445-403D-A407-339AC4D1611B}" EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}" + ProjectSection(ProjectDependencies) = postProject + {A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B} + {5B810E3D-112E-4857-B197-F09D2FD41E27} = {5B810E3D-112E-4857-B197-F09D2FD41E27} + {F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68} + {F0333D8E-0B27-42B7-B2C6-78F3657624E2} = {F0333D8E-0B27-42B7-B2C6-78F3657624E2} + {42681D9D-750A-4DF7-BD9F-9292CFD5C253} = {42681D9D-750A-4DF7-BD9F-9292CFD5C253} + {2110CBB0-3B38-4EE4-A743-DF6968D80D90} = {2110CBB0-3B38-4EE4-A743-DF6968D80D90} + {CFE2FACB-4538-4B99-8A10-306F3882952D} = {CFE2FACB-4538-4B99-8A10-306F3882952D} + {231226CE-690B-4979-8870-9A79D80928E2} = {231226CE-690B-4979-8870-9A79D80928E2} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSPA", "src\Web\WebSPA\WebSPA.csproj", "{F16E3C6A-1C94-4EAB-BE91-099618060B68}" EndProject From 00491910a26755a846c254a9d3e41dcf6d581217 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 20 Mar 2017 14:18:20 -0400 Subject: [PATCH 39/80] C# 7 language feature updates Controllers/AccountController: Replace single line methods with expression bodied members. Includes constructor, C# 7 feature. Controllers/CatalogController: Remove unnecessary ToString() call. Replace single line methods with expression bodied members. Extensions/HttpClientExtensions: Replace single line methods with expression bodied members. Extensions/SessionExtensions: Replace single line methods with expression bodied members. Services/BasketService: Remove unnecessary ToString() calls. Add ?? to simplify conditional initialization Use TryGetValue and out variable initialization to simplify Quantity calculation Services/CatalogService: Use Value generic method instead of dynamic types. There is a lot of overhead for dynamic and it seems overkill here. Services/IdentityParser: Use the pattern matching is expression. Refactor the LINQ queries to enumerate the collection (and create an enumerator) only once. The previous code had 3 enumerations. Services/Utilities/HttpApiClient: Remove the 'async' modifier and 'await' statements from methods where the only asynchronous statement is the final statement of the method, and the task from the underlying method can be returned. Services/Utilities/HttpApiClientWrapper: Use expression bodied members where applicable. Remove the 'async' modifier and 'await' statements from methods where the only asynchronous statement is the final statement of the method, and the task from the underlying method can be returned. ViewComponents/Cart: Use expression bodied members where applicable. ViewComponents/CartList: Use expression bodied members where applicable. Remove the 'async' modifier and 'await' statements from methods where the only asynchronous statement is the final statement of the method, and the task from the underlying method can be returned. ViewModels/Annotations/CardExpiration: Use the out variable initializer to simplify the validation of the card expiration date. ViewModels/Basket: Use property initializer syntax instead of constructor body ViewModels/CatalogViewModels/IndexViewModel: Use expression bodied property to return the calculated 'Disabled' property ViewModels/Order: Use property initializer syntax. --- .../WebMVC/Controllers/AccountController.cs | 11 +- .../WebMVC/Controllers/CatalogController.cs | 11 +- .../WebMVC/Extensions/HttpClientExtensions.cs | 12 +- .../WebMVC/Extensions/SessionExtensions.cs | 4 +- src/Web/WebMVC/Services/BasketService.cs | 21 ++-- src/Web/WebMVC/Services/CatalogService.cs | 15 ++- src/Web/WebMVC/Services/IdentityParser.cs | 43 ++++--- .../Services/Utilities/HttpApiClient.cs | 36 +++--- .../Utilities/HttpApiClientWrapper.cs | 118 +++++++++--------- src/Web/WebMVC/ViewComponents/Cart.cs | 5 +- src/Web/WebMVC/ViewComponents/CartList.cs | 26 ++-- .../ViewModels/Annotations/CardExpiration.cs | 17 ++- src/Web/WebMVC/ViewModels/Basket.cs | 10 +- .../CartViewModels/IndexViewModel.cs | 2 +- src/Web/WebMVC/ViewModels/Order.cs | 9 +- 15 files changed, 173 insertions(+), 167 deletions(-) diff --git a/src/Web/WebMVC/Controllers/AccountController.cs b/src/Web/WebMVC/Controllers/AccountController.cs index ab2d75da8..0dee2ccfb 100644 --- a/src/Web/WebMVC/Controllers/AccountController.cs +++ b/src/Web/WebMVC/Controllers/AccountController.cs @@ -11,16 +11,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers public class AccountController : Controller { private readonly IIdentityParser _identityParser; - public AccountController(IIdentityParser identityParser) - { + public AccountController(IIdentityParser identityParser) => _identityParser = identityParser; - } - - public ActionResult Index() - { - return View(); - } + public ActionResult Index() => View(); + [Authorize] public IActionResult SignIn(string returnUrl) { diff --git a/src/Web/WebMVC/Controllers/CatalogController.cs b/src/Web/WebMVC/Controllers/CatalogController.cs index 4c649e507..83ea0b4a0 100644 --- a/src/Web/WebMVC/Controllers/CatalogController.cs +++ b/src/Web/WebMVC/Controllers/CatalogController.cs @@ -14,10 +14,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers { private ICatalogService _catalogSvc; - public CatalogController(ICatalogService catalogSvc) - { + public CatalogController(ICatalogService catalogSvc) => _catalogSvc = catalogSvc; - } public async Task Index(int? BrandFilterApplied, int? TypesFilterApplied, int? page) { @@ -35,7 +33,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers ActualPage = page ?? 0, ItemsPerPage = catalog.Data.Count, TotalItems = catalog.Count, - TotalPages = int.Parse(Math.Ceiling(((decimal)catalog.Count / itemsPage)).ToString()) + TotalPages = (int)Math.Ceiling(((decimal)catalog.Count / itemsPage)) } }; @@ -45,10 +43,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers return View(vm); } - public IActionResult Error() - { - return View(); - } + public IActionResult Error() => View(); } } diff --git a/src/Web/WebMVC/Extensions/HttpClientExtensions.cs b/src/Web/WebMVC/Extensions/HttpClientExtensions.cs index d1b83dc00..a1d6f215d 100644 --- a/src/Web/WebMVC/Extensions/HttpClientExtensions.cs +++ b/src/Web/WebMVC/Extensions/HttpClientExtensions.cs @@ -11,20 +11,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Extensions { public static class HttpClientExtensions { - public static void SetBasicAuthentication(this HttpClient client, string userName, string password) - { + public static void SetBasicAuthentication(this HttpClient client, string userName, string password) => client.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(userName, password); - } - public static void SetToken(this HttpClient client, string scheme, string token) - { + public static void SetToken(this HttpClient client, string scheme, string token) => client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, token); - } - public static void SetBearerToken(this HttpClient client, string token) - { + public static void SetBearerToken(this HttpClient client, string token) => client.SetToken(JwtConstants.TokenType, token); - } } public class BasicAuthenticationHeaderValue : AuthenticationHeaderValue diff --git a/src/Web/WebMVC/Extensions/SessionExtensions.cs b/src/Web/WebMVC/Extensions/SessionExtensions.cs index 4b095866d..79bd435f0 100644 --- a/src/Web/WebMVC/Extensions/SessionExtensions.cs +++ b/src/Web/WebMVC/Extensions/SessionExtensions.cs @@ -8,10 +8,8 @@ using System.Threading.Tasks; public static class SessionExtensions { - public static void SetObject(this ISession session, string key, object value) - { + public static void SetObject(this ISession session, string key, object value) => session.SetString(key, JsonConvert.SerializeObject(value)); - } public static T GetObject(this ISession session, string key) { diff --git a/src/Web/WebMVC/Services/BasketService.cs b/src/Web/WebMVC/Services/BasketService.cs index bcb138b87..0e6b13ab5 100644 --- a/src/Web/WebMVC/Services/BasketService.cs +++ b/src/Web/WebMVC/Services/BasketService.cs @@ -33,16 +33,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}"; + var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id}"; var dataString = await _apiClient.GetStringAsync(basketUrl); - var response = JsonConvert.DeserializeObject(dataString); - if (response == null) - { - response = new Basket() + // Use the ?? Null conditional operator to simplify the initialization of response + var response = JsonConvert.DeserializeObject(dataString) ?? + new Basket() { BuyerId = user.Id }; - } return response; } @@ -67,9 +65,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services basket.Items.ForEach(x => { - var quantity = quantities.Where(y => y.Key == x.Id).FirstOrDefault(); - if (quantities.Where(y => y.Key == x.Id).Count() > 0) - x.Quantity = quantity.Value; + // Simplify this logic by using the + // new out variable initializer. + if (quantities.TryGetValue(x.Id, out var quantity)) + { + x.Quantity = quantity; + } }); return basket; @@ -119,7 +120,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services var token = await context.Authentication.GetTokenAsync("access_token"); _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}"; + var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id}"; var response = await _apiClient.DeleteAsync(basketUrl); //CCE: response status code... diff --git a/src/Web/WebMVC/Services/CatalogService.cs b/src/Web/WebMVC/Services/CatalogService.cs index 83b139c1f..0e521c637 100644 --- a/src/Web/WebMVC/Services/CatalogService.cs +++ b/src/Web/WebMVC/Services/CatalogService.cs @@ -61,8 +61,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services JArray brands = JArray.Parse(dataString); foreach (JObject brand in brands.Children()) { - dynamic item = brand; - items.Add(new SelectListItem() { Value = item.id, Text = item.brand }); + items.Add(new SelectListItem() + { + Value = brand.Value("id"), + Text = brand.Value("brand") + }); } return items; @@ -79,10 +82,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services JArray brands = JArray.Parse(dataString); foreach (JObject brand in brands.Children()) { - dynamic item = brand; - items.Add(new SelectListItem() { Value = item.id, Text = item.type }); + items.Add(new SelectListItem() + { + Value = brand.Value("id"), + Text = brand.Value("type") + }); } - return items; } } diff --git a/src/Web/WebMVC/Services/IdentityParser.cs b/src/Web/WebMVC/Services/IdentityParser.cs index 619cfa517..70e82e53b 100644 --- a/src/Web/WebMVC/Services/IdentityParser.cs +++ b/src/Web/WebMVC/Services/IdentityParser.cs @@ -12,26 +12,31 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services { public ApplicationUser Parse(IPrincipal principal) { - var user = new ApplicationUser(); - var claims = (ClaimsPrincipal)principal; + // Pattern matching 'is' expression + // assigns "claims" if "principal" is a "ClaimsPrincipal" + if (principal is ClaimsPrincipal claims) + { + return new ApplicationUser + { - user.CardHolderName = (claims.Claims.Where(x => x.Type == "card_holder").Count() > 0) ? claims.Claims.First(x => x.Type == "card_holder").Value : ""; - user.CardNumber = (claims.Claims.Where(x => x.Type == "card_number").Count() > 0) ? claims.Claims.First(x => x.Type == "card_number").Value : ""; - user.Expiration = (claims.Claims.Where(x => x.Type == "card_expiration").Count() > 0) ? claims.Claims.First(x => x.Type == "card_expiration").Value : ""; - user.CardType = (claims.Claims.Where(x => x.Type == "missing").Count() > 0) ? int.Parse(claims.Claims.First(x => x.Type == "missing").Value) : 0; - user.City = (claims.Claims.Where(x => x.Type == "address_city").Count() > 0) ? claims.Claims.First(x => x.Type == "address_city").Value : ""; - user.Country = (claims.Claims.Where(x => x.Type == "address_country").Count() > 0) ? claims.Claims.First(x => x.Type == "address_country").Value : ""; - user.Email = (claims.Claims.Where(x => x.Type == "email").Count() > 0) ? claims.Claims.First(x => x.Type == "email").Value : ""; - user.Id = (claims.Claims.Where(x => x.Type == "sub").Count() > 0) ? claims.Claims.First(x => x.Type == "sub").Value : ""; - user.LastName = (claims.Claims.Where(x => x.Type == "last_name").Count() > 0) ? claims.Claims.First(x => x.Type == "last_name").Value : ""; - user.Name = (claims.Claims.Where(x => x.Type == "name").Count() > 0) ? claims.Claims.First(x => x.Type == "name").Value : ""; - user.PhoneNumber = (claims.Claims.Where(x => x.Type == "phone_number").Count() > 0) ? claims.Claims.First(x => x.Type == "phone_number").Value : ""; - user.SecurityNumber = (claims.Claims.Where(x => x.Type == "card_security_number").Count() > 0) ? claims.Claims.First(x => x.Type == "card_security_number").Value : ""; - user.State = (claims.Claims.Where(x => x.Type == "address_state").Count() > 0) ? claims.Claims.First(x => x.Type == "address_state").Value : ""; - user.Street = (claims.Claims.Where(x => x.Type == "address_street").Count() > 0) ? claims.Claims.First(x => x.Type == "address_street").Value : ""; - user.ZipCode = (claims.Claims.Where(x => x.Type == "address_zip_code").Count() > 0) ? claims.Claims.First(x => x.Type == "address_zip_code").Value : ""; - - return user; + CardHolderName = claims.Claims.FirstOrDefault(x => x.Type == "card_holder")?.Value ?? "", + CardNumber = claims.Claims.FirstOrDefault(x => x.Type == "card_number")?.Value ?? "", + Expiration = claims.Claims.FirstOrDefault(x => x.Type == "card_expiration")?.Value ?? "", + CardType = int.Parse(claims.Claims.FirstOrDefault(x => x.Type == "missing")?.Value ?? "0"), + City = claims.Claims.FirstOrDefault(x => x.Type == "address_city")?.Value ?? "", + Country = claims.Claims.FirstOrDefault(x => x.Type == "address_country")?.Value ?? "", + Email = claims.Claims.FirstOrDefault(x => x.Type == "email")?.Value ?? "", + Id = claims.Claims.FirstOrDefault(x => x.Type == "sub")?.Value ?? "", + LastName = claims.Claims.FirstOrDefault(x => x.Type == "last_name")?.Value ?? "", + Name = claims.Claims.FirstOrDefault(x => x.Type == "name")?.Value ?? "", + PhoneNumber = claims.Claims.FirstOrDefault(x => x.Type == "phone_number")?.Value ?? "", + SecurityNumber = claims.Claims.FirstOrDefault(x => x.Type == "card_security_number")?.Value ?? "", + State = claims.Claims.FirstOrDefault(x => x.Type == "address_state")?.Value ?? "", + Street = claims.Claims.FirstOrDefault(x => x.Type == "address_street")?.Value ?? "", + ZipCode = claims.Claims.FirstOrDefault(x => x.Type == "address_zip_code")?.Value ?? "" + }; + } + throw new ArgumentException(message: "The principal must be a ClaimsPrincipal", paramName: nameof(principal)); } } } diff --git a/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs b/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs index 8b142f393..87c885b82 100644 --- a/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs +++ b/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs @@ -17,29 +17,29 @@ namespace WebMVC.Services.Utilities _logger = new LoggerFactory().CreateLogger(nameof(HttpApiClientWrapper)); } - public async Task GetStringAsync(string uri) - { - return await HttpInvoker(async () => - await _client.GetStringAsync(uri)); - } + // Notice that these (and other methods below) are Task + // returning asynchronous methods. But, they do not + // have the 'async' modifier, and do not contain + // any 'await statements. In each of these methods, + // the only asynchronous call is the last (or only) + // statement of the method. In those instances, + // a Task returning method that does not use the + // async modifier is preferred. The compiler generates + // synchronous code for this method, but returns the + // task from the underlying asynchronous method. The + // generated code does not contain the state machine + // generated for asynchronous methods. + public Task GetStringAsync(string uri) => + _client.GetStringAsync(uri); - public async Task PostAsync(string uri, T item) + public Task PostAsync(string uri, T item) { var contentString = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"); - return await HttpInvoker(async () => - await _client.PostAsync(uri, contentString)); + return _client.PostAsync(uri, contentString); } - public async Task DeleteAsync(string uri) - { - return await HttpInvoker(async () => - await _client.DeleteAsync(uri)); - } - - private async Task HttpInvoker(Func> action) - { - return await action(); - } + public Task DeleteAsync(string uri) => + _client.DeleteAsync(uri); } } diff --git a/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs index dba0db9df..5dd26a0fb 100644 --- a/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs +++ b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs @@ -24,76 +24,74 @@ namespace WebMVC.Services.Utilities CreateRetryPolicy(), CreateCircuitBreakerPolicy() ); - } - - private Policy CreateCircuitBreakerPolicy() - { - return Policy - .Handle() - .CircuitBreakerAsync( - // number of exceptions before breaking circuit - 3, - // time circuit opened before retry - TimeSpan.FromMinutes(1), - (exception, duration) => { - // on circuit opened - _logger.LogTrace("Circuit breaker opened"); - }, - () => { - // on circuit closed - _logger.LogTrace("Circuit breaker reset"); - } - ); } - private Policy CreateRetryPolicy() - { - return Policy - .Handle() - .WaitAndRetryAsync( - // number of retries - 3, - // exponential backofff - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - // on retry - (exception, timeSpan, retryCount, context) => - { - _logger.LogTrace($"Retry {retryCount} " + - $"of {context.PolicyKey} " + - $"at {context.ExecutionKey}, " + - $"due to: {exception}."); - }); - } + private Policy CreateCircuitBreakerPolicy() => + Policy.Handle() + .CircuitBreakerAsync( + // number of exceptions before breaking circuit + 3, + // time circuit opened before retry + TimeSpan.FromMinutes(1), + (exception, duration) => + { + // on circuit opened + _logger.LogTrace("Circuit breaker opened"); + }, + () => + { + // on circuit closed + _logger.LogTrace("Circuit breaker reset"); + } + ); - public async Task GetStringAsync(string uri) - { - return await HttpInvoker(async () => - await _client.GetStringAsync(uri)); - } + private Policy CreateRetryPolicy() => + Policy.Handle() + .WaitAndRetryAsync( + // number of retries + 3, + // exponential backofff + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + // on retry + (exception, timeSpan, retryCount, context) => + { + _logger.LogTrace($"Retry {retryCount} " + + $"of {context.PolicyKey} " + + $"at {context.ExecutionKey}, " + + $"due to: {exception}."); + } + ); - public async Task PostAsync(string uri, T item) - { + // Notice that these (and other methods below) are Task + // returning asynchronous methods. But, they do not + // have the 'async' modifier, and do not contain + // any 'await statements. In each of these methods, + // the only asynchronous call is the last (or only) + // statement of the method. In those instances, + // a Task returning method that does not use the + // async modifier is preferred. The compiler generates + // synchronous code for this method, but returns the + // task from the underlying asynchronous method. The + // generated code does not contain the state machine + // generated for asynchronous methods. + public Task GetStringAsync(string uri) => + HttpInvoker(() => _client.GetStringAsync(uri)); + + public Task PostAsync(string uri, T item) => // a new StringContent must be created for each retry // as it is disposed after each call - return await HttpInvoker(async () => - await _client.PostAsync(uri, - new StringContent(JsonConvert.SerializeObject(item), - System.Text.Encoding.UTF8, "application/json"))); - } + HttpInvoker(() =>_client.PostAsync(uri, + new StringContent(JsonConvert.SerializeObject(item), + System.Text.Encoding.UTF8, "application/json"))); - public async Task DeleteAsync(string uri) - { - return await HttpInvoker(async () => - await _client.DeleteAsync(uri)); - } + public Task DeleteAsync(string uri) => + HttpInvoker(() => _client.DeleteAsync(uri)); - private async Task HttpInvoker(Func> action) - { + + private Task HttpInvoker(Func> action) => // Executes the action applying all // the policies defined in the wrapper - return await _policyWrapper - .ExecuteAsync(async () => await action()); - } + _policyWrapper.ExecuteAsync(() => action()); } } diff --git a/src/Web/WebMVC/ViewComponents/Cart.cs b/src/Web/WebMVC/ViewComponents/Cart.cs index 952c5ed3f..2db0e35b6 100644 --- a/src/Web/WebMVC/ViewComponents/Cart.cs +++ b/src/Web/WebMVC/ViewComponents/Cart.cs @@ -13,10 +13,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents { private readonly IBasketService _cartSvc; - public Cart(IBasketService cartSvc) - { - _cartSvc = cartSvc; - } + public Cart(IBasketService cartSvc) => _cartSvc = cartSvc; public async Task InvokeAsync(ApplicationUser user) { diff --git a/src/Web/WebMVC/ViewComponents/CartList.cs b/src/Web/WebMVC/ViewComponents/CartList.cs index fbb9894fe..055a79a25 100644 --- a/src/Web/WebMVC/ViewComponents/CartList.cs +++ b/src/Web/WebMVC/ViewComponents/CartList.cs @@ -12,19 +12,29 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents { private readonly IBasketService _cartSvc; - public CartList(IBasketService cartSvc) - { - _cartSvc = cartSvc; - } + public CartList(IBasketService cartSvc) => _cartSvc = cartSvc; public async Task InvokeAsync(ApplicationUser user) { var item = await GetItemsAsync(user); return View(item); } - private async Task GetItemsAsync(ApplicationUser user) - { - return await _cartSvc.GetBasket(user); - } + + // Notice that this method is a Task + // returning asynchronous method. But, it does not + // have the 'async' modifier, and does not contain + // any 'await statements. + // The only asynchronous call is the last (or only) + // statement of the method. In those instances, + // a Task returning method that does not use the + // async modifier is preferred. The compiler generates + // synchronous code for this method, but returns the + // task from the underlying asynchronous method. The + // generated code does not contain the state machine + // generated for asynchronous methods. + // Contrast that with the method above, which calls + // and awaits an asynchronous method, and then processes + // it further. + private Task GetItemsAsync(ApplicationUser user) => _cartSvc.GetBasket(user); } } diff --git a/src/Web/WebMVC/ViewModels/Annotations/CardExpiration.cs b/src/Web/WebMVC/ViewModels/Annotations/CardExpiration.cs index 5955ab30f..d60150a44 100644 --- a/src/Web/WebMVC/ViewModels/Annotations/CardExpiration.cs +++ b/src/Web/WebMVC/ViewModels/Annotations/CardExpiration.cs @@ -14,11 +14,20 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations if (value == null) return false; - var month = value.ToString().Split('/')[0]; - var year = $"20{value.ToString().Split('/')[1]}"; - DateTime d = new DateTime(int.Parse(year), int.Parse(month), 1); + var monthString = value.ToString().Split('/')[0]; + var yearString = $"20{value.ToString().Split('/')[1]}"; + // Use the 'out' variable initializer to simplify + // the logic of validating the expiration date + if ((int.TryParse(monthString, out var month)) && + (int.TryParse(yearString, out var year))) + { + DateTime d = new DateTime(year, month, 1); - return d > DateTime.UtcNow; + return d > DateTime.UtcNow; + } else + { + return false; + } } } } diff --git a/src/Web/WebMVC/ViewModels/Basket.cs b/src/Web/WebMVC/ViewModels/Basket.cs index aef34e4fd..b95a910a2 100644 --- a/src/Web/WebMVC/ViewModels/Basket.cs +++ b/src/Web/WebMVC/ViewModels/Basket.cs @@ -7,11 +7,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels { public class Basket { - public Basket() - { - Items = new List(); - } - public List Items { get; set; } + // Use property initializer syntax. + // While this is often more useful for read only + // auto implemented properties, it can simplify logic + // for read/write properties. + public List Items { get; set; } = new List(); public string BuyerId { get; set; } public decimal Total() diff --git a/src/Web/WebMVC/ViewModels/CartViewModels/IndexViewModel.cs b/src/Web/WebMVC/ViewModels/CartViewModels/IndexViewModel.cs index e46fdd4a3..9a8802542 100644 --- a/src/Web/WebMVC/ViewModels/CartViewModels/IndexViewModel.cs +++ b/src/Web/WebMVC/ViewModels/CartViewModels/IndexViewModel.cs @@ -9,6 +9,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.CartViewModels public class CartComponentViewModel { public int ItemsCount { get; set; } - public string Disabled { get { return (ItemsCount == 0) ? "is-disabled" : ""; } } + public string Disabled => (ItemsCount == 0) ? "is-disabled" : ""; } } diff --git a/src/Web/WebMVC/ViewModels/Order.cs b/src/Web/WebMVC/ViewModels/Order.cs index 8f3acc562..785b29119 100644 --- a/src/Web/WebMVC/ViewModels/Order.cs +++ b/src/Web/WebMVC/ViewModels/Order.cs @@ -11,10 +11,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels { public class Order { - public Order() { - OrderItems = new List(); - } - public string OrderNumber {get;set;} public DateTime Date {get;set;} @@ -53,7 +49,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels public string Buyer { get; set; } - public List OrderItems { get; } + // See the property initializer syntax below. This + // initializes the compiler generated field for this + // auto-implemented property. + public List OrderItems { get; } = new List(); [Required] public Guid RequestId { get; set; } From dd7de1be52973d57a24a635641ce02624d75320a Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Mon, 20 Mar 2017 12:18:30 -0700 Subject: [PATCH 40/80] Removed not used Common folder from the Solution --- eShopOnContainers-ServicesAndWebApps.sln | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index e6083e255..619dcf4c3 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.0 +VisualStudioVersion = 15.0.26228.4 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" EndProject @@ -64,8 +64,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTests", "test\Se EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{CFE2FACB-4538-4B99-8A10-306F3882952D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{47857844-D05A-4C37-BFB2-AF19B7EC418D}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{DB0EFB20-B024-4E5E-A75C-52143C131D25}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventBus", "EventBus", "{807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}" From 8c6d880f1850e1ed5ca7c51ce6815100f8041150 Mon Sep 17 00:00:00 2001 From: RamonTC Date: Tue, 21 Mar 2017 12:51:25 +0100 Subject: [PATCH 41/80] Remove async await issue comments Thanks for the review! --- src/Web/WebMVC/Services/Utilities/HttpApiClient.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs b/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs index 87c885b82..30a31ff23 100644 --- a/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs +++ b/src/Web/WebMVC/Services/Utilities/HttpApiClient.cs @@ -16,19 +16,7 @@ namespace WebMVC.Services.Utilities _client = new HttpClient(); _logger = new LoggerFactory().CreateLogger(nameof(HttpApiClientWrapper)); } - - // Notice that these (and other methods below) are Task - // returning asynchronous methods. But, they do not - // have the 'async' modifier, and do not contain - // any 'await statements. In each of these methods, - // the only asynchronous call is the last (or only) - // statement of the method. In those instances, - // a Task returning method that does not use the - // async modifier is preferred. The compiler generates - // synchronous code for this method, but returns the - // task from the underlying asynchronous method. The - // generated code does not contain the state machine - // generated for asynchronous methods. + public Task GetStringAsync(string uri) => _client.GetStringAsync(uri); From ec71fbb5ae4136818848eca2218d044b24d67bbc Mon Sep 17 00:00:00 2001 From: RamonTC Date: Tue, 21 Mar 2017 12:52:59 +0100 Subject: [PATCH 42/80] Remove async await issue comments --- src/Web/WebMVC/ViewComponents/CartList.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Web/WebMVC/ViewComponents/CartList.cs b/src/Web/WebMVC/ViewComponents/CartList.cs index 055a79a25..099cc8a3d 100644 --- a/src/Web/WebMVC/ViewComponents/CartList.cs +++ b/src/Web/WebMVC/ViewComponents/CartList.cs @@ -19,22 +19,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents var item = await GetItemsAsync(user); return View(item); } - - // Notice that this method is a Task - // returning asynchronous method. But, it does not - // have the 'async' modifier, and does not contain - // any 'await statements. - // The only asynchronous call is the last (or only) - // statement of the method. In those instances, - // a Task returning method that does not use the - // async modifier is preferred. The compiler generates - // synchronous code for this method, but returns the - // task from the underlying asynchronous method. The - // generated code does not contain the state machine - // generated for asynchronous methods. - // Contrast that with the method above, which calls - // and awaits an asynchronous method, and then processes - // it further. + private Task GetItemsAsync(ApplicationUser user) => _cartSvc.GetBasket(user); } } From 8a689c45f251e0c8d4498cddad01c5c4d418b42d Mon Sep 17 00:00:00 2001 From: dsanz Date: Tue, 21 Mar 2017 13:10:40 +0100 Subject: [PATCH 43/80] Add Delete and Create actions to the CatalogController. --- .../Controllers/CatalogController.cs | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 431928e0b..4add23b35 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -130,20 +130,21 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return Ok(items); } + [Route("edit")] [HttpPost] - public async Task Post([FromBody]CatalogItem value) + public async Task EditProduct([FromBody]CatalogItem product) { - var item = await _context.CatalogItems.SingleOrDefaultAsync(i => i.Id == value.Id); + var item = await _context.CatalogItems.SingleOrDefaultAsync(i => i.Id == product.Id); if (item == null) { return NotFound(); } - if (item.Price != value.Price) + if (item.Price != product.Price) { var oldPrice = item.Price; - item.Price = value.Price; + item.Price = product.Price; _context.CatalogItems.Update(item); var @event = new ProductPriceChangedIntegrationEvent(item.Id, item.Price, oldPrice); @@ -158,10 +159,47 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers eventLogEntry.State = EventStateEnum.Published; _context.IntegrationEventLog.Update(eventLogEntry); await _context.SaveChangesAsync(); - } + } return Ok(); - } + } + + [Route("create")] + [HttpPost] + public async Task CreateProduct([FromBody]CatalogItem product) + { + _context.CatalogItems.Add( + new CatalogItem + { + CatalogBrandId = product.CatalogBrandId, + CatalogTypeId = product.CatalogTypeId, + Description = product.Description, + Name = product.Name, + PictureUri = product.PictureUri, + Price = product.Price + }); + + await _context.SaveChangesAsync(); + + return Ok(); + } + + [Route("{id}")] + [HttpDelete] + public async Task DeleteProduct(int id) + { + var product = _context.CatalogItems.SingleOrDefault(x => x.Id == id); + + if (product == null) + { + return NotFound(); + } + + _context.CatalogItems.Remove(product); + await _context.SaveChangesAsync(); + + return Ok(); + } private List ComposePicUri(List items) { var baseUri = _settings.Value.ExternalCatalogBaseUrl; From 3a8e68abb474bc4efc7c6120e67e1a2f9ee7e7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 21 Mar 2017 18:15:28 +0100 Subject: [PATCH 44/80] Increased number of retries Increased number of fails before open circuit --- src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs index dba0db9df..9a27c57b4 100644 --- a/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs +++ b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs @@ -32,7 +32,7 @@ namespace WebMVC.Services.Utilities .Handle() .CircuitBreakerAsync( // number of exceptions before breaking circuit - 3, + 5, // time circuit opened before retry TimeSpan.FromMinutes(1), (exception, duration) => { @@ -52,7 +52,7 @@ namespace WebMVC.Services.Utilities .Handle() .WaitAndRetryAsync( // number of retries - 3, + 5, // exponential backofff retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // on retry From cb4da9864d2a38c1fdd35abba1600b75c5a54075 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Tue, 21 Mar 2017 12:55:33 -0700 Subject: [PATCH 45/80] Dispatching Domain Events right before DbContext SaveChanges() so side effects from additional Domain Event Handlers are included within the same transaction --- .../Ordering.Infrastructure/OrderingContext.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index de3f870da..d28b4e3f9 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -237,10 +237,18 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) { + // Dispatch Domain Events collection. + // Choices: + // A) Right BEFORE committing data (EF SaveChanges) into the DB will make a single transaction including + // side effects from the domain event handlers which are using the same DbContext with "InstancePerLifetimeScope" or "scoped" lifetime + // B) Right AFTER committing data (EF SaveChanges) into the DB. will make multiple transactions. + // You will need to handle eventual consistency and compensatory actions in case of failures. + await _mediator.DispatchDomainEventsAsync(this); + + + // After executing this line all the changes performed thought the DbContext will be commited var result = await base.SaveChangesAsync(); - // Dispatch the Domain Events collection right after saving/committing data into the database - await _mediator.DispatchDomainEventsAsync(this); return result; } } From bb39e5fd6eda370937f7c7ce05e94e544ab0dcfb Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Tue, 21 Mar 2017 12:58:07 -0700 Subject: [PATCH 46/80] Minor comment change --- .../Ordering/Ordering.Infrastructure/OrderingContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index d28b4e3f9..b74c7631d 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -241,8 +241,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure // Choices: // A) Right BEFORE committing data (EF SaveChanges) into the DB will make a single transaction including // side effects from the domain event handlers which are using the same DbContext with "InstancePerLifetimeScope" or "scoped" lifetime - // B) Right AFTER committing data (EF SaveChanges) into the DB. will make multiple transactions. - // You will need to handle eventual consistency and compensatory actions in case of failures. + // B) Right AFTER committing data (EF SaveChanges) into the DB will make multiple transactions. + // You will need to handle eventual consistency and compensatory actions in case of failures in any of the Handlers. await _mediator.DispatchDomainEventsAsync(this); From 696610ed3636fd66584ed4ad12d14cdf3dbe693c Mon Sep 17 00:00:00 2001 From: dsanz Date: Wed, 22 Mar 2017 09:57:08 +0100 Subject: [PATCH 47/80] Fix #129 Post_update_product_price_and_catalog_and_basket_list_modified test failing --- .../Catalog/Catalog.API/Controllers/CatalogController.cs | 3 +++ .../FunctionalTests/Services/Catalog/CatalogScenariosBase.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 4add23b35..b60ddc27c 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -130,6 +130,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return Ok(items); } + //POST api/v1/[controller]/edit [Route("edit")] [HttpPost] public async Task EditProduct([FromBody]CatalogItem product) @@ -164,6 +165,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return Ok(); } + //POST api/v1/[controller]/create [Route("create")] [HttpPost] public async Task CreateProduct([FromBody]CatalogItem product) @@ -184,6 +186,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return Ok(); } + //DELETE api/v1/[controller]/id [Route("{id}")] [HttpDelete] public async Task DeleteProduct(int id) diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs index 727e58a00..f7a847934 100644 --- a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs +++ b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs @@ -34,7 +34,7 @@ namespace FunctionalTests.Services.Catalog public static class Post { - public static string UpdateCatalogProduct = "api/v1/catalog"; + public static string UpdateCatalogProduct = "api/v1/catalog/edit"; } } } From 6f8512f434910d9fbbcc44300133e38b5c2940f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Wed, 22 Mar 2017 12:24:55 +0100 Subject: [PATCH 48/80] Increased circuit breaker threshold Fix lifescope issue with IHttpClient Adapt circuit breaker to trigger its counter for no success http response --- .../Utilities/HttpApiClientWrapper.cs | 36 +++++++++---------- src/Web/WebMVC/Startup.cs | 4 +-- .../modules/shared/services/data.service.ts | 2 ++ 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs index 5dd26a0fb..1e55305c5 100644 --- a/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs +++ b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs @@ -8,6 +8,12 @@ using System.Threading.Tasks; namespace WebMVC.Services.Utilities { + /// + /// HttpClient wrapper that integrates Retry and Circuit + /// breaker policies when calling to Api services. + /// Currently is ONLY implemented for the ASP MVC + /// and Xamarin App + /// public class HttpApiClientWrapper : IHttpClient { private HttpClient _client; @@ -49,11 +55,11 @@ namespace WebMVC.Services.Utilities Policy.Handle() .WaitAndRetryAsync( // number of retries - 3, + 3, // exponential backofff retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // on retry - (exception, timeSpan, retryCount, context) => + (exception, timeSpan, retryCount, context) => { _logger.LogTrace($"Retry {retryCount} " + $"of {context.PolicyKey} " + @@ -62,27 +68,21 @@ namespace WebMVC.Services.Utilities } ); - // Notice that these (and other methods below) are Task - // returning asynchronous methods. But, they do not - // have the 'async' modifier, and do not contain - // any 'await statements. In each of these methods, - // the only asynchronous call is the last (or only) - // statement of the method. In those instances, - // a Task returning method that does not use the - // async modifier is preferred. The compiler generates - // synchronous code for this method, but returns the - // task from the underlying asynchronous method. The - // generated code does not contain the state machine - // generated for asynchronous methods. public Task GetStringAsync(string uri) => - HttpInvoker(() => _client.GetStringAsync(uri)); + HttpInvoker(() => + _client.GetStringAsync(uri)); public Task PostAsync(string uri, T item) => // a new StringContent must be created for each retry // as it is disposed after each call - HttpInvoker(() =>_client.PostAsync(uri, - new StringContent(JsonConvert.SerializeObject(item), - System.Text.Encoding.UTF8, "application/json"))); + HttpInvoker(() => + { + var response = _client.PostAsync(uri, new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json")); + // raise exception if not success response + // needed for circuit breaker to track fails + response.Result.EnsureSuccessStatusCode(); + return response; + }); public Task DeleteAsync(string uri) => HttpInvoker(() => _client.DeleteAsync(uri)); diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index 7888ae253..ee2412bee 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -54,11 +54,11 @@ namespace Microsoft.eShopOnContainers.WebMVC if(Configuration.GetValue("ActivateCircuitBreaker") == bool.TrueString) { - services.AddSingleton(); + services.AddTransient(); } else { - services.AddSingleton(); + services.AddTransient(); } } diff --git a/src/Web/WebSPA/Client/modules/shared/services/data.service.ts b/src/Web/WebSPA/Client/modules/shared/services/data.service.ts index 785728943..3b121f4e7 100644 --- a/src/Web/WebSPA/Client/modules/shared/services/data.service.ts +++ b/src/Web/WebSPA/Client/modules/shared/services/data.service.ts @@ -11,6 +11,8 @@ import 'rxjs/add/operator/catch'; import { SecurityService } from './security.service'; import { Guid } from '../../../guid'; +// Implementing a Retry-Circuit breaker policy +// is pending to do for the SPA app @Injectable() export class DataService { constructor(private http: Http, private securityService: SecurityService) { } From 449ee3f7a310349cee74856a38b8f6d34482b0bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Wed, 22 Mar 2017 12:40:20 +0100 Subject: [PATCH 49/80] Increase Retries in HttpClientWrapper --- src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs index 1e55305c5..06172eb53 100644 --- a/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs +++ b/src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs @@ -36,7 +36,7 @@ namespace WebMVC.Services.Utilities Policy.Handle() .CircuitBreakerAsync( // number of exceptions before breaking circuit - 3, + 5, // time circuit opened before retry TimeSpan.FromMinutes(1), (exception, duration) => @@ -55,7 +55,7 @@ namespace WebMVC.Services.Utilities Policy.Handle() .WaitAndRetryAsync( // number of retries - 3, + 5, // exponential backofff retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // on retry From 2e674ad5321fef5bea8ab2a5bef6f31b39d5e44e Mon Sep 17 00:00:00 2001 From: Eduard Tomas Date: Wed, 22 Mar 2017 14:23:25 +0100 Subject: [PATCH 50/80] Replaced Tuple by C#7 multiple return statement Replaced private set only used in ctor by readonly prop --- .../EventBus/Events/IntegrationEvent.cs | 2 +- .../EventBusRabbitMQ/EventBusRabbitMQ.cs | 78 ++++++++++--------- .../EventBusRabbitMQ/EventBusRabbitMQ.csproj | 1 + 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs index 11185764e..e1802704e 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs @@ -11,6 +11,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events Id = Guid.NewGuid(); } - public Guid Id { get; private set; } + public Guid Id { get; } } } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index e31c43396..b850c4eef 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -21,7 +21,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ private readonly Dictionary> _handlers; private readonly List _eventTypes; - private Tuple _connection; + private IModel _model; + private IConnection _connection; private string _queueName; @@ -86,15 +87,15 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ _handlers.Remove(eventName); var eventType = _eventTypes.Single(e => e.Name == eventName); _eventTypes.Remove(eventType); - _connection.Item1.QueueUnbind(queue: _queueName, + _model.QueueUnbind(queue: _queueName, exchange: _brokerName, routingKey: eventName); if (_handlers.Keys.Count == 0) { _queueName = string.Empty; - _connection.Item1.Dispose(); - _connection.Item2.Dispose(); + _model.Dispose(); + _connection.Dispose(); } } @@ -103,50 +104,53 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ public void Dispose() { - if (_connection != null) - { - _handlers.Clear(); - _connection.Item1.Dispose(); - _connection.Item2.Dispose(); - } + _handlers.Clear(); + _model?.Dispose(); + _connection?.Dispose(); } private IModel GetChannel() { - if (_connection != null) + if (_model != null) { - return _connection.Item1; + return _model; } else { - var factory = new ConnectionFactory() { HostName = _connectionString }; - var connection = factory.CreateConnection(); - var channel = connection.CreateModel(); - - channel.ExchangeDeclare(exchange: _brokerName, - type: "direct"); - if (string.IsNullOrEmpty(_queueName)) - { - _queueName = channel.QueueDeclare().QueueName; - } - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += async (model, ea) => - { - var eventName = ea.RoutingKey; - var message = Encoding.UTF8.GetString(ea.Body); - - await ProcessEvent(eventName, message); - }; - channel.BasicConsume(queue: _queueName, - noAck: true, - consumer: consumer); - _connection = new Tuple(channel, connection); - - return _connection.Item1; + ((_model, _connection) = CreateConnection(); + return _model; } } + + private (IModel model, IConnection connection) CreateConnection() + { + var factory = new ConnectionFactory() { HostName = _connectionString }; + var con = factory.CreateConnection(); + var channel = con.CreateModel(); + + channel.ExchangeDeclare(exchange: _brokerName, + type: "direct"); + if (string.IsNullOrEmpty(_queueName)) + { + _queueName = channel.QueueDeclare().QueueName; + } + + var consumer = new EventingBasicConsumer(channel); + consumer.Received += async (model, ea) => + { + var eventName = ea.RoutingKey; + var message = Encoding.UTF8.GetString(ea.Body); + + await ProcessEvent(eventName, message); + }; + channel.BasicConsume(queue: _queueName, + noAck: true, + consumer: consumer); + + return (channel, con); + } + private async Task ProcessEvent(string eventName, string message) { if (_handlers.ContainsKey(eventName)) diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj index 6e399a627..cf36a2222 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj @@ -9,6 +9,7 @@ + From 20d2e32719dfada74b8dd54de5710edb027ccc11 Mon Sep 17 00:00:00 2001 From: Eduard Tomas Date: Wed, 22 Mar 2017 14:40:00 +0100 Subject: [PATCH 51/80] Ups... too many parens xD --- .../EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index b850c4eef..3388875ab 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -117,7 +117,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } else { - ((_model, _connection) = CreateConnection(); + (_model, _connection) = CreateConnection(); return _model; } } From 7a63490558e9a7f40229d746db309839c3d5e21d Mon Sep 17 00:00:00 2001 From: dsanz Date: Wed, 22 Mar 2017 16:10:46 +0100 Subject: [PATCH 52/80] Add the sql implementation for the storage of Integration events. --- eShopOnContainers-ServicesAndWebApps.sln | 53 +++++++++- .../EventStateEnum.cs | 2 +- .../IntegrationEventLogContext.cs | 48 +++++++++ .../IntegrationEventLogEF.csproj | 27 +++++ .../IntegrationEventLogEntry.cs | 3 +- .../Catalog/Catalog.API/Catalog.API.csproj | 1 + .../Controllers/CatalogController.cs | 50 +++++----- .../Infrastructure/CatalogContext.cs | 42 ++++---- .../20161103152832_Initial.Designer.cs | 0 .../20161103152832_Initial.cs | 0 ...0161103153420_UpdateTableNames.Designer.cs | 0 .../20161103153420_UpdateTableNames.cs | 0 .../20170314083211_AddEventTable.Designer.cs | 0 .../20170314083211_AddEventTable.cs | 0 ...factoringToIntegrationEventLog.Designer.cs | 0 ...012921_RefactoringToIntegrationEventLog.cs | 0 ..._RefactoringEventBusNamespaces.Designer.cs | 0 ...316120022_RefactoringEventBusNamespaces.cs | 0 ...244_RemoveIntegrationEventLogs.Designer.cs | 99 +++++++++++++++++++ ...170322124244_RemoveIntegrationEventLogs.cs | 34 +++++++ .../CatalogContextModelSnapshot.cs | 23 ----- ...145434_IntegrationEventInitial.Designer.cs | 43 ++++++++ .../20170322145434_IntegrationEventInitial.cs | 34 +++++++ ...IntegrationEventLogContextModelSnapshot.cs | 42 ++++++++ src/Services/Catalog/Catalog.API/Startup.cs | 15 ++- 25 files changed, 443 insertions(+), 73 deletions(-) rename src/BuildingBlocks/EventBus/{EventBus/Events/IntegrationEventLog => IntegrationEventLogEF}/EventStateEnum.cs (68%) create mode 100644 src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogContext.cs create mode 100644 src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj rename src/BuildingBlocks/EventBus/{EventBus/Events/IntegrationEventLog => IntegrationEventLogEF}/IntegrationEventLogEntry.cs (85%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20161103152832_Initial.Designer.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20161103152832_Initial.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20161103153420_UpdateTableNames.Designer.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20161103153420_UpdateTableNames.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20170314083211_AddEventTable.Designer.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20170314083211_AddEventTable.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20170316012921_RefactoringToIntegrationEventLog.Designer.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20170316012921_RefactoringToIntegrationEventLog.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20170316120022_RefactoringEventBusNamespaces.Designer.cs (100%) rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/20170316120022_RefactoringEventBusNamespaces.cs (100%) create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.Designer.cs create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.cs rename src/Services/Catalog/Catalog.API/Infrastructure/{Migrations => CatalogMigrations}/CatalogContextModelSnapshot.cs (84%) create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.Designer.cs create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.cs create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 619dcf4c3..e497a67e5 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26228.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" EndProject @@ -72,6 +72,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus", "src\BuildingBlo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBusRabbitMQ", "src\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj", "{8088F3FC-6787-45FA-A924-816EC81CBFAC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationEventLogEF", "src\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj", "{9EE28E45-1533-472B-8267-56C48855BA0E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -662,6 +664,54 @@ Global {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x64.Build.0 = Release|Any CPU {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x86.ActiveCfg = Release|Any CPU {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x86.Build.0 = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|ARM.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|iPhone.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|x64.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|x64.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|x86.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.AppStore|x86.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|ARM.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|iPhone.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|x64.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|x64.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|x86.ActiveCfg = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Debug|x86.Build.0 = Debug|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|Any CPU.Build.0 = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|ARM.ActiveCfg = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|ARM.Build.0 = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|iPhone.ActiveCfg = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|iPhone.Build.0 = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x64.ActiveCfg = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x64.Build.0 = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.ActiveCfg = Release|Any CPU + {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -689,5 +739,6 @@ Global {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {0044B293-1DCC-4224-B948-00CF6DC7F510} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} {8088F3FC-6787-45FA-A924-816EC81CBFAC} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} + {9EE28E45-1533-472B-8267-56C48855BA0E} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} EndGlobalSection EndGlobal diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/EventStateEnum.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs similarity index 68% rename from src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/EventStateEnum.cs rename to src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs index 41ddc119c..3efb78e74 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/EventStateEnum.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog +namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF { public enum EventStateEnum { diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogContext.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogContext.cs new file mode 100644 index 000000000..de8754e03 --- /dev/null +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogContext.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF +{ + public class IntegrationEventLogContext : DbContext + { + public IntegrationEventLogContext(DbContextOptions options) : base(options) + { + } + + public DbSet IntegrationEventLogs { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity(ConfigureIntegrationEventLogEntry); + } + + void ConfigureIntegrationEventLogEntry(EntityTypeBuilder builder) + { + builder.ToTable("IntegrationEventLog"); + + builder.HasKey(e => e.EventId); + + builder.Property(e => e.EventId) + .IsRequired(); + + builder.Property(e => e.Content) + .IsRequired(); + + builder.Property(e => e.CreationTime) + .IsRequired(); + + builder.Property(e => e.State) + .IsRequired(); + + builder.Property(e => e.TimesSent) + .IsRequired(); + + builder.Property(e => e.EventTypeName) + .IsRequired(); + + } + } +} diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj new file mode 100644 index 000000000..039b26ae2 --- /dev/null +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj @@ -0,0 +1,27 @@ + + + + netcoreapp1.1 + 1.1.0 + Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs similarity index 85% rename from src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/IntegrationEventLogEntry.cs rename to src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs index a74c600e4..2b31e6681 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEventLog/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Text; using Newtonsoft.Json; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog +namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF { public class IntegrationEventLogEntry { diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 5cef06749..f4567b88a 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -58,6 +58,7 @@ + diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index b60ddc27c..2feaa423d 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Catalog.API.Model; @@ -16,28 +16,31 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [Route("api/v1/[controller]")] public class CatalogController : ControllerBase { - private readonly CatalogContext _context; + private readonly CatalogContext _catalogContext; + private readonly IntegrationEventLogContext _integrationEventLogContext; private readonly IOptionsSnapshot _settings; private readonly IEventBus _eventBus; - public CatalogController(CatalogContext context, IOptionsSnapshot settings, IEventBus eventBus) + public CatalogController(CatalogContext catalogContext, IntegrationEventLogContext integrationEventLogContext, IOptionsSnapshot settings, IEventBus eventBus) { - _context = context; + _catalogContext = catalogContext; + _integrationEventLogContext = integrationEventLogContext; _settings = settings; _eventBus = eventBus; - ((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + ((DbContext)catalogContext).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } // GET api/v1/[controller]/items[?pageSize=3&pageIndex=10] [HttpGet] [Route("[action]")] public async Task Items([FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) + { - var totalItems = await _context.CatalogItems + var totalItems = await _catalogContext.CatalogItems .LongCountAsync(); - var itemsOnPage = await _context.CatalogItems + var itemsOnPage = await _catalogContext.CatalogItems .OrderBy(c=>c.Name) .Skip(pageSize * pageIndex) .Take(pageSize) @@ -57,11 +60,11 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers public async Task Items(string name, [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) { - var totalItems = await _context.CatalogItems + var totalItems = await _catalogContext.CatalogItems .Where(c => c.Name.StartsWith(name)) .LongCountAsync(); - var itemsOnPage = await _context.CatalogItems + var itemsOnPage = await _catalogContext.CatalogItems .Where(c => c.Name.StartsWith(name)) .Skip(pageSize * pageIndex) .Take(pageSize) @@ -80,7 +83,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [Route("[action]/type/{catalogTypeId}/brand/{catalogBrandId}")] public async Task Items(int? catalogTypeId, int? catalogBrandId, [FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0) { - var root = (IQueryable)_context.CatalogItems; + var root = (IQueryable)_catalogContext.CatalogItems; if (catalogTypeId.HasValue) { @@ -113,7 +116,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [Route("[action]")] public async Task CatalogTypes() { - var items = await _context.CatalogTypes + var items = await _catalogContext.CatalogTypes .ToListAsync(); return Ok(items); @@ -124,7 +127,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [Route("[action]")] public async Task CatalogBrands() { - var items = await _context.CatalogBrands + var items = await _catalogContext.CatalogBrands .ToListAsync(); return Ok(items); @@ -135,7 +138,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [HttpPost] public async Task EditProduct([FromBody]CatalogItem product) { - var item = await _context.CatalogItems.SingleOrDefaultAsync(i => i.Id == product.Id); + var item = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == product.Id); if (item == null) { @@ -146,20 +149,21 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers { var oldPrice = item.Price; item.Price = product.Price; - _context.CatalogItems.Update(item); + _catalogContext.CatalogItems.Update(item); var @event = new ProductPriceChangedIntegrationEvent(item.Id, item.Price, oldPrice); var eventLogEntry = new IntegrationEventLogEntry(@event); - _context.IntegrationEventLog.Add(eventLogEntry); + _integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry); - await _context.SaveChangesAsync(); + await _integrationEventLogContext.SaveChangesAsync(); + await _catalogContext.SaveChangesAsync(); _eventBus.Publish(@event); eventLogEntry.TimesSent++; eventLogEntry.State = EventStateEnum.Published; - _context.IntegrationEventLog.Update(eventLogEntry); - await _context.SaveChangesAsync(); + _integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); + await _integrationEventLogContext.SaveChangesAsync(); } return Ok(); @@ -170,7 +174,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [HttpPost] public async Task CreateProduct([FromBody]CatalogItem product) { - _context.CatalogItems.Add( + _catalogContext.CatalogItems.Add( new CatalogItem { CatalogBrandId = product.CatalogBrandId, @@ -181,7 +185,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers Price = product.Price }); - await _context.SaveChangesAsync(); + await _catalogContext.SaveChangesAsync(); return Ok(); } @@ -191,15 +195,15 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers [HttpDelete] public async Task DeleteProduct(int id) { - var product = _context.CatalogItems.SingleOrDefault(x => x.Id == id); + var product = _catalogContext.CatalogItems.SingleOrDefault(x => x.Id == id); if (product == null) { return NotFound(); } - _context.CatalogItems.Remove(product); - await _context.SaveChangesAsync(); + _catalogContext.CatalogItems.Remove(product); + await _catalogContext.SaveChangesAsync(); return Ok(); } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs index 846b1dbb5..b8ae183c8 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs @@ -3,24 +3,24 @@ using EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore; using Model; - using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLog; + using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; public class CatalogContext : DbContext { - public CatalogContext(DbContextOptions options) : base(options) + public CatalogContext(DbContextOptions options) : base(options) { } public DbSet CatalogItems { get; set; } public DbSet CatalogBrands { get; set; } public DbSet CatalogTypes { get; set; } - public DbSet IntegrationEventLog { get; set; } + //public DbSet IntegrationEventLog { get; set; } protected override void OnModelCreating(ModelBuilder builder) { builder.Entity(ConfigureCatalogBrand); builder.Entity(ConfigureCatalogType); builder.Entity(ConfigureCatalogItem); - builder.Entity(ConfigureIntegrationEventLogEntry); + //builder.Entity(ConfigureIntegrationEventLogEntry); } void ConfigureCatalogItem(EntityTypeBuilder builder) @@ -80,30 +80,30 @@ .HasMaxLength(100); } - void ConfigureIntegrationEventLogEntry(EntityTypeBuilder builder) - { - builder.ToTable("IntegrationEventLog"); + //void ConfigureIntegrationEventLogEntry(EntityTypeBuilder builder) + //{ + // builder.ToTable("IntegrationEventLog"); - builder.HasKey(e => e.EventId); + // builder.HasKey(e => e.EventId); - builder.Property(e => e.EventId) - .IsRequired(); + // builder.Property(e => e.EventId) + // .IsRequired(); - builder.Property(e => e.Content) - .IsRequired(); + // builder.Property(e => e.Content) + // .IsRequired(); - builder.Property(e => e.CreationTime) - .IsRequired(); + // builder.Property(e => e.CreationTime) + // .IsRequired(); - builder.Property(e => e.State) - .IsRequired(); + // builder.Property(e => e.State) + // .IsRequired(); - builder.Property(e => e.TimesSent) - .IsRequired(); + // builder.Property(e => e.TimesSent) + // .IsRequired(); - builder.Property(e => e.EventTypeName) - .IsRequired(); + // builder.Property(e => e.EventTypeName) + // .IsRequired(); - } + //} } } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20161103152832_Initial.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20161103152832_Initial.Designer.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20161103152832_Initial.Designer.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20161103152832_Initial.Designer.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20161103152832_Initial.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20161103152832_Initial.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20161103152832_Initial.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20161103152832_Initial.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20161103153420_UpdateTableNames.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20161103153420_UpdateTableNames.Designer.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20161103153420_UpdateTableNames.Designer.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20161103153420_UpdateTableNames.Designer.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20161103153420_UpdateTableNames.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20161103153420_UpdateTableNames.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20161103153420_UpdateTableNames.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20161103153420_UpdateTableNames.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170314083211_AddEventTable.Designer.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170314083211_AddEventTable.Designer.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170314083211_AddEventTable.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170314083211_AddEventTable.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316012921_RefactoringToIntegrationEventLog.Designer.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316012921_RefactoringToIntegrationEventLog.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316012921_RefactoringToIntegrationEventLog.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316012921_RefactoringToIntegrationEventLog.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316120022_RefactoringEventBusNamespaces.Designer.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.Designer.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316120022_RefactoringEventBusNamespaces.Designer.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316120022_RefactoringEventBusNamespaces.cs similarity index 100% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170316120022_RefactoringEventBusNamespaces.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170316120022_RefactoringEventBusNamespaces.cs diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.Designer.cs new file mode 100644 index 000000000..aea1086f6 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.Designer.cs @@ -0,0 +1,99 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; + +namespace Catalog.API.Infrastructure.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20170322124244_RemoveIntegrationEventLogs")] + partial class RemoveIntegrationEventLogs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_brand_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogBrand"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("CatalogBrandId"); + + b.Property("CatalogTypeId"); + + b.Property("Description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50); + + b.Property("PictureUri"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_type_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.cs new file mode 100644 index 000000000..88e0c76db --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170322124244_RemoveIntegrationEventLogs.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Catalog.API.Infrastructure.Migrations +{ + public partial class RemoveIntegrationEventLogs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "IntegrationEventLog"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "IntegrationEventLog", + columns: table => new + { + EventId = table.Column(nullable: false), + Content = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + EventTypeName = table.Column(nullable: false), + State = table.Column(nullable: false), + TimesSent = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IntegrationEventLog", x => x.EventId); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs similarity index 84% rename from src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs rename to src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs index 77c820212..ba1151672 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; namespace Catalog.API.Infrastructure.Migrations { @@ -20,28 +19,6 @@ namespace Catalog.API.Infrastructure.Migrations .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events.IntegrationEventLogEntry", b => - { - b.Property("EventId") - .ValueGeneratedOnAdd(); - - b.Property("Content") - .IsRequired(); - - b.Property("CreationTime"); - - b.Property("EventTypeName") - .IsRequired(); - - b.Property("State"); - - b.Property("TimesSent"); - - b.HasKey("EventId"); - - b.ToTable("IntegrationEventLog"); - }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => { b.Property("Id") diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.Designer.cs new file mode 100644 index 000000000..a3cdeb53f --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.Designer.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; + +namespace Catalog.API.Migrations +{ + [DbContext(typeof(IntegrationEventLogContext))] + [Migration("20170322145434_IntegrationEventInitial")] + partial class IntegrationEventInitial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired(); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEventLog"); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.cs b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.cs new file mode 100644 index 000000000..fff1ad04c --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/20170322145434_IntegrationEventInitial.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Catalog.API.Migrations +{ + public partial class IntegrationEventInitial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "IntegrationEventLog", + columns: table => new + { + EventId = table.Column(nullable: false), + Content = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + EventTypeName = table.Column(nullable: false), + State = table.Column(nullable: false), + TimesSent = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IntegrationEventLog", x => x.EventId); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "IntegrationEventLog"); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs new file mode 100644 index 000000000..ab2414bb5 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; + +namespace Catalog.API.Migrations +{ + [DbContext(typeof(IntegrationEventLogContext))] + partial class IntegrationEventLogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired(); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEventLog"); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 818855d69..05c6409fe 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; + using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -45,6 +46,12 @@ //Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval }); + services.AddDbContext(c => + { + c.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API")); + c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); + }); + services.Configure(Configuration); // Add framework services. @@ -77,7 +84,7 @@ services.AddMvc(); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IntegrationEventLogContext integrationEventLogContext) { //Configure logs @@ -98,9 +105,11 @@ //Seed Data CatalogContextSeed.SeedAsync(app, loggerFactory) - .Wait(); + .Wait(); + + // TODO: move this creation to a db initializer + integrationEventLogContext.Database.Migrate(); - } } } From 6e4d9461de69adfee709f210218d053033a5a203 Mon Sep 17 00:00:00 2001 From: dsanz Date: Thu, 23 Mar 2017 13:24:17 +0100 Subject: [PATCH 53/80] Add shared scope transaction between updating catalog product priceand store ProductPriceChangedIntegrationEvent. Added service to encapsulate logic for storage of integration event logs. --- .../IntegrationEventLogEntry.cs | 1 + .../Controllers/CatalogController.cs | 38 +++++++------- .../IIntegrationEventLogService.cs | 14 +++++ .../IntegrationEventLogService.cs | 51 +++++++++++++++++++ src/Services/Catalog/Catalog.API/Startup.cs | 15 ++++-- 5 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs create mode 100644 src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs index 2b31e6681..0b68e56a0 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs @@ -8,6 +8,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF { public class IntegrationEventLogEntry { + private IntegrationEventLogEntry() { } public IntegrationEventLogEntry(IntegrationEvent @event) { EventId = @event.Id; diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 2feaa423d..bbc30a772 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -1,5 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +using Catalog.API.IntegrationEvents; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; @@ -17,18 +19,18 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers public class CatalogController : ControllerBase { private readonly CatalogContext _catalogContext; - private readonly IntegrationEventLogContext _integrationEventLogContext; private readonly IOptionsSnapshot _settings; private readonly IEventBus _eventBus; + private readonly IIntegrationEventLogService _integrationEventLogService; - public CatalogController(CatalogContext catalogContext, IntegrationEventLogContext integrationEventLogContext, IOptionsSnapshot settings, IEventBus eventBus) + public CatalogController(CatalogContext Context, IOptionsSnapshot settings, IEventBus eventBus, IIntegrationEventLogService integrationEventLogService) { - _catalogContext = catalogContext; - _integrationEventLogContext = integrationEventLogContext; + _catalogContext = Context; _settings = settings; _eventBus = eventBus; + _integrationEventLogService = integrationEventLogService; - ((DbContext)catalogContext).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + ((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } // GET api/v1/[controller]/items[?pageSize=3&pageIndex=10] @@ -149,21 +151,21 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers { var oldPrice = item.Price; item.Price = product.Price; - _catalogContext.CatalogItems.Update(item); - var @event = new ProductPriceChangedIntegrationEvent(item.Id, item.Price, oldPrice); - var eventLogEntry = new IntegrationEventLogEntry(@event); - _integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry); - await _integrationEventLogContext.SaveChangesAsync(); - await _catalogContext.SaveChangesAsync(); - + using (var transaction = _catalogContext.Database.BeginTransaction()) + { + _catalogContext.CatalogItems.Update(item); + await _catalogContext.SaveChangesAsync(); + + await _integrationEventLogService.SaveEventAsync(@event); + + transaction.Commit(); + } + _eventBus.Publish(@event); - - eventLogEntry.TimesSent++; - eventLogEntry.State = EventStateEnum.Published; - _integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); - await _integrationEventLogContext.SaveChangesAsync(); + + await _integrationEventLogService.MarkEventAsPublishedAsync(@event); } return Ok(); diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs new file mode 100644 index 000000000..423a0eb98 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs @@ -0,0 +1,14 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Catalog.API.IntegrationEvents +{ + public interface IIntegrationEventLogService + { + Task SaveEventAsync(IntegrationEvent @event); + Task MarkEventAsPublishedAsync(IntegrationEvent @event); + } +} diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs new file mode 100644 index 000000000..e9859c5ae --- /dev/null +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Catalog.API.IntegrationEvents +{ + public class IntegrationEventLogService : IIntegrationEventLogService + { + private readonly IntegrationEventLogContext _integrationEventLogContext; + private readonly CatalogContext _catalogContext; + + public IntegrationEventLogService(CatalogContext catalogContext) + { + _catalogContext = catalogContext; + _integrationEventLogContext = new IntegrationEventLogContext( + new DbContextOptionsBuilder() + .UseSqlServer(catalogContext.Database.GetDbConnection()) + .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)) + .Options); + } + + public Task SaveEventAsync(IntegrationEvent @event) + { + var eventLogEntry = new IntegrationEventLogEntry(@event); + + // as a constraint this transaction has to be done together with a catalogContext transaction + _integrationEventLogContext.Database.UseTransaction(_catalogContext.Database.CurrentTransaction.GetDbTransaction()); + _integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry); + + return _integrationEventLogContext.SaveChangesAsync(); + } + + public Task MarkEventAsPublishedAsync(IntegrationEvent @event) + { + var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id); + eventLogEntry.TimesSent++; + eventLogEntry.State = EventStateEnum.Published; + + _integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); + + return _integrationEventLogContext.SaveChangesAsync(); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 05c6409fe..1598eb5a0 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -1,5 +1,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API { + using global::Catalog.API.IntegrationEvents; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -12,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using System.Data.SqlClient; using System.Reflection; public class Startup @@ -37,9 +39,11 @@ public void ConfigureServices(IServiceCollection services) { + var sqlConnection = new SqlConnection(Configuration["ConnectionString"]); + services.AddDbContext(c => { - c.UseSqlServer(Configuration["ConnectionString"]); + c.UseSqlServer(sqlConnection); // Changing default behavior when client evaluation occurs to throw. // Default in EF Core would be to log a warning when client evaluation is performed. c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); @@ -48,7 +52,7 @@ services.AddDbContext(c => { - c.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API")); + c.UseSqlServer(sqlConnection, b => b.MigrationsAssembly("Catalog.API")); c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); }); @@ -77,6 +81,8 @@ .AllowCredentials()); }); + services.AddTransient(); + var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; services.AddSingleton(new EventBusRabbitMQ(configuration.EventBusConnection)); @@ -106,9 +112,8 @@ //Seed Data CatalogContextSeed.SeedAsync(app, loggerFactory) .Wait(); - - // TODO: move this creation to a db initializer - integrationEventLogContext.Database.Migrate(); + + integrationEventLogContext.Database.Migrate(); } } From 94c7fd31e1d0da1fb36b087aea327e45a52bbbe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Thu, 23 Mar 2017 13:44:15 +0100 Subject: [PATCH 54/80] Added decorator for validation in commands Added Validation for orders and identified commands --- .../Decorators/ValidatorDecorator.cs | 45 +++++++++++++++++++ .../CreateOrderCommandValidator.cs | 39 ++++++++++++++++ .../Validations/IdentifierCommandValidator.cs | 17 +++++++ .../AutofacModules/MediatorModule.cs | 26 ++++++++--- .../Ordering/Ordering.API/Ordering.API.csproj | 2 + 5 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 src/Services/Ordering/Ordering.API/Application/Decorators/ValidatorDecorator.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Validations/IdentifierCommandValidator.cs diff --git a/src/Services/Ordering/Ordering.API/Application/Decorators/ValidatorDecorator.cs b/src/Services/Ordering/Ordering.API/Application/Decorators/ValidatorDecorator.cs new file mode 100644 index 000000000..5bdff8330 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Decorators/ValidatorDecorator.cs @@ -0,0 +1,45 @@ +using FluentValidation; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Decorators +{ + public class ValidatorDecorator + : IAsyncRequestHandler + where TRequest : IAsyncRequest + { + private readonly IAsyncRequestHandler _inner; + private readonly IValidator[] _validators; + + + public ValidatorDecorator( + IAsyncRequestHandler inner, + IValidator[] validators) + { + _inner = inner; + _validators = validators; + } + + public async Task Handle(TRequest message) + { + var failures = _validators + .Select(v => v.Validate(message)) + .SelectMany(result => result.Errors) + .Where(error => error != null) + .ToList(); + + if (failures.Any()) + { + throw new ValidationException( + $"Command Validation Errors for type {typeof(TRequest).Name}", failures); + } + + var response = await _inner.Handle(message); + + return response; + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs b/src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs new file mode 100644 index 000000000..449f95df5 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs @@ -0,0 +1,39 @@ +using FluentValidation; +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; + +namespace Ordering.API.Application.Validations +{ + public class CreateOrderCommandValidator : AbstractValidator + { + public CreateOrderCommandValidator() + { + RuleFor(order => order.City).NotEmpty(); + RuleFor(order => order.Street).NotEmpty(); + RuleFor(order => order.State).NotEmpty(); + RuleFor(order => order.Country).NotEmpty(); + RuleFor(order => order.ZipCode).NotEmpty(); + RuleFor(order => order.CardNumber).NotEmpty().Length(12, 19); + RuleFor(order => order.CardHolderName).NotEmpty(); + RuleFor(order => order.CardExpiration).NotEmpty().Must(BeValidExpirationDate).WithMessage("Please specify a valid card expiration date"); + RuleFor(order => order.CardSecurityNumber).NotEmpty().Length(3); + RuleFor(order => order.CardTypeId).NotEmpty(); + RuleFor(order => order.OrderItems).Must(ContainOrderItems).WithMessage("No order items found"); + } + + private bool BeValidExpirationDate(DateTime dateTime) + { + return dateTime >= DateTime.UtcNow; + } + + private bool ContainOrderItems(IEnumerable orderItems) + { + return orderItems.Any(); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Validations/IdentifierCommandValidator.cs b/src/Services/Ordering/Ordering.API/Application/Validations/IdentifierCommandValidator.cs new file mode 100644 index 000000000..44b374ee6 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Validations/IdentifierCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Validations +{ + public class IdentifierCommandValidator : AbstractValidator> + { + public IdentifierCommandValidator() + { + RuleFor(customer => customer.Id).NotEmpty(); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index a6864e0ef..e741644f3 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -1,9 +1,12 @@ using Autofac; using Autofac.Core; +using FluentValidation; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Decorators; +using Ordering.API.Application.Decorators; using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; +using Ordering.API.Application.Validations; using Ordering.Domain.Events; using System.Collections.Generic; using System.Linq; @@ -24,11 +27,17 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof .Where(i => i.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>))) .Select(i => new KeyedService("IAsyncRequestHandler", i))); - // Register all the Domain Event Handler classes (they implement IAsyncNotificationHandler<>) in assembly holding the Domain Events + // Register all the event classes (they implement IAsyncNotificationHandler) in assembly holding the Commands + builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly) + .As(o => o.GetInterfaces() + .Where(i => i.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>))) + .Select(i => new KeyedService("IAsyncNotificationHandler", i))); + builder - .RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly) - .Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>))) - .AsImplementedInterfaces(); + .RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly) + .Where(t => t.IsClosedTypeOf(typeof(IValidator<>))) + .AsImplementedInterfaces(); + builder.Register(context => { @@ -44,9 +53,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof return t => (IEnumerable)componentContext.Resolve(typeof(IEnumerable<>).MakeGenericType(t)); }); + + builder.RegisterGenericDecorator(typeof(LogDecorator<,>), typeof(IAsyncRequestHandler<,>), - "IAsyncRequestHandler"); + "IAsyncRequestHandler") + .Keyed("handlerDecorator", typeof(IAsyncRequestHandler<,>)); + + builder.RegisterGenericDecorator(typeof(ValidatorDecorator<,>), + typeof(IAsyncRequestHandler<,>), + fromKey: "handlerDecorator"); } } } diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index d0102c19f..d7dd2bc1a 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -28,6 +28,8 @@ + + From 5d8628c9e2502e6548c12b6ecd5046b6ca357069 Mon Sep 17 00:00:00 2001 From: dsanz Date: Thu, 23 Mar 2017 17:01:55 +0100 Subject: [PATCH 55/80] Fix test that was failing sporadically. --- .../Services/Ordering/OrderingScenarios.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs index f15797793..756bbed2a 100644 --- a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs @@ -1,10 +1,9 @@ -using Microsoft.AspNetCore.TestHost; -using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Data.SqlClient; +using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -22,20 +21,20 @@ namespace FunctionalTests.Services.Ordering { var client = server.CreateClient(); - //Arrange + // GIVEN an order is created await client.PostAsync(Post.AddNewOrder, new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json")); var ordersResponse = await client.GetAsync(Get.Orders); var responseBody = await ordersResponse.Content.ReadAsStringAsync(); - dynamic orders = JsonConvert.DeserializeObject(responseBody); - string orderId = orders[0].ordernumber; + var orders = JsonConvert.DeserializeObject>(responseBody); + string orderId = orders.OrderByDescending(o => o.Date).First().OrderNumber; - //Act + //WHEN we request the order bit its id var order= await client.GetAsync(Get.OrderBy(int.Parse(orderId))); var orderBody = await order.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject(orderBody); - //Assert + //THEN the requested order is returned Assert.Equal(orderId, result.OrderNumber); Assert.Equal("inprocess", result.Status); Assert.Equal(1, result.OrderItems.Count); @@ -63,7 +62,7 @@ namespace FunctionalTests.Services.Ordering order.AddOrderItem(new OrderItemDTO() { ProductId = 1, - Discount = 10M, + Discount = 12M, UnitPrice = 10, Units = 1, ProductName = "Some name" From 561ba3b1ec092e1465c81f2145c88aac9725f168 Mon Sep 17 00:00:00 2001 From: Eduard Tomas Date: Thu, 23 Mar 2017 19:10:55 +0100 Subject: [PATCH 56/80] 1st version of healthcheck & webstatus --- docker-compose.override.yml | 13 +- docker-compose.prod.yml | 12 +- docker-compose.vs.debug.yml | 15 + docker-compose.vs.release.yml | 10 + docker-compose.yml | 6 + eShopOnContainers-ServicesAndWebApps.sln | 213 +- .../HealthCheckMiddleware.cs | 68 + .../HealthCheckStartupFilter.cs | 38 + .../HealthCheckWebHostBuilderExtension.cs | 36 + .../HealthCheckWebHostExtensions.cs | 38 + .../Microsoft.AspNetCore.HealthChecks.csproj | 16 + .../HealthCheckBuilderDataExtensions.cs | 45 + ...rosoft.Extensions.HealthChecks.Data.csproj | 19 + .../CheckStatus.cs | 13 + .../Checks/AddCheck.cs | 110 + .../Checks/NumericChecks.cs | 55 + .../Checks/SystemChecks.cs | 21 + .../Checks/UrlChecks.cs | 109 + .../CompositeHealthCheckResult.cs | 83 + .../HealthCheck.cs | 76 + .../HealthCheckBuilder.cs | 40 + .../HealthCheckExtensions.cs | 18 + .../HealthCheckResult.cs | 54 + .../HealthCheckResults.cs | 12 + .../HealthCheckService.cs | 54 + .../HealthCheckServiceCollectionExtensions.cs | 22 + .../HealthCheckServiceExtensions.cs | 38 + .../IHealthCheck.cs | 16 + .../IHealthCheckResult.cs | 14 + .../IHealthCheckService.cs | 13 + .../Internal/UrlChecker.cs | 92 + .../Microsoft.Extensions.HealthChecks.csproj | 20 + .../HealthChecks/src/common/Guard.cs | 45 + .../Basket/Basket.API/Basket.API.csproj | 3 + src/Services/Basket/Basket.API/Program.cs | 1 + src/Services/Basket/Basket.API/Startup.cs | 8 + .../Catalog/Catalog.API/Catalog.API.csproj | 3 + src/Services/Catalog/Catalog.API/Program.cs | 1 + src/Services/Catalog/Catalog.API/Startup.cs | 7 + .../Ordering/Ordering.API/Ordering.API.csproj | 3 + src/Services/Ordering/Ordering.API/Program.cs | 1 + src/Services/Ordering/Ordering.API/Startup.cs | 7 + src/Web/WebMVC/wwwroot/css/site.min.css | 2 +- src/Web/WebStatus/.bowerrc | 3 + src/Web/WebStatus/.dockerignore | 3 + .../WebStatus/Controllers/HomeController.cs | 39 + src/Web/WebStatus/Dockerfile | 6 + .../HealthCheckBuilderExtensions.cs | 22 + src/Web/WebStatus/Program.cs | 25 + .../WebStatus/Properties/launchSettings.json | 27 + src/Web/WebStatus/Startup.cs | 70 + .../Viewmodels/HealthStatusViewModel.cs | 22 + src/Web/WebStatus/Views/Home/Index.cshtml | 20 + src/Web/WebStatus/Views/Shared/Error.cshtml | 14 + src/Web/WebStatus/Views/Shared/_Layout.cshtml | 73 + .../Shared/_ValidationScriptsPartial.cshtml | 18 + src/Web/WebStatus/Views/_ViewImports.cshtml | 2 + src/Web/WebStatus/Views/_ViewStart.cshtml | 3 + src/Web/WebStatus/WebStatus.csproj | 28 + .../WebStatus/appsettings.Development.json | 10 + src/Web/WebStatus/appsettings.json | 11 + src/Web/WebStatus/bower.json | 10 + src/Web/WebStatus/bundleconfig.json | 24 + src/Web/WebStatus/wwwroot/css/site.css | 37 + src/Web/WebStatus/wwwroot/css/site.min.css | 1 + src/Web/WebStatus/wwwroot/favicon.ico | Bin 0 -> 32038 bytes src/Web/WebStatus/wwwroot/images/banner1.svg | 1 + src/Web/WebStatus/wwwroot/images/banner2.svg | 1 + src/Web/WebStatus/wwwroot/images/banner3.svg | 1 + src/Web/WebStatus/wwwroot/images/banner4.svg | 1 + src/Web/WebStatus/wwwroot/js/site.js | 1 + src/Web/WebStatus/wwwroot/js/site.min.js | 0 .../wwwroot/lib/bootstrap/.bower.json | 45 + .../WebStatus/wwwroot/lib/bootstrap/LICENSE | 21 + .../bootstrap/dist/css/bootstrap-theme.css | 587 + .../dist/css/bootstrap-theme.css.map | 1 + .../dist/css/bootstrap-theme.min.css | 6 + .../dist/css/bootstrap-theme.min.css.map | 1 + .../lib/bootstrap/dist/css/bootstrap.css | 6757 +++++++++++ .../lib/bootstrap/dist/css/bootstrap.css.map | 1 + .../lib/bootstrap/dist/css/bootstrap.min.css | 6 + .../bootstrap/dist/css/bootstrap.min.css.map | 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 .../lib/bootstrap/dist/js/bootstrap.js | 2377 ++++ .../lib/bootstrap/dist/js/bootstrap.min.js | 7 + .../wwwroot/lib/bootstrap/dist/js/npm.js | 13 + .../jquery-validation-unobtrusive/.bower.json | 44 + .../jquery.validate.unobtrusive.js | 416 + .../jquery.validate.unobtrusive.min.js | 5 + .../wwwroot/lib/jquery-validation/.bower.json | 40 + .../wwwroot/lib/jquery-validation/LICENSE.md | 22 + .../dist/additional-methods.js | 998 ++ .../dist/additional-methods.min.js | 4 + .../jquery-validation/dist/jquery.validate.js | 1398 +++ .../dist/jquery.validate.min.js | 4 + .../WebStatus/wwwroot/lib/jquery/.bower.json | 25 + .../WebStatus/wwwroot/lib/jquery/LICENSE.txt | 36 + .../wwwroot/lib/jquery/dist/jquery.js | 9831 +++++++++++++++++ .../wwwroot/lib/jquery/dist/jquery.min.js | 4 + .../wwwroot/lib/jquery/dist/jquery.min.map | 1 + 104 files changed, 24876 insertions(+), 6 deletions(-) create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostExtensions.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/Microsoft.AspNetCore.HealthChecks.csproj create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CheckStatus.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResults.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckResult.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs create mode 100644 src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Microsoft.Extensions.HealthChecks.csproj create mode 100644 src/BuildingBlocks/HealthChecks/src/common/Guard.cs create mode 100644 src/Web/WebStatus/.bowerrc create mode 100644 src/Web/WebStatus/.dockerignore create mode 100644 src/Web/WebStatus/Controllers/HomeController.cs create mode 100644 src/Web/WebStatus/Dockerfile create mode 100644 src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs create mode 100644 src/Web/WebStatus/Program.cs create mode 100644 src/Web/WebStatus/Properties/launchSettings.json create mode 100644 src/Web/WebStatus/Startup.cs create mode 100644 src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs create mode 100644 src/Web/WebStatus/Views/Home/Index.cshtml create mode 100644 src/Web/WebStatus/Views/Shared/Error.cshtml create mode 100644 src/Web/WebStatus/Views/Shared/_Layout.cshtml create mode 100644 src/Web/WebStatus/Views/Shared/_ValidationScriptsPartial.cshtml create mode 100644 src/Web/WebStatus/Views/_ViewImports.cshtml create mode 100644 src/Web/WebStatus/Views/_ViewStart.cshtml create mode 100644 src/Web/WebStatus/WebStatus.csproj create mode 100644 src/Web/WebStatus/appsettings.Development.json create mode 100644 src/Web/WebStatus/appsettings.json create mode 100644 src/Web/WebStatus/bower.json create mode 100644 src/Web/WebStatus/bundleconfig.json create mode 100644 src/Web/WebStatus/wwwroot/css/site.css create mode 100644 src/Web/WebStatus/wwwroot/css/site.min.css create mode 100644 src/Web/WebStatus/wwwroot/favicon.ico create mode 100644 src/Web/WebStatus/wwwroot/images/banner1.svg create mode 100644 src/Web/WebStatus/wwwroot/images/banner2.svg create mode 100644 src/Web/WebStatus/wwwroot/images/banner3.svg create mode 100644 src/Web/WebStatus/wwwroot/images/banner4.svg create mode 100644 src/Web/WebStatus/wwwroot/js/site.js create mode 100644 src/Web/WebStatus/wwwroot/js/site.min.js create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/.bower.json create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/LICENSE create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.css create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/js/bootstrap.js create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js create mode 100644 src/Web/WebStatus/wwwroot/lib/bootstrap/dist/js/npm.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation-unobtrusive/.bower.json create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation/.bower.json create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation/LICENSE.md create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation/dist/additional-methods.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation/dist/additional-methods.min.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation/dist/jquery.validate.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery/.bower.json create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery/LICENSE.txt create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery/dist/jquery.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery/dist/jquery.min.js create mode 100644 src/Web/WebStatus/wwwroot/lib/jquery/dist/jquery.min.map diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 3f3d5994f..0e6d98187 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -76,4 +76,15 @@ services: - SA_PASSWORD=Pass@word - ACCEPT_EULA=Y ports: - - "5433:1433" \ No newline at end of file + - "5433:1433" + + webstatus: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:5107 + - CatalogUrl=http://catalog.api:5101/hc + - OrderingUrl=http://ordering.api:5102/hc + - BasketUrl=http://basket.api:5103/hc + - IdentityUrl=http://10.0.75.1:5105/hc + ports: + - "5107:5107" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 14d8114ea..2a5dfe215 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -77,4 +77,14 @@ services: - SA_PASSWORD=Pass@word - ACCEPT_EULA=Y ports: - - "5433:1433" \ No newline at end of file + - "5433:1433" + + webstatus: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - CatalogUrl=http://catalog.api:5101/hc + - OrderingUrl=http://ordering.api:5102/hc + - BasketUrl=http://basket.api:5103/hc + - IdentityUrl=http://10.0.75.1:5105/hc + ports: + - "5107:5107" diff --git a/docker-compose.vs.debug.yml b/docker-compose.vs.debug.yml index 2446fdb2c..2e7145637 100644 --- a/docker-compose.vs.debug.yml +++ b/docker-compose.vs.debug.yml @@ -90,3 +90,18 @@ services: entrypoint: tail -f /dev/null labels: - "com.microsoft.visualstudio.targetoperatingsystem=linux" + + webstatus: + image: eshop/webstatus:dev + build: + args: + source: ${DOCKER_BUILD_SOURCE} + environment: + - DOTNET_USE_POLLING_FILE_WATCHER=1 + volumes: + - ./src/Web/WebStatus:/app + - ~/.nuget/packages:/root/.nuget/packages:ro + - ~/clrdbg:/clrdbg:ro + entrypoint: tail -f /dev/null + labels: + - "com.microsoft.visualstudio.targetoperatingsystem=linux" diff --git a/docker-compose.vs.release.yml b/docker-compose.vs.release.yml index 4ea31f8ca..d1ca5b2c6 100644 --- a/docker-compose.vs.release.yml +++ b/docker-compose.vs.release.yml @@ -60,3 +60,13 @@ services: entrypoint: tail -f /dev/null labels: - "com.microsoft.visualstudio.targetoperatingsystem=linux" + + webstatus: + build: + args: + source: ${DOCKER_BUILD_SOURCE} + volumes: + - ~/clrdbg:/clrdbg:ro + entrypoint: tail -f /dev/null + labels: + - "com.microsoft.visualstudio.targetoperatingsystem=linux" diff --git a/docker-compose.yml b/docker-compose.yml index 09380e299..09212da8e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,3 +68,9 @@ services: image: rabbitmq ports: - "5672:5672" + + webstatus: + image: eshop/webstatus + build: + context: ./src/Web/WebStatus + dockerfile: Dockerfile diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 619dcf4c3..0fd8c1a98 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26228.9 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" EndProject @@ -68,9 +68,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingB EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventBus", "EventBus", "{807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus", "src\BuildingBlocks\EventBus\EventBus\EventBus.csproj", "{0044B293-1DCC-4224-B948-00CF6DC7F510}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus", "src\BuildingBlocks\EventBus\EventBus\EventBus.csproj", "{0044B293-1DCC-4224-B948-00CF6DC7F510}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBusRabbitMQ", "src\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj", "{8088F3FC-6787-45FA-A924-816EC81CBFAC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBusRabbitMQ", "src\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj", "{8088F3FC-6787-45FA-A924-816EC81CBFAC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks", "HealthChecks", "{A81ECBC2-6B00-4DCD-8388-469174033379}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{942ED6E8-0050-495F-A0EA-01E97F63760C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.Data", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj", "{7804FC60-23E6-490C-8E08-F9FEF829F184}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -662,6 +672,198 @@ Global {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x64.Build.0 = Release|Any CPU {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x86.ActiveCfg = Release|Any CPU {8088F3FC-6787-45FA-A924-816EC81CBFAC}.Release|x86.Build.0 = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|ARM.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhone.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x64.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x64.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x86.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x86.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|ARM.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|ARM.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhone.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x64.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x86.Build.0 = Debug|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|Any CPU.Build.0 = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|ARM.ActiveCfg = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|ARM.Build.0 = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhone.ActiveCfg = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhone.Build.0 = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x64.ActiveCfg = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x64.Build.0 = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x86.ActiveCfg = Release|Any CPU + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x86.Build.0 = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|ARM.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhone.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x64.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x64.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x86.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x86.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|ARM.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhone.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x64.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x64.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x86.ActiveCfg = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x86.Build.0 = Debug|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|Any CPU.Build.0 = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|ARM.ActiveCfg = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|ARM.Build.0 = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhone.ActiveCfg = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhone.Build.0 = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.ActiveCfg = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.Build.0 = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.ActiveCfg = Release|Any CPU + {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.Build.0 = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|ARM.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhone.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x64.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x64.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x86.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x86.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|ARM.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|ARM.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhone.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x64.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x64.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x86.ActiveCfg = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x86.Build.0 = Debug|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|Any CPU.Build.0 = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|ARM.ActiveCfg = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|ARM.Build.0 = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhone.ActiveCfg = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhone.Build.0 = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x64.ActiveCfg = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x64.Build.0 = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x86.ActiveCfg = Release|Any CPU + {7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x86.Build.0 = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|ARM.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|iPhone.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|x64.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|x64.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|x86.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.AppStore|x86.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|ARM.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|iPhone.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|x64.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Debug|x86.Build.0 = Debug|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|Any CPU.Build.0 = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|ARM.ActiveCfg = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|ARM.Build.0 = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|iPhone.ActiveCfg = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|iPhone.Build.0 = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x64.ActiveCfg = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x64.Build.0 = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.ActiveCfg = Release|Any CPU + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -689,5 +891,10 @@ Global {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {0044B293-1DCC-4224-B948-00CF6DC7F510} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} {8088F3FC-6787-45FA-A924-816EC81CBFAC} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} + {A81ECBC2-6B00-4DCD-8388-469174033379} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} + {DF8367F8-E6BD-4D07-99D2-E416BF8AB01E} = {A81ECBC2-6B00-4DCD-8388-469174033379} + {942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379} + {7804FC60-23E6-490C-8E08-F9FEF829F184} = {A81ECBC2-6B00-4DCD-8388-469174033379} + {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} EndGlobalSection EndGlobal diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs new file mode 100644 index 000000000..64b4f48c2 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.HealthChecks; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.HealthChecks +{ + public class HealthCheckMiddleware + { + private RequestDelegate _next; + private string _path; + private int? _port; + private IHealthCheckService _service; + + public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port) + { + _port = port; + _service = service; + _next = next; + } + + public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path) + { + _path = path; + _service = service; + _next = next; + } + + public async Task Invoke(HttpContext context) + { + if (IsHealthCheckRequest(context)) + { + var result = await _service.CheckHealthAsync(); + var status = result.CheckStatus; + + if (status != CheckStatus.Healthy) + context.Response.StatusCode = 503; + + context.Response.Headers.Add("content-type", "application/json"); + await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = status.ToString() })); + return; + } + else + { + await _next.Invoke(context); + } + } + + private bool IsHealthCheckRequest(HttpContext context) + { + if (_port.HasValue) + { + var connInfo = context.Features.Get(); + if (connInfo.LocalPort == _port) + return true; + } + + if (context.Request.Path == _path) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs new file mode 100644 index 000000000..4aa91b070 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; + +namespace Microsoft.AspNetCore.HealthChecks +{ + public class HealthCheckStartupFilter : IStartupFilter + { + private string _path; + private int? _port; + + public HealthCheckStartupFilter(int port) + { + _port = port; + } + + public HealthCheckStartupFilter(string path) + { + _path = path; + } + + public Action Configure(Action next) + { + return app => + { + if (_port.HasValue) + app.UseMiddleware(_port); + else + app.UseMiddleware(_path); + + next(app); + }; + } + } +} \ No newline at end of file diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs new file mode 100644 index 000000000..0b806ca06 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.HealthChecks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting +{ + public static class HealthCheckWebHostBuilderExtension + { + public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port) + { + Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535"); + + builder.ConfigureServices(services => + { + var existingUrl = builder.GetSetting(WebHostDefaults.ServerUrlsKey); + builder.UseSetting(WebHostDefaults.ServerUrlsKey, $"{existingUrl};http://localhost:{port}"); + + services.AddSingleton(new HealthCheckStartupFilter(port)); + }); + return builder; + } + + public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path) + { + Guard.ArgumentNotNull(nameof(path), path); + // REVIEW: Is there a better URL path validator somewhere? + Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values"); + Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with /"); + + builder.ConfigureServices(services => services.AddSingleton(new HealthCheckStartupFilter(path))); + return builder; + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostExtensions.cs new file mode 100644 index 000000000..67448ff17 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.HealthChecks; + +namespace Microsoft.AspNetCore.Hosting +{ + public static class HealthCheckWebHostExtensions + { + private const int DEFAULT_TIMEOUT_SECONDS = 300; + + public static void RunWhenHealthy(this IWebHost webHost) + { + webHost.RunWhenHealthy(TimeSpan.FromSeconds(DEFAULT_TIMEOUT_SECONDS)); + } + + public static void RunWhenHealthy(this IWebHost webHost, TimeSpan timeout) + { + var healthChecks = webHost.Services.GetService(typeof(IHealthCheckService)) as IHealthCheckService; + + var loops = 0; + do + { + var checkResult = healthChecks.CheckHealthAsync().Result; + if (checkResult.CheckStatus == CheckStatus.Healthy) + { + webHost.Run(); + break; + } + + System.Threading.Thread.Sleep(1000); + loops++; + + } while (loops < timeout.TotalSeconds); + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/Microsoft.AspNetCore.HealthChecks.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/Microsoft.AspNetCore.HealthChecks.csproj new file mode 100644 index 000000000..62a414c17 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/Microsoft.AspNetCore.HealthChecks.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp1.0 + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs new file mode 100644 index 000000000..e209dfdaf --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Data; +using System.Data.SqlClient; + +namespace Microsoft.Extensions.HealthChecks +{ + public static class HealthCheckBuilderDataExtensions + { + public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString) + { + builder.AddCheck($"SqlCheck({name})", async () => + { + try + { + //TODO: There is probably a much better way to do this. + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + using (var command = connection.CreateCommand()) + { + command.CommandType = CommandType.Text; + command.CommandText = "SELECT 1"; + var result = (int)await command.ExecuteScalarAsync().ConfigureAwait(false); + if (result == 1) + { + return HealthCheckResult.Healthy($"SqlCheck({name}): Healthy"); + } + + return HealthCheckResult.Unhealthy($"SqlCheck({name}): Unhealthy"); + } + } + } + catch(Exception ex) + { + return HealthCheckResult.Unhealthy($"SqlCheck({name}): Exception during check: {ex.GetType().FullName}"); + } + }); + + return builder; + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj new file mode 100644 index 000000000..38cb3ffe3 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj @@ -0,0 +1,19 @@ + + + + netstandard1.3 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CheckStatus.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CheckStatus.cs new file mode 100644 index 000000000..c5d1a093f --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CheckStatus.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.Extensions.HealthChecks +{ + public enum CheckStatus + { + Unknown, + Unhealthy, + Healthy, + Warning + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs new file mode 100644 index 000000000..cd10fb93d --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/AddCheck.cs @@ -0,0 +1,110 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.HealthChecks +{ + public static partial class HealthCheckBuilderExtensions + { + // Lambda versions of AddCheck for Func/Func/Func + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check, TimeSpan cacheDuration) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func check, TimeSpan cacheDuration) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration)); + return builder; + } + + public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func> check, TimeSpan cacheDuration) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration)); + return builder; + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs new file mode 100644 index 000000000..cb97eec46 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.HealthChecks +{ + public static partial class HealthCheckBuilderExtensions + { + // Numeric checks + + public static HealthCheckBuilder AddMinValueCheck(this HealthCheckBuilder builder, string name, T minValue, Func currentValueFunc) + where T : IComparable + { + Guard.ArgumentNotNull(nameof(builder), builder); + Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc); + + builder.AddCheck(name, () => + { + var currentValue = currentValueFunc(); + var status = currentValue.CompareTo(minValue) >= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy; + return HealthCheckResult.FromStatus( + status, + $"{name}: min={minValue}, current={currentValue}", + new Dictionary { { "min", minValue }, { "current", currentValue } } + ); + }); + + return builder; + } + + public static HealthCheckBuilder AddMaxValueCheck(this HealthCheckBuilder builder, string name, T maxValue, Func currentValueFunc) + where T : IComparable + { + Guard.ArgumentNotNull(nameof(builder), builder); + Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc); + + builder.AddCheck($"{name}", () => + { + var currentValue = currentValueFunc(); + var status = currentValue.CompareTo(maxValue) <= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy; + return HealthCheckResult.FromStatus( + status, + $"{name}: max={maxValue}, current={currentValue}", + new Dictionary { { "max", maxValue }, { "current", currentValue } } + ); + }); + + return builder; + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs new file mode 100644 index 000000000..d4491fda4 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; + +namespace Microsoft.Extensions.HealthChecks +{ + public static partial class HealthCheckBuilderExtensions + { + // System checks + + public static HealthCheckBuilder AddPrivateMemorySizeCheck(this HealthCheckBuilder builder, long maxSize) + => AddMaxValueCheck(builder, $"PrivateMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().PrivateMemorySize64); + + public static HealthCheckBuilder AddVirtualMemorySizeCheck(this HealthCheckBuilder builder, long maxSize) + => AddMaxValueCheck(builder, $"VirtualMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().VirtualMemorySize64); + + public static HealthCheckBuilder AddWorkingSetCheck(this HealthCheckBuilder builder, long maxSize) + => AddMaxValueCheck(builder, $"WorkingSet({maxSize})", maxSize, () => Process.GetCurrentProcess().WorkingSet64); + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs new file mode 100644 index 000000000..6ab393547 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.HealthChecks.Internal; + +namespace Microsoft.Extensions.HealthChecks +{ + public static partial class HealthCheckBuilderExtensions + { + // URL checks + + public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url) + => AddUrlCheck(builder, url, response => UrlChecker.DefaultUrlCheck(response)); + + public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, + Func checkFunc) + { + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + + return AddUrlCheck(builder, url, response => new ValueTask(checkFunc(response))); + } + + public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, + Func> checkFunc) + { + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + + return AddUrlCheck(builder, url, response => new ValueTask(checkFunc(response))); + } + + public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, + Func> checkFunc) + { + Guard.ArgumentNotNull(nameof(builder), builder); + Guard.ArgumentNotNullOrWhitespace(nameof(url), url); + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + + var urlCheck = new UrlChecker(checkFunc, url); + builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync()); + return builder; + } + + public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName) + => AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => UrlChecker.DefaultUrlCheck(response)); + + public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, + Func checkFunc) + { + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + + return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask(checkFunc(response))); + } + + public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, + Func> checkFunc) + { + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + + return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask(checkFunc(response))); + } + + public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, + Func> checkFunc) + { + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + + return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => checkFunc(response)); + } + + public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, + CheckStatus partialSuccessStatus) + => AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => UrlChecker.DefaultUrlCheck(response)); + + public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, + CheckStatus partialSuccessStatus, Func checkFunc) + { + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + + return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask(checkFunc(response))); + } + + public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, + CheckStatus partialSuccessStatus, Func> checkFunc) + { + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + + return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask(checkFunc(response))); + } + + public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable urlItems, string groupName, + CheckStatus partialSuccessStatus, Func> checkFunc) + { + var urls = urlItems?.ToArray(); + + Guard.ArgumentNotNull(nameof(builder), builder); + Guard.ArgumentNotNullOrEmpty(nameof(urlItems), urls); + Guard.ArgumentNotNullOrWhitespace(nameof(groupName), groupName); + + var urlChecker = new UrlChecker(checkFunc, urls) { PartiallyHealthyStatus = partialSuccessStatus }; + builder.AddCheck($"UrlChecks({groupName})", () => urlChecker.CheckAsync()); + return builder; + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs new file mode 100644 index 000000000..5a3367843 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/CompositeHealthCheckResult.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.HealthChecks +{ + // REVIEW: Does this need to be thread safe? + /// + /// Represents a composite health check result built from several results. + /// + public class CompositeHealthCheckResult : IHealthCheckResult + { + private static readonly IReadOnlyDictionary _emptyData = new Dictionary(); + private readonly CheckStatus _initialStatus; + private readonly CheckStatus _partiallyHealthyStatus; + private readonly Dictionary _results = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public CompositeHealthCheckResult(CheckStatus partiallyHealthyStatus = CheckStatus.Warning, + CheckStatus initialStatus = CheckStatus.Unknown) + { + _partiallyHealthyStatus = partiallyHealthyStatus; + _initialStatus = initialStatus; + } + + public CheckStatus CheckStatus + { + get + { + var checkStatuses = new HashSet(_results.Select(x => x.Value.CheckStatus)); + if (checkStatuses.Count == 0) + return _initialStatus; + if (checkStatuses.Count == 1) + return checkStatuses.First(); + if (checkStatuses.Contains(CheckStatus.Healthy)) + return _partiallyHealthyStatus; + + return CheckStatus.Unhealthy; + } + } + + public string Description => string.Join(Environment.NewLine, _results.Select(r => r.Value.Description)); + + public IReadOnlyDictionary Data + { + get + { + var result = new Dictionary(); + + foreach (var kvp in _results) + result.Add(kvp.Key, kvp.Value.Data); + + return result; + } + } + + public IReadOnlyDictionary Results => _results; + + // REVIEW: Should description be required? Seems redundant for success checks. + + public void Add(string name, CheckStatus status, string description) + => Add(name, status, description, null); + + public void Add(string name, CheckStatus status, string description, Dictionary data) + { + Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add unknown status to composite health check result"); + Guard.ArgumentNotNullOrWhitespace(nameof(description), description); + + _results.Add(name, HealthCheckResult.FromStatus(status, description, data)); + } + + public void Add(string name, IHealthCheckResult checkResult) + { + Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentNotNull(nameof(checkResult), checkResult); + + _results.Add(name, checkResult); + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs new file mode 100644 index 000000000..d068a7d05 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheck.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.HealthChecks +{ + public class HealthCheck : IHealthCheck + { + private DateTimeOffset _cacheExpiration; + private IHealthCheckResult _cachedResult; + private volatile int _writerCount; + + protected HealthCheck(Func> check, TimeSpan cacheDuration) + { + Guard.ArgumentNotNull(nameof(check), check); + Guard.ArgumentValid(cacheDuration >= TimeSpan.Zero, nameof(cacheDuration), "Cache duration must either be zero (disabled) or a positive value"); + + Check = check; + CacheDuration = cacheDuration; + } + + public TimeSpan CacheDuration { get; } + + protected Func> Check { get; } + + protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow; + + public async ValueTask CheckAsync(CancellationToken cancellationToken) + { + while (_cacheExpiration <= UtcNow) + { + // Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value, + // and the waiters who aren't allowed to write will just spin wait for the new value. + if (Interlocked.Exchange(ref _writerCount, 1) != 0) + { + await Task.Delay(5, cancellationToken).ConfigureAwait(false); + continue; + } + + try + { + _cachedResult = await Check(cancellationToken).ConfigureAwait(false); + _cacheExpiration = UtcNow + CacheDuration; + break; + } + finally + { + _writerCount = 0; + } + } + + return _cachedResult; + } + + public static HealthCheck FromCheck(Func check, TimeSpan cacheDuration) + => new HealthCheck(token => new ValueTask(check()), cacheDuration); + + public static HealthCheck FromCheck(Func check, TimeSpan cacheDuration) + => new HealthCheck(token => new ValueTask(check(token)), cacheDuration); + + public static HealthCheck FromTaskCheck(Func> check, TimeSpan cacheDuration) + => new HealthCheck(token => new ValueTask(check()), cacheDuration); + + public static HealthCheck FromTaskCheck(Func> check, TimeSpan cacheDuration) + => new HealthCheck(token => new ValueTask(check(token)), cacheDuration); + + public static HealthCheck FromValueTaskCheck(Func> check, TimeSpan cacheDuration) + => new HealthCheck(token => check(), cacheDuration); + + public static HealthCheck FromValueTaskCheck(Func> check, TimeSpan cacheDuration) + => new HealthCheck(check, cacheDuration); + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs new file mode 100644 index 000000000..f6cc17304 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.HealthChecks +{ + public class HealthCheckBuilder + { + private readonly Dictionary _checks; + + public HealthCheckBuilder() + { + _checks = new Dictionary(StringComparer.OrdinalIgnoreCase); + DefaultCacheDuration = TimeSpan.FromMinutes(5); + } + + public IReadOnlyDictionary Checks => _checks; + + public TimeSpan DefaultCacheDuration { get; private set; } + + public HealthCheckBuilder AddCheck(string name, IHealthCheck check) + { + Guard.ArgumentNotNullOrWhitespace(nameof(name), name); + Guard.ArgumentNotNull(nameof(check), check); + + _checks.Add(name, check); + return this; + } + + public HealthCheckBuilder WithDefaultCacheDuration(TimeSpan duration) + { + Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration"); + + DefaultCacheDuration = duration; + return this; + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs new file mode 100644 index 000000000..2669afc77 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckExtensions.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.HealthChecks +{ + public static class HealthCheckExtensions + { + public static ValueTask CheckAsync(this IHealthCheck healthCheck) + { + Guard.ArgumentNotNull(nameof(healthCheck), healthCheck); + + return healthCheck.CheckAsync(CancellationToken.None); + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs new file mode 100644 index 000000000..7ea31b42a --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResult.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.HealthChecks +{ + public class HealthCheckResult : IHealthCheckResult + { + private static readonly IReadOnlyDictionary _emptyData = new Dictionary(); + + public CheckStatus CheckStatus { get; } + public IReadOnlyDictionary Data { get; } + public string Description { get; } + + private HealthCheckResult(CheckStatus checkStatus, string description, IReadOnlyDictionary data) + { + CheckStatus = checkStatus; + Description = description; + Data = data ?? _emptyData; + } + + public static HealthCheckResult Unhealthy(string description) + => new HealthCheckResult(CheckStatus.Unhealthy, description, null); + + public static HealthCheckResult Unhealthy(string description, IReadOnlyDictionary data) + => new HealthCheckResult(CheckStatus.Unhealthy, description, data); + + public static HealthCheckResult Healthy(string description) + => new HealthCheckResult(CheckStatus.Healthy, description, null); + + public static HealthCheckResult Healthy(string description, IReadOnlyDictionary data) + => new HealthCheckResult(CheckStatus.Healthy, description, data); + + public static HealthCheckResult Warning(string description) + => new HealthCheckResult(CheckStatus.Warning, description, null); + + public static HealthCheckResult Warning(string description, IReadOnlyDictionary data) + => new HealthCheckResult(CheckStatus.Warning, description, data); + + public static HealthCheckResult Unknown(string description) + => new HealthCheckResult(CheckStatus.Unknown, description, null); + + public static HealthCheckResult Unknown(string description, IReadOnlyDictionary data) + => new HealthCheckResult(CheckStatus.Unknown, description, data); + + public static HealthCheckResult FromStatus(CheckStatus status, string description) + => new HealthCheckResult(status, description, null); + + public static HealthCheckResult FromStatus(CheckStatus status, string description, IReadOnlyDictionary data) + => new HealthCheckResult(status, description, data); + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResults.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResults.cs new file mode 100644 index 000000000..d85dd9e39 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckResults.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.HealthChecks +{ + public class HealthCheckResults + { + public IList CheckResults { get; } = new List(); + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs new file mode 100644 index 000000000..6e16bed61 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckService.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Extensions.HealthChecks +{ + public class HealthCheckService : IHealthCheckService + { + public IReadOnlyDictionary _checks; + + private ILogger _logger; + + public HealthCheckService(HealthCheckBuilder builder, ILogger logger) + { + _checks = builder.Checks; + _logger = logger; + } + + public async Task CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken) + { + var logMessage = new StringBuilder(); + var result = new CompositeHealthCheckResult(partiallyHealthyStatus); + + foreach (var check in _checks) + { + try + { + var healthCheckResult = await check.Value.CheckAsync().ConfigureAwait(false); + logMessage.AppendLine($"HealthCheck: {check.Key} : {healthCheckResult.CheckStatus}"); + result.Add(check.Key, healthCheckResult); + } + catch (Exception ex) + { + logMessage.AppendLine($"HealthCheck: {check.Key} : Exception {ex.GetType().FullName} thrown"); + result.Add(check.Key, CheckStatus.Unhealthy, $"Exception during check: {ex.GetType().FullName}"); + } + } + + if (logMessage.Length == 0) + logMessage.AppendLine("HealthCheck: No checks have been registered"); + + _logger.Log((result.CheckStatus == CheckStatus.Healthy ? LogLevel.Information : LogLevel.Error), 0, logMessage.ToString(), null, MessageFormatter); + return result; + } + + private static string MessageFormatter(string state, Exception error) => state; + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs new file mode 100644 index 000000000..cdd763d9f --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.HealthChecks; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class HealthCheckServiceCollectionExtensions + { + public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action checkupAction) + { + var checkupBuilder = new HealthCheckBuilder(); + + checkupAction.Invoke(checkupBuilder); + + services.AddSingleton(checkupBuilder); + services.AddSingleton(); + return services; + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs new file mode 100644 index 000000000..98ab54b70 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckServiceExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.HealthChecks +{ + public static class HealthCheckServiceExtensions + { + public static Task CheckHealthAsync(this IHealthCheckService service) + { + Guard.ArgumentNotNull(nameof(service), service); + + return service.CheckHealthAsync(CheckStatus.Unhealthy, CancellationToken.None); + } + + public static Task CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus) + { + Guard.ArgumentNotNull(nameof(service), service); + + return service.CheckHealthAsync(partiallyHealthyStatus, CancellationToken.None); + } + + public static Task CheckHealthAsync(this IHealthCheckService service, CancellationToken cancellationToken) + { + Guard.ArgumentNotNull(nameof(service), service); + + return service.CheckHealthAsync(CheckStatus.Unhealthy, cancellationToken); + } + public static Task CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken) + { + Guard.ArgumentNotNull(nameof(service), service); + + return service.CheckHealthAsync(partiallyHealthyStatus, cancellationToken); + } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs new file mode 100644 index 000000000..b5b95405f --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheck.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.HealthChecks +{ + public interface IHealthCheck + { + TimeSpan CacheDuration { get; } + + ValueTask CheckAsync(CancellationToken cancellationToken); + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckResult.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckResult.cs new file mode 100644 index 000000000..18a97f93c --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckResult.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.HealthChecks +{ + public interface IHealthCheckResult + { + CheckStatus CheckStatus { get; } + string Description { get; } + IReadOnlyDictionary Data { get; } + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs new file mode 100644 index 000000000..60e917264 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/IHealthCheckService.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.HealthChecks +{ + public interface IHealthCheckService + { + Task CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken); + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs new file mode 100644 index 000000000..ad021b149 --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Internal/UrlChecker.cs @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.HealthChecks.Internal +{ + public class UrlChecker + { + private readonly Func> _checkFunc; + private readonly string[] _urls; + + // REVIEW: Cache timeout here? + public UrlChecker(Func> checkFunc, params string[] urls) + { + Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); + Guard.ArgumentNotNullOrEmpty(nameof(urls), urls); + + _checkFunc = checkFunc; + _urls = urls; + } + + public CheckStatus PartiallyHealthyStatus { get; set; } = CheckStatus.Warning; + + public Task CheckAsync() + => _urls.Length == 1 ? CheckSingleAsync() : CheckMultiAsync(); + + public async Task CheckSingleAsync() + { + var httpClient = CreateHttpClient(); + var result = default(IHealthCheckResult); + await CheckUrlAsync(httpClient, _urls[0], (_, checkResult) => result = checkResult).ConfigureAwait(false); + return result; + } + + public async Task CheckMultiAsync() + { + var composite = new CompositeHealthCheckResult(PartiallyHealthyStatus); + var httpClient = CreateHttpClient(); + + // REVIEW: Should these be done in parallel? + foreach (var url in _urls) + await CheckUrlAsync(httpClient, url, (name, checkResult) => composite.Add(name, checkResult)).ConfigureAwait(false); + + return composite; + } + + private async Task CheckUrlAsync(HttpClient httpClient, string url, Action adder) + { + var name = $"UrlCheck({url})"; + try + { + var response = await httpClient.GetAsync(url).ConfigureAwait(false); + var result = await _checkFunc(response); + adder(name, result); + } + catch (Exception ex) + { + adder(name, HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}")); + } + } + + private HttpClient CreateHttpClient() + { + var httpClient = GetHttpClient(); + httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true }; + return httpClient; + } + + public static async ValueTask DefaultUrlCheck(HttpResponseMessage response) + { + // REVIEW: Should this be an explicit 200 check, or just an "is success" check? + var status = response.StatusCode == HttpStatusCode.OK ? CheckStatus.Healthy : CheckStatus.Unhealthy; + var data = new Dictionary + { + { "url", response.RequestMessage.RequestUri.ToString() }, + { "status", (int)response.StatusCode }, + { "reason", response.ReasonPhrase }, + { "body", await response.Content?.ReadAsStringAsync() } + }; + return HealthCheckResult.FromStatus(status, $"UrlCheck({response.RequestMessage.RequestUri}): status code {response.StatusCode} ({(int)response.StatusCode})", data); + } + + protected virtual HttpClient GetHttpClient() + => new HttpClient(); + } +} diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Microsoft.Extensions.HealthChecks.csproj b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Microsoft.Extensions.HealthChecks.csproj new file mode 100644 index 000000000..75eef991e --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Microsoft.Extensions.HealthChecks.csproj @@ -0,0 +1,20 @@ + + + + netstandard1.3 + + + + + + + + + + + + + + + + diff --git a/src/BuildingBlocks/HealthChecks/src/common/Guard.cs b/src/BuildingBlocks/HealthChecks/src/common/Guard.cs new file mode 100644 index 000000000..8ed91054e --- /dev/null +++ b/src/BuildingBlocks/HealthChecks/src/common/Guard.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +static class Guard +{ + public static void ArgumentNotNull(string argumentName, object value) + { + if (value == null) + throw new ArgumentNullException(argumentName); + } + + public static void ArgumentNotNullOrEmpty(string argumentName, string value) + { + if (value == null) + throw new ArgumentNullException(argumentName); + if (string.IsNullOrEmpty(value)) + throw new ArgumentException("Value cannot be an empty string", argumentName); + } + + // Use IReadOnlyCollection instead of IEnumerable to discourage double enumeration + public static void ArgumentNotNullOrEmpty(string argumentName, IReadOnlyCollection items) + { + if (items == null) + throw new ArgumentNullException(argumentName); + if (items.Count == 0) + throw new ArgumentException("Collection must contain at least one item", argumentName); + } + + public static void ArgumentNotNullOrWhitespace(string argumentName, string value) + { + if (value == null) + throw new ArgumentNullException(argumentName); + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Value must contain a non-whitespace value", argumentName); + } + + public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage) + { + if (!valid) + throw new ArgumentException(exceptionMessage, argumentName); + } +} diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index bbc596d8f..ce20513cc 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -42,6 +42,9 @@ + + + diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index d2b305ce8..74ab84c18 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -10,6 +10,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API { var host = new WebHostBuilder() .UseKestrel() + .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index db72792bd..057aed0f7 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -14,6 +14,8 @@ using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using System; +using Microsoft.Extensions.HealthChecks; +using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -34,6 +36,12 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + + services.AddHealthChecks(checks => + { + checks.AddValueTaskCheck("Always OK", () => new ValueTask(HealthCheckResult.Healthy("Ok"))); + }); + // Add framework services. services.AddMvc(); services.Configure(Configuration); diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 5cef06749..0176b12a3 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -58,6 +58,9 @@ + + + diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index b2e338b8c..93c151cf2 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -10,6 +10,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API { var host = new WebHostBuilder() .UseKestrel() + .UseHealthChecks("/hc") .UseIISIntegration() .UseContentRoot(Directory.GetCurrentDirectory()) .UseWebRoot("Pics") diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 818855d69..a205f7178 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -9,6 +9,7 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Reflection; @@ -36,6 +37,12 @@ public void ConfigureServices(IServiceCollection services) { + + services.AddHealthChecks(checks => + { + checks.AddUrlCheck(Configuration["ConnectionString"]); + }); + services.AddDbContext(c => { c.UseSqlServer(Configuration["ConnectionString"]); diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index d0102c19f..9595a9e39 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -23,6 +23,9 @@ + + + diff --git a/src/Services/Ordering/Ordering.API/Program.cs b/src/Services/Ordering/Ordering.API/Program.cs index 1a9d705d4..ba92a2da9 100644 --- a/src/Services/Ordering/Ordering.API/Program.cs +++ b/src/Services/Ordering/Ordering.API/Program.cs @@ -10,6 +10,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API { var host = new WebHostBuilder() .UseKestrel() + .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 3f0840b07..7d269b2e3 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -14,6 +14,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using Ordering.Infrastructure; using System; @@ -49,6 +50,12 @@ }).AddControllersAsServices(); //Injecting Controllers themselves thru DI //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services + + services.AddHealthChecks(checks => + { + checks.AddSqlCheck("Ordering_Db", Configuration["ConnectionString"]); + }); + services.AddEntityFrameworkSqlServer() .AddDbContext(options => { diff --git a/src/Web/WebMVC/wwwroot/css/site.min.css b/src/Web/WebMVC/wwwroot/css/site.min.css index f95f6d90e..2a4a53b47 100644 --- a/src/Web/WebMVC/wwwroot/css/site.min.css +++ b/src/Web/WebMVC/wwwroot/css/site.min.css @@ -1 +1 @@ -.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}.esh-app-footer-text{color:#83d01b;line-height:50px;text-align:right;width:100%}@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item--navigable{display:inline-block;cursor:pointer}.esh-pager-item--navigable.is-disabled{opacity:0;pointer-events:none}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 4vw}}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:0;min-width:14rem;right:0;overflow:hidden;padding:.5rem;position:absolute;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:7rem;transition:height .35s}.esh-identity-item{cursor:pointer;display:block;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5) !important;line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-header-back:hover{color:#fff !important;transition:color .35s}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders_new{min-height:80vh}.esh-orders_new-header{background-color:#00a69c;height:4rem}.esh-orders_new-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders_new-back:hover{color:#fff;transition:color .35s}.esh-orders_new-section{padding:1rem 0}.esh-orders_new-section--right{text-align:right}.esh-orders_new-placeOrder{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-orders_new-placeOrder:hover{background-color:#4a760f;transition:all .35s}.esh-orders_new-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_new-title{font-size:1.25rem;text-transform:uppercase}.esh-orders_new-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_new-items--border:last-of-type{border-color:transparent}.esh-orders_new-item{font-size:1rem;font-weight:300}.esh-orders_new-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_new-item--middle{line-height:1rem}}.esh-orders_new-item--mark{color:#83d01b}.esh-orders_new-image{height:8rem}.esh-orders_detail{min-height:80vh}.esh-orders_detail-section{padding:1rem 0}.esh-orders_detail-section--right{text-align:right}.esh-orders_detail-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_detail-title{text-transform:uppercase}.esh-orders_detail-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_detail-items--border:last-of-type{border-color:transparent}.esh-orders_detail-item{font-size:1rem;font-weight:300}.esh-orders_detail-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_detail-item--middle{line-height:1rem}}.esh-orders_detail-item--mark{color:#83d01b}.esh-orders_detail-image{height:8rem}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;min-width:140px;-webkit-appearance:none}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-top:.65rem;margin-left:.5rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");height:7px;content:'';position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;transform:translateY(.5rem);padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{text-align:center;margin-bottom:1.5rem;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:none;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{text-align:center;font-weight:900;font-size:28px}.esh-catalog-price::before{content:'$'}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{border:none;border-radius:0;background-color:#83d01b;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s} \ No newline at end of file +.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}.esh-app-footer-text{color:#83d01b;line-height:50px;text-align:right;width:100%}@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item--navigable{display:inline-block;cursor:pointer}.esh-pager-item--navigable.is-disabled{opacity:0;pointer-events:none}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 4vw}}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:0;min-width:14rem;right:0;overflow:hidden;padding:.5rem;position:absolute;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:7rem;transition:height .35s}.esh-identity-item{cursor:pointer;display:block;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5) !important;line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-header-back:hover{color:#fff !important;transition:color .35s}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders_new{min-height:80vh}.esh-orders_new-header{background-color:#00a69c;height:4rem}.esh-orders_new-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders_new-back:hover{color:#fff;transition:color .35s}.esh-orders_new-section{padding:1rem 0}.esh-orders_new-section--right{text-align:right}.esh-orders_new-placeOrder{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-orders_new-placeOrder:hover{background-color:#4a760f;transition:all .35s}.esh-orders_new-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_new-title{font-size:1.25rem;text-transform:uppercase}.esh-orders_new-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_new-items--border:last-of-type{border-color:transparent}.esh-orders_new-item{font-size:1rem;font-weight:300}.esh-orders_new-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_new-item--middle{line-height:1rem}}.esh-orders_new-item--mark{color:#83d01b}.esh-orders_new-image{height:8rem}.esh-orders_detail{min-height:80vh}.esh-orders_detail-section{padding:1rem 0}.esh-orders_detail-section--right{text-align:right}.esh-orders_detail-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_detail-title{text-transform:uppercase}.esh-orders_detail-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_detail-items--border:last-of-type{border-color:transparent}.esh-orders_detail-item{font-size:1rem;font-weight:300}.esh-orders_detail-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_detail-item--middle{line-height:1rem}}.esh-orders_detail-item--mark{color:#83d01b}.esh-orders_detail-image{height:8rem}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;min-width:140px;-webkit-appearance:none}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-top:.65rem;margin-left:.5rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");height:7px;content:'';position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;transform:translateY(.5rem);padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{text-align:center;margin-bottom:1.5rem;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:none;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{text-align:center;font-weight:900;font-size:28px}.esh-catalog-price::before{content:'$'}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{border:none;border-radius:0;background-color:#83d01b;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basket-margin12{margin-left:12px}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s} \ No newline at end of file diff --git a/src/Web/WebStatus/.bowerrc b/src/Web/WebStatus/.bowerrc new file mode 100644 index 000000000..6406626ab --- /dev/null +++ b/src/Web/WebStatus/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "wwwroot/lib" +} diff --git a/src/Web/WebStatus/.dockerignore b/src/Web/WebStatus/.dockerignore new file mode 100644 index 000000000..d8f8175f6 --- /dev/null +++ b/src/Web/WebStatus/.dockerignore @@ -0,0 +1,3 @@ +* +!obj/Docker/publish/* +!obj/Docker/empty/ diff --git a/src/Web/WebStatus/Controllers/HomeController.cs b/src/Web/WebStatus/Controllers/HomeController.cs new file mode 100644 index 000000000..f75725502 --- /dev/null +++ b/src/Web/WebStatus/Controllers/HomeController.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.HealthChecks; +using WebStatus.Viewmodels; + +namespace WebStatus.Controllers +{ + public class HomeController : Controller + { + private readonly IHealthCheckService _healthCheckSvc; + public HomeController(IHealthCheckService checkSvc) + { + _healthCheckSvc = checkSvc; + } + + public async Task Index() + { + var result = await _healthCheckSvc.CheckHealthAsync(); + + var data = new HealthStatusViewModel(result.CheckStatus); + + foreach (var checkResult in result.Results) + { + data.AddResult(checkResult.Value); + } + + return View(data); + } + + public IActionResult Error() + { + return View(); + } + } +} diff --git a/src/Web/WebStatus/Dockerfile b/src/Web/WebStatus/Dockerfile new file mode 100644 index 000000000..9bfb04192 --- /dev/null +++ b/src/Web/WebStatus/Dockerfile @@ -0,0 +1,6 @@ +FROM microsoft/aspnetcore:1.1 +ARG source +WORKDIR /app +EXPOSE 80 +COPY ${source:-obj/Docker/publish} . +ENTRYPOINT ["dotnet", "WebStatus.dll"] diff --git a/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs b/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs new file mode 100644 index 000000000..369f59722 --- /dev/null +++ b/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.HealthChecks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebStatus.Extensions +{ + public static class HealthCheckBuilderExtensions + { + public static HealthCheckBuilder AddUrlCheckIfNotNull(this HealthCheckBuilder builder, string url) + { + if (!string.IsNullOrEmpty(url)) + { + builder.AddUrlCheck(url); + } + + return builder; + } + + } +} diff --git a/src/Web/WebStatus/Program.cs b/src/Web/WebStatus/Program.cs new file mode 100644 index 000000000..00d35edfb --- /dev/null +++ b/src/Web/WebStatus/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; + +namespace WebStatus +{ + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .UseApplicationInsights() + .Build(); + + host.Run(); + } + } +} diff --git a/src/Web/WebStatus/Properties/launchSettings.json b/src/Web/WebStatus/Properties/launchSettings.json new file mode 100644 index 000000000..554e3f093 --- /dev/null +++ b/src/Web/WebStatus/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5107/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WebStatus": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:50880" + } + } +} \ No newline at end of file diff --git a/src/Web/WebStatus/Startup.cs b/src/Web/WebStatus/Startup.cs new file mode 100644 index 000000000..6bd5a119d --- /dev/null +++ b/src/Web/WebStatus/Startup.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +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 WebStatus.Extensions; + +namespace WebStatus +{ + public class Startup + { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddOptions(); + // Add framework services. + services.AddHealthChecks(checks => + { + checks.AddUrlCheckIfNotNull(Configuration["OrderingUrl"]); + checks.AddUrlCheckIfNotNull(Configuration["BasketUrl"]); + checks.AddUrlCheckIfNotNull(Configuration["CatalogUrl"]); + checks.AddUrlCheckIfNotNull(Configuration["mvc"]); + checks.AddUrlCheckIfNotNull(Configuration["spa"]); + }); + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseBrowserLink(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + } + + app.UseStaticFiles(); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} diff --git a/src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs b/src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs new file mode 100644 index 000000000..f01d3e616 --- /dev/null +++ b/src/Web/WebStatus/Viewmodels/HealthStatusViewModel.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.HealthChecks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebStatus.Viewmodels +{ + public class HealthStatusViewModel + { + private readonly CheckStatus _overall; + private readonly List _results; + + public CheckStatus OverallStatus => _overall; + public IEnumerable Results => _results; + private HealthStatusViewModel() => _results = new List(); + public HealthStatusViewModel(CheckStatus overall) : this() => _overall = overall; + public void AddResult(IHealthCheckResult result) => _results.Add(result); + + + } +} diff --git a/src/Web/WebStatus/Views/Home/Index.cshtml b/src/Web/WebStatus/Views/Home/Index.cshtml new file mode 100644 index 000000000..d81f09bd8 --- /dev/null +++ b/src/Web/WebStatus/Views/Home/Index.cshtml @@ -0,0 +1,20 @@ +@model WebStatus.Viewmodels.HealthStatusViewModel + +@{ + ViewData["Title"] = "System Status"; +} + + +
+
+

Overall Status: @Model.OverallStatus

+
+
+ +@foreach (var result in Model.Results) +{ +
+
@result.Description
+
@result.CheckStatus
+
+} diff --git a/src/Web/WebStatus/Views/Shared/Error.cshtml b/src/Web/WebStatus/Views/Shared/Error.cshtml new file mode 100644 index 000000000..e514139c4 --- /dev/null +++ b/src/Web/WebStatus/Views/Shared/Error.cshtml @@ -0,0 +1,14 @@ +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. +

diff --git a/src/Web/WebStatus/Views/Shared/_Layout.cshtml b/src/Web/WebStatus/Views/Shared/_Layout.cshtml new file mode 100644 index 000000000..e9b750b37 --- /dev/null +++ b/src/Web/WebStatus/Views/Shared/_Layout.cshtml @@ -0,0 +1,73 @@ +@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet + + + + + + @ViewData["Title"] - WebStatus + + + + + + + + + + @Html.Raw(JavaScriptSnippet.FullScript) + + + +
+ @RenderBody() +
+
+

© 2017 - WebStatus

+
+
+ + + + + + + + + + + + + @RenderSection("Scripts", required: false) + + diff --git a/src/Web/WebStatus/Views/Shared/_ValidationScriptsPartial.cshtml b/src/Web/WebStatus/Views/Shared/_ValidationScriptsPartial.cshtml new file mode 100644 index 000000000..27e0ea7ce --- /dev/null +++ b/src/Web/WebStatus/Views/Shared/_ValidationScriptsPartial.cshtml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/src/Web/WebStatus/Views/_ViewImports.cshtml b/src/Web/WebStatus/Views/_ViewImports.cshtml new file mode 100644 index 000000000..6a5648da4 --- /dev/null +++ b/src/Web/WebStatus/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using WebStatus +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/Web/WebStatus/Views/_ViewStart.cshtml b/src/Web/WebStatus/Views/_ViewStart.cshtml new file mode 100644 index 000000000..a5f10045d --- /dev/null +++ b/src/Web/WebStatus/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/src/Web/WebStatus/WebStatus.csproj b/src/Web/WebStatus/WebStatus.csproj new file mode 100644 index 000000000..e5b1a0e77 --- /dev/null +++ b/src/Web/WebStatus/WebStatus.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp1.1 + + + + $(PackageTargetFallback);portable-net45+win8+wp8+wpa81; + ..\..\..\docker-compose.dcproj + + + + + + + + + + + + + + + + + + + diff --git a/src/Web/WebStatus/appsettings.Development.json b/src/Web/WebStatus/appsettings.Development.json new file mode 100644 index 000000000..fa8ce71a9 --- /dev/null +++ b/src/Web/WebStatus/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Web/WebStatus/appsettings.json b/src/Web/WebStatus/appsettings.json new file mode 100644 index 000000000..f7a4c68dd --- /dev/null +++ b/src/Web/WebStatus/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning" + } + }, + "OrderingUrl": "http://localhost:5102/hc", + "BasketUrl": "http://localhost:5103/hc", + "CatalogUrl": "http://localhost:5101/hc" +} diff --git a/src/Web/WebStatus/bower.json b/src/Web/WebStatus/bower.json new file mode 100644 index 000000000..b07e3cc5a --- /dev/null +++ b/src/Web/WebStatus/bower.json @@ -0,0 +1,10 @@ +{ + "name": "asp.net", + "private": true, + "dependencies": { + "bootstrap": "3.3.7", + "jquery": "2.2.0", + "jquery-validation": "1.14.0", + "jquery-validation-unobtrusive": "3.2.6" + } +} diff --git a/src/Web/WebStatus/bundleconfig.json b/src/Web/WebStatus/bundleconfig.json new file mode 100644 index 000000000..6d3f9a57a --- /dev/null +++ b/src/Web/WebStatus/bundleconfig.json @@ -0,0 +1,24 @@ +// Configure bundling and minification for the project. +// More info at https://go.microsoft.com/fwlink/?LinkId=808241 +[ + { + "outputFileName": "wwwroot/css/site.min.css", + // An array of relative input file paths. Globbing patterns supported + "inputFiles": [ + "wwwroot/css/site.css" + ] + }, + { + "outputFileName": "wwwroot/js/site.min.js", + "inputFiles": [ + "wwwroot/js/site.js" + ], + // Optionally specify minification options + "minify": { + "enabled": true, + "renameLocals": true + }, + // Optionally generate .map file + "sourceMap": false + } +] diff --git a/src/Web/WebStatus/wwwroot/css/site.css b/src/Web/WebStatus/wwwroot/css/site.css new file mode 100644 index 000000000..e31abdefd --- /dev/null +++ b/src/Web/WebStatus/wwwroot/css/site.css @@ -0,0 +1,37 @@ +body { + padding-top: 50px; + padding-bottom: 20px; +} + +/* Wrapping element */ +/* Set some basic padding to keep content from hitting the edges */ +.body-content { + padding-left: 15px; + padding-right: 15px; +} + +/* Set widths on the form inputs since otherwise they're 100% wide */ +input, +select, +textarea { + max-width: 280px; +} + +/* Carousel */ +.carousel-caption p { + font-size: 20px; + line-height: 1.4; +} + +/* Make .svg files in the carousel display properly in older browsers */ +.carousel-inner .item img[src$=".svg"] { + width: 100%; +} + +/* Hide/rearrange for smaller screens */ +@media screen and (max-width: 767px) { + /* Hide captions */ + .carousel-caption { + display: none; + } +} diff --git a/src/Web/WebStatus/wwwroot/css/site.min.css b/src/Web/WebStatus/wwwroot/css/site.min.css new file mode 100644 index 000000000..3beb45f52 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/css/site.min.css @@ -0,0 +1 @@ +body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width:767px){.carousel-caption{display:none}} \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/favicon.ico b/src/Web/WebStatus/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 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 \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/images/banner2.svg b/src/Web/WebStatus/wwwroot/images/banner2.svg new file mode 100644 index 000000000..9679c604d --- /dev/null +++ b/src/Web/WebStatus/wwwroot/images/banner2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/images/banner3.svg b/src/Web/WebStatus/wwwroot/images/banner3.svg new file mode 100644 index 000000000..9be2c2503 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/images/banner3.svg @@ -0,0 +1 @@ +banner3b \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/images/banner4.svg b/src/Web/WebStatus/wwwroot/images/banner4.svg new file mode 100644 index 000000000..38b3d7cd1 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/images/banner4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/js/site.js b/src/Web/WebStatus/wwwroot/js/site.js new file mode 100644 index 000000000..82ecce7b4 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/js/site.js @@ -0,0 +1 @@ +// Write your Javascript code. diff --git a/src/Web/WebStatus/wwwroot/js/site.min.js b/src/Web/WebStatus/wwwroot/js/site.min.js new file mode 100644 index 000000000..e69de29bb diff --git a/src/Web/WebStatus/wwwroot/lib/bootstrap/.bower.json b/src/Web/WebStatus/wwwroot/lib/bootstrap/.bower.json new file mode 100644 index 000000000..1e99b6299 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/lib/bootstrap/.bower.json @@ -0,0 +1,45 @@ +{ + "name": "bootstrap", + "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", + "keywords": [ + "css", + "js", + "less", + "mobile-first", + "responsive", + "front-end", + "framework", + "web" + ], + "homepage": "http://getbootstrap.com", + "license": "MIT", + "moduleType": "globals", + "main": [ + "less/bootstrap.less", + "dist/js/bootstrap.js" + ], + "ignore": [ + "/.*", + "_config.yml", + "CNAME", + "composer.json", + "CONTRIBUTING.md", + "docs", + "js/tests", + "test-infra" + ], + "dependencies": { + "jquery": "1.9.1 - 3" + }, + "version": "3.3.7", + "_release": "3.3.7", + "_resolution": { + "type": "version", + "tag": "v3.3.7", + "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" + }, + "_source": "https://github.com/twbs/bootstrap.git", + "_target": "v3.3.7", + "_originalSource": "bootstrap", + "_direct": true +} \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/lib/bootstrap/LICENSE b/src/Web/WebStatus/wwwroot/lib/bootstrap/LICENSE new file mode 100644 index 000000000..7a300022c --- /dev/null +++ b/src/Web/WebStatus/wwwroot/lib/bootstrap/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011-2016 Twitter, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css new file mode 100644 index 000000000..31d888266 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css @@ -0,0 +1,587 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); +} +.btn-default:active, +.btn-primary:active, +.btn-success:active, +.btn-info:active, +.btn-warning:active, +.btn-danger:active, +.btn-default.active, +.btn-primary.active, +.btn-success.active, +.btn-info.active, +.btn-warning.active, +.btn-danger.active { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-default.disabled, +.btn-primary.disabled, +.btn-success.disabled, +.btn-info.disabled, +.btn-warning.disabled, +.btn-danger.disabled, +.btn-default[disabled], +.btn-primary[disabled], +.btn-success[disabled], +.btn-info[disabled], +.btn-warning[disabled], +.btn-danger[disabled], +fieldset[disabled] .btn-default, +fieldset[disabled] .btn-primary, +fieldset[disabled] .btn-success, +fieldset[disabled] .btn-info, +fieldset[disabled] .btn-warning, +fieldset[disabled] .btn-danger { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-default .badge, +.btn-primary .badge, +.btn-success .badge, +.btn-info .badge, +.btn-warning .badge, +.btn-danger .badge { + text-shadow: none; +} +.btn:active, +.btn.active { + background-image: none; +} +.btn-default { + text-shadow: 0 1px 0 #fff; + background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); + background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); + background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #dbdbdb; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus { + background-color: #e0e0e0; + background-position: 0 -15px; +} +.btn-default:active, +.btn-default.active { + background-color: #e0e0e0; + border-color: #dbdbdb; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #e0e0e0; + background-image: none; +} +.btn-primary { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); + background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #245580; +} +.btn-primary:hover, +.btn-primary:focus { + background-color: #265a88; + background-position: 0 -15px; +} +.btn-primary:active, +.btn-primary.active { + background-color: #265a88; + border-color: #245580; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #265a88; + background-image: none; +} +.btn-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); + background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #3e8f3e; +} +.btn-success:hover, +.btn-success:focus { + background-color: #419641; + background-position: 0 -15px; +} +.btn-success:active, +.btn-success.active { + background-color: #419641; + border-color: #3e8f3e; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #419641; + background-image: none; +} +.btn-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); + background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #28a4c9; +} +.btn-info:hover, +.btn-info:focus { + background-color: #2aabd2; + background-position: 0 -15px; +} +.btn-info:active, +.btn-info.active { + background-color: #2aabd2; + border-color: #28a4c9; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #2aabd2; + background-image: none; +} +.btn-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #e38d13; +} +.btn-warning:hover, +.btn-warning:focus { + background-color: #eb9316; + background-position: 0 -15px; +} +.btn-warning:active, +.btn-warning.active { + background-color: #eb9316; + border-color: #e38d13; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #eb9316; + background-image: none; +} +.btn-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); + background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #b92c28; +} +.btn-danger:hover, +.btn-danger:focus { + background-color: #c12e2a; + background-position: 0 -15px; +} +.btn-danger:active, +.btn-danger.active { + background-color: #c12e2a; + border-color: #b92c28; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #c12e2a; + background-image: none; +} +.thumbnail, +.img-thumbnail { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background-color: #e8e8e8; + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + background-color: #2e6da4; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; +} +.navbar-default { + background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); + background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); + background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); + background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, .25); +} +.navbar-inverse { + background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); + background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-radius: 4px; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); + background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); + background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); +} +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); +} +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} +@media (max-width: 767px) { + .navbar .navbar-nav .open .dropdown-menu > .active > a, + .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; + } +} +.alert { + text-shadow: 0 1px 0 rgba(255, 255, 255, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); +} +.alert-success { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); + background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); + background-repeat: repeat-x; + border-color: #b2dba1; +} +.alert-info { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); + background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); + background-repeat: repeat-x; + border-color: #9acfea; +} +.alert-warning { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); + background-repeat: repeat-x; + border-color: #f5e79e; +} +.alert-danger { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); + background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); + background-repeat: repeat-x; + border-color: #dca7a7; +} +.progress { + background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); + background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); + background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); + background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); + background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-striped { + 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); +} +.list-group { + border-radius: 4px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 #286090; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); + background-repeat: repeat-x; + border-color: #2b669a; +} +.list-group-item.active .badge, +.list-group-item.active:hover .badge, +.list-group-item.active:focus .badge { + text-shadow: none; +} +.panel { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); + box-shadow: 0 1px 2px rgba(0, 0, 0, .05); +} +.panel-default > .panel-heading { + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.panel-primary > .panel-heading { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; +} +.panel-success > .panel-heading { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); + background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); + background-repeat: repeat-x; +} +.panel-info > .panel-heading { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); + background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); + background-repeat: repeat-x; +} +.panel-warning > .panel-heading { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); + background-repeat: repeat-x; +} +.panel-danger > .panel-heading { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); + background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); + background-repeat: repeat-x; +} +.well { + background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); + background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; + border-color: #dcdcdc; + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); +} +/*# sourceMappingURL=bootstrap-theme.css.map */ diff --git a/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map new file mode 100644 index 000000000..d876f60fb --- /dev/null +++ b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap-theme.css","less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"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","file":"bootstrap-theme.css","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default.disabled,\n.btn-primary.disabled,\n.btn-success.disabled,\n.btn-info.disabled,\n.btn-warning.disabled,\n.btn-danger.disabled,\n.btn-default[disabled],\n.btn-primary[disabled],\n.btn-success[disabled],\n.btn-info[disabled],\n.btn-warning[disabled],\n.btn-danger[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-primary,\nfieldset[disabled] .btn-success,\nfieldset[disabled] .btn-info,\nfieldset[disabled] .btn-warning,\nfieldset[disabled] .btn-danger {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css new file mode 100644 index 000000000..5e3940195 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.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:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.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:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.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:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.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:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.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:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.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:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{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)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} +/*# sourceMappingURL=bootstrap-theme.min.css.map */ \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map new file mode 100644 index 000000000..94813e900 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file diff --git a/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.css b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.css new file mode 100644 index 000000000..6167622ce --- /dev/null +++ b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.css @@ -0,0 +1,6757 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 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: bold; +} +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 #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +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 { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !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^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + 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 th, + .table-bordered td { + 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: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur: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; +} +*:before, +*:after { + -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; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > 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, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} +h1, +.h1, +h2, +.h2, +h3, +.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, +.h4, +h5, +.h5, +h6, +.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:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + 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; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +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[title], +abbr[data-original-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 p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} +blockquote footer:before, +blockquote small: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 footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small: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: bold; + -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-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + 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-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + 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-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + 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-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + 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 > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + 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 > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + 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 > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + 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 > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + 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: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + 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="radio"]:focus, +input[type="checkbox"]:focus { + 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::-ms-expand { + background-color: transparent; + border: 0; +} +.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[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .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"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .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"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox 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; +} +textarea.input-sm, +select[multiple].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 textarea.form-control, +.form-group-sm select[multiple].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; +} +textarea.input-lg, +select[multiple].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 textarea.form-control, +.form-group-lg select[multiple].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; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-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 .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-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 .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-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 .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + 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: 11px; + 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: normal; + 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:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + 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:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + 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:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + 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:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + 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:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + 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:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + 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:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + 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:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + 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:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + 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:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + 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:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + 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:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + 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:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + 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:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + 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="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].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; +} +.dropup, +.dropdown { + 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: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + 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 > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + 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-left-radius: 4px; + 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-right-radius: 4px; + 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="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + 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 .form-control:focus { + z-index: 3; +} +.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; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].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; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control: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: normal; + 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="radio"], +.input-group-addon input[type="checkbox"] { + 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:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + 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:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + 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:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + 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:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + 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:hover, +.nav-tabs > li.active > a:focus { + 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:hover, +.nav-tabs.nav-justified > .active > a:focus { + 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:hover, + .nav-tabs.nav-justified > .active > a:focus { + 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:hover, +.nav-pills > li.active > a:focus { + 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:hover, +.nav-tabs-justified > .active > a:focus { + 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:hover, + .nav-tabs-justified > .active > a:focus { + 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-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + 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-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + 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:hover, +.navbar-brand:focus { + 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 > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + 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 .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + 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:hover, +.navbar-default .navbar-brand:focus { + 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:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + 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:hover, +.navbar-default .navbar-nav > .open > a:focus { + 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:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + 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:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + 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:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + 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:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + 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:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + 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:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + 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:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + 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:hover, +.pager li > a:focus { + 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:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + 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]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + 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-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + 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 { + padding-right: 15px; + padding-left: 15px; + 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 > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + 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: bold; +} +.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-striped .progress-bar, +.progress-bar-striped { + 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.active .progress-bar, +.progress-bar.active { + -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-left, +.media-right, +.media-body { + 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:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + 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:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .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:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + 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:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + 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:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + 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:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + 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 > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.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 > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead: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-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .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 td:first-child, +.panel > .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 td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th: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 th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .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 td:last-child, +.panel > .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 td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th: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 th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .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 td:first-child, +.panel > .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 td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .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 td:last-child, +.panel > .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 td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .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 th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.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 > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + 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 iframe, +.embed-responsive embed, +.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: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + 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 { + 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: normal; + 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: normal; + 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 > img, +.carousel-inner > .item > a > 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.next, + .carousel-inner > .item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + 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); + background-color: rgba(0, 0, 0, 0); + 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:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + margin-top: -10px; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + 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-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer: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-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-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; + } + th.visible-xs, + td.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; + } + th.visible-sm, + td.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; + } + th.visible-md, + td.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; + } + th.visible-lg, + td.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; + } + th.visible-print, + td.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; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map new file mode 100644 index 000000000..f010c82d1 --- /dev/null +++ b/src/Web/WebStatus/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/mixins/reset-text.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"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AW,iBAAA;CHsR9C;AGrRmC;EAAW,iBAAA;CHwR9C;AGvRmC;EAAW,iBAAA;CH0R9C;AGzRmC;EAAW,iBAAA;CH4R9C;AG3RmC;EAAW,iBAAA;CH8R9C;AG7RmC;EAAW,iBAAA;CHgS9C;AG/RmC;EAAW,iBAAA;CHkS9C;AGjSmC;EAAW,iBAAA;CHoS9C;AGnSmC;EAAW,iBAAA;CHsS9C;AGrSmC;EAAW,iBAAA;CHwS9C;AGvSmC;EAAW,iBAAA;CH0S9C;AGzSmC;EAAW,iBAAA;CH4S9C;AG3SmC;EAAW,iBAAA;CH8S9C;AG7SmC;EAAW,iBAAA;CHgT9C;AG/SmC;EAAW,iBAAA;CHkT9C;AGjTmC;EAAW,iBAAA;CHoT9C;AGnTmC;EAAW,iBAAA;CHsT9C;AGrTmC;EAAW,iBAAA;CHwT9C;AGvTmC;EAAW,iBAAA;CH0T9C;AGzTmC;EAAW,iBAAA;CH4T9C;AG3TmC;EAAW,iBAAA;CH8T9C;AG7TmC;EAAW,iBAAA;CHgU9C;AG/TmC;EAAW,iBAAA;CHkU9C;AGjUmC;EAAW,iBAAA;CHoU9C;AGnUmC;EAAW,iBAAA;CHsU9C;AGrUmC;EAAW,iBAAA;CHwU9C;AGvUmC;EAAW,iBAAA;CH0U9C;AGzUmC;EAAW,iBAAA;CH4U9C;AG3UmC;EAAW,iBAAA;CH8U9C;AG7UmC;EAAW,iBAAA;CHgV9C;AG/UmC;EAAW,iBAAA;CHkV9C;AGjVmC;EAAW,iBAAA;CHoV9C;AGnVmC;EAAW,iBAAA;CHsV9C;AGrVmC;EAAW,iBAAA;CHwV9C;AGvVmC;EAAW,iBAAA;CH0V9C;AGzVmC;EAAW,iBAAA;CH4V9C;AG3VmC;EAAW,iBAAA;CH8V9C;AG7VmC;EAAW,iBAAA;CHgW9C;AG/VmC;EAAW,iBAAA;CHkW9C;AGjWmC;EAAW,iBAAA;CHoW9C;AGnWmC;EAAW,iBAAA;CHsW9C;AGrWmC;EAAW,iBAAA;CHwW9C;AGvWmC;EAAW,iBAAA;CH0W9C;AGzWmC;EAAW,iBAAA;CH4W9C;AG3WmC;EAAW,iBAAA;CH8W9C;AG7WmC;EAAW,iBAAA;CHgX9C;AG/WmC;EAAW,iBAAA;CHkX9C;AGjXmC;EAAW,iBAAA;CHoX9C;AGnXmC;EAAW,iBAAA;CHsX9C;AGrXmC;EAAW,iBAAA;CHwX9C;AGvXmC;EAAW,iBAAA;CH0X9C;AGzXmC;EAAW,iBAAA;CH4X9C;AG3XmC;EAAW,iBAAA;CH8X9C;AG7XmC;EAAW,iBAAA;CHgY9C;AG/XmC;EAAW,iBAAA;CHkY9C;AGjYmC;EAAW,iBAAA;CHoY9C;AGnYmC;EAAW,iBAAA;CHsY9C;AGrYmC;EAAW,iBAAA;CHwY9C;AGvYmC;EAAW,iBAAA;CH0Y9C;AGzYmC;EAAW,iBAAA;CH4Y9C;AG3YmC;EAAW,iBAAA;CH8Y9C;AG7YmC;EAAW,iBAAA;CHgZ9C;AG/YmC;EAAW,iBAAA;CHkZ9C;AGjZmC;EAAW,iBAAA;CHoZ9C;AGnZmC;EAAW,iBAAA;CHsZ9C;AGrZmC;EAAW,iBAAA;CHwZ9C;AGvZmC;EAAW,iBAAA;CH0Z9C;AGzZmC;EAAW,iBAAA;CH4Z9C;AG3ZmC;EAAW,iBAAA;CH8Z9C;AG7ZmC;EAAW,iBAAA;CHga9C;AG/ZmC;EAAW,iBAAA;CHka9C;AGjamC;EAAW,iBAAA;CHoa9C;AGnamC;EAAW,iBAAA;CHsa9C;AGramC;EAAW,iBAAA;CHwa9C;AGvamC;EAAW,iBAAA;CH0a9C;AGzamC;EAAW,iBAAA;CH4a9C;AG3amC;EAAW,iBAAA;CH8a9C;AG7amC;EAAW,iBAAA;CHgb9C;AG/amC;EAAW,iBAAA;CHkb9C;AGjbmC;EAAW,iBAAA;CHob9C;AGnbmC;EAAW,iBAAA;CHsb9C;AGrbmC;EAAW,iBAAA;CHwb9C;AGvbmC;EAAW,iBAAA;CH0b9C;AGzbmC;EAAW,iBAAA;CH4b9C;AG3bmC;EAAW,iBAAA;CH8b9C;AG7bmC;EAAW,iBAAA;CHgc9C;AG/bmC;EAAW,iBAAA;CHkc9C;AGjcmC;EAAW,iBAAA;CHoc9C;AGncmC;EAAW,iBAAA;CHsc9C;AGrcmC;EAAW,iBAAA;CHwc9C;AGvcmC;EAAW,iBAAA;CH0c9C;AGzcmC;EAAW,iBAAA;CH4c9C;AG3cmC;EAAW,iBAAA;CH8c9C;AG7cmC;EAAW,iBAAA;CHgd9C;AG/cmC;EAAW,iBAAA;CHkd9C;AGjdmC;EAAW,iBAAA;CHod9C;AGndmC;EAAW,iBAAA;CHsd9C;AGrdmC;EAAW,iBAAA;CHwd9C;AGvdmC;EAAW,iBAAA;CH0d9C;AGzdmC;EAAW,iBAAA;CH4d9C;AG3dmC;EAAW,iBAAA;CH8d9C;AG7dmC;EAAW,iBAAA;CHge9C;AG/dmC;EAAW,iBAAA;CHke9C;AGjemC;EAAW,iBAAA;CHoe9C;AGnemC;EAAW,iBAAA;CHse9C;AGremC;EAAW,iBAAA;CHwe9C;AGvemC;EAAW,iBAAA;CH0e9C;AGzemC;EAAW,iBAAA;CH4e9C;AG3emC;EAAW,iBAAA;CH8e9C;AG7emC;EAAW,iBAAA;CHgf9C;AG/emC;EAAW,iBAAA;CHkf9C;AGjfmC;EAAW,iBAAA;CHof9C;AGnfmC;EAAW,iBAAA;CHsf9C;AGrfmC;EAAW,iBAAA;CHwf9C;AGvfmC;EAAW,iBAAA;CH0f9C;AGzfmC;EAAW,iBAAA;CH4f9C;AG3fmC;EAAW,iBAAA;CH8f9C;AG7fmC;EAAW,iBAAA;CHggB9C;AG/fmC;EAAW,iBAAA;CHkgB9C;AGjgBmC;EAAW,iBAAA;CHogB9C;AGngBmC;EAAW,iBAAA;CHsgB9C;AGrgBmC;EAAW,iBAAA;CHwgB9C;AGvgBmC;EAAW,iBAAA;CH0gB9C;AGzgBmC;EAAW,iBAAA;CH4gB9C;AG3gBmC;EAAW,iBAAA;CH8gB9C;AG7gBmC;EAAW,iBAAA;CHghB9C;AG/gBmC;EAAW,iBAAA;CHkhB9C;AGjhBmC;EAAW,iBAAA;CHohB9C;AGnhBmC;EAAW,iBAAA;CHshB9C;AGrhBmC;EAAW,iBAAA;CHwhB9C;AGvhBmC;EAAW,iBAAA;CH0hB9C;AGzhBmC;EAAW,iBAAA;CH4hB9C;AG3hBmC;EAAW,iBAAA;CH8hB9C;AG7hBmC;EAAW,iBAAA;CHgiB9C;AG/hBmC;EAAW,iBAAA;CHkiB9C;AGjiBmC;EAAW,iBAAA;CHoiB9C;AGniBmC;EAAW,iBAAA;CHsiB9C;AGriBmC;EAAW,iBAAA;CHwiB9C;AGviBmC;EAAW,iBAAA;CH0iB9C;AGziBmC;EAAW,iBAAA;CH4iB9C;AG3iBmC;EAAW,iBAAA;CH8iB9C;AG7iBmC;EAAW,iBAAA;CHgjB9C;AG/iBmC;EAAW,iBAAA;CHkjB9C;AGjjBmC;EAAW,iBAAA;CHojB9C;AGnjBmC;EAAW,iBAAA;CHsjB9C;AGrjBmC;EAAW,iBAAA;CHwjB9C;AGvjBmC;EAAW,iBAAA;CH0jB9C;AGzjBmC;EAAW,iBAAA;CH4jB9C;AG3jBmC;EAAW,iBAAA;CH8jB9C;AG7jBmC;EAAW,iBAAA;CHgkB9C;AG/jBmC;EAAW,iBAAA;CHkkB9C;AGjkBmC;EAAW,iBAAA;CHokB9C;AGnkBmC;EAAW,iBAAA;CHskB9C;AGrkBmC;EAAW,iBAAA;CHwkB9C;AGvkBmC;EAAW,iBAAA;CH0kB9C;AGzkBmC;EAAW,iBAAA;CH4kB9C;AG3kBmC;EAAW,iBAAA;CH8kB9C;AG7kBmC;EAAW,iBAAA;CHglB9C;AG/kBmC;EAAW,iBAAA;CHklB9C;AGjlBmC;EAAW,iBAAA;CHolB9C;AGnlBmC;EAAW,iBAAA;CHslB9C;AGrlBmC;EAAW,iBAAA;CHwlB9C;AGvlBmC;EAAW,iBAAA;CH0lB9C;AGzlBmC;EAAW,iBAAA;CH4lB9C;AG3lBmC;EAAW,iBAAA;CH8lB9C;AG7lBmC;EAAW,iBAAA;CHgmB9C;AG/lBmC;EAAW,iBAAA;CHkmB9C;AGjmBmC;EAAW,iBAAA;CHomB9C;AGnmBmC;EAAW,iBAAA;CHsmB9C;AGrmBmC;EAAW,iBAAA;CHwmB9C;AGvmBmC;EAAW,iBAAA;CH0mB9C;AGzmBmC;EAAW,iBAAA;CH4mB9C;AG3mBmC;EAAW,iBAAA;CH8mB9C;AG7mBmC;EAAW,iBAAA;CHgnB9C;AG/mBmC;EAAW,iBAAA;CHknB9C;AGjnBmC;EAAW,iBAAA;CHonB9C;AGnnBmC;EAAW,iBAAA;CHsnB9C;AGrnBmC;EAAW,iBAAA;CHwnB9C;AGvnBmC;EAAW,iBAAA;CH0nB9C;AGznBmC;EAAW,iBAAA;CH4nB9C;AG3nBmC;EAAW,iBAAA;CH8nB9C;AG7nBmC;EAAW,iBAAA;CHgoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AGvoBmC;EAAW,iBAAA;CH0oB9C;AGzoBmC;EAAW,iBAAA;CH4oB9C;AG3oBmC;EAAW,iBAAA;CH8oB9C;AG7oBmC;EAAW,iBAAA;CHgpB9C;AG/oBmC;EAAW,iBAAA;CHkpB9C;AGjpBmC;EAAW,iBAAA;CHopB9C;AGnpBmC;EAAW,iBAAA;CHspB9C;AGrpBmC;EAAW,iBAAA;CHwpB9C;AGvpBmC;EAAW,iBAAA;CH0pB9C;AGzpBmC;EAAW,iBAAA;CH4pB9C;AG3pBmC;EAAW,iBAAA;CH8pB9C;AG7pBmC;EAAW,iBAAA;CHgqB9C;AG/pBmC;EAAW,iBAAA;CHkqB9C;AGjqBmC;EAAW,iBAAA;CHoqB9C;AGnqBmC;EAAW,iBAAA;CHsqB9C;AGrqBmC;EAAW,iBAAA;CHwqB9C;AGvqBmC;EAAW,iBAAA;CH0qB9C;AGzqBmC;EAAW,iBAAA;CH4qB9C;AG3qBmC;EAAW,iBAAA;CH8qB9C;AG7qBmC;EAAW,iBAAA;CHgrB9C;AG/qBmC;EAAW,iBAAA;CHkrB9C;AGjrBmC;EAAW,iBAAA;CHorB9C;AGnrBmC;EAAW,iBAAA;CHsrB9C;AGrrBmC;EAAW,iBAAA;CHwrB9C;AGvrBmC;EAAW,iBAAA;CH0rB9C;AGzrBmC;EAAW,iBAAA;CH4rB9C;AG3rBmC;EAAW,iBAAA;CH8rB9C;AG7rBmC;EAAW,iBAAA;CHgsB9C;AG/rBmC;EAAW,iBAAA;CHksB9C;AGjsBmC;EAAW,iBAAA;CHosB9C;AGnsBmC;EAAW,iBAAA;CHssB9C;AGrsBmC;EAAW,iBAAA;CHwsB9C;AGvsBmC;EAAW,iBAAA;CH0sB9C;AGzsBmC;EAAW,iBAAA;CH4sB9C;AG3sBmC;EAAW,iBAAA;CH8sB9C;AG7sBmC;EAAW,iBAAA;CHgtB9C;AG/sBmC;EAAW,iBAAA;CHktB9C;AGjtBmC;EAAW,iBAAA;CHotB9C;AGntBmC;EAAW,iBAAA;CHstB9C;AGrtBmC;EAAW,iBAAA;CHwtB9C;AGvtBmC;EAAW,iBAAA;CH0tB9C;AGztBmC;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ukDH;AexkDC;EACE,oBAAA;Cf0kDH;Ae3kDC;EACE,WAAA;Cf6kDH;Ae9kDC;EACE,oBAAA;CfglDH;AejlDC;EACE,mBAAA;CfmlDH;Ae/kDC;EACE,YAAA;CfilDH;AejmDC;EACE,WAAA;CfmmDH;AepmDC;EACE,mBAAA;CfsmDH;AevmDC;EACE,mBAAA;CfymDH;Ae1mDC;EACE,UAAA;Cf4mDH;Ae7mDC;EACE,mBAAA;Cf+mDH;AehnDC;EACE,mBAAA;CfknDH;AennDC;EACE,UAAA;CfqnDH;AetnDC;EACE,mBAAA;CfwnDH;AeznDC;EACE,mBAAA;Cf2nDH;Ae5nDC;EACE,UAAA;Cf8nDH;Ae/nDC;EACE,mBAAA;CfioDH;AeloDC;EACE,kBAAA;CfooDH;AehoDC;EACE,WAAA;CfkoDH;AepnDC;EACE,kBAAA;CfsnDH;AevnDC;EACE,0BAAA;CfynDH;Ae1nDC;EACE,0BAAA;Cf4nDH;Ae7nDC;EACE,iBAAA;Cf+nDH;AehoDC;EACE,0BAAA;CfkoDH;AenoDC;EACE,0BAAA;CfqoDH;AetoDC;EACE,iBAAA;CfwoDH;AezoDC;EACE,0BAAA;Cf2oDH;Ae5oDC;EACE,0BAAA;Cf8oDH;Ae/oDC;EACE,iBAAA;CfipDH;AelpDC;EACE,0BAAA;CfopDH;AerpDC;EACE,yBAAA;CfupDH;AexpDC;EACE,gBAAA;Cf0pDH;Aa1pDD;EElCI;IACE,YAAA;Gf+rDH;EexrDD;IACE,YAAA;Gf0rDD;Ee3rDD;IACE,oBAAA;Gf6rDD;Ee9rDD;IACE,oBAAA;GfgsDD;EejsDD;IACE,WAAA;GfmsDD;EepsDD;IACE,oBAAA;GfssDD;EevsDD;IACE,oBAAA;GfysDD;Ee1sDD;IACE,WAAA;Gf4sDD;Ee7sDD;IACE,oBAAA;Gf+sDD;EehtDD;IACE,oBAAA;GfktDD;EentDD;IACE,WAAA;GfqtDD;EettDD;IACE,oBAAA;GfwtDD;EeztDD;IACE,mBAAA;Gf2tDD;Ee7sDD;IACE,YAAA;Gf+sDD;EehtDD;IACE,oBAAA;GfktDD;EentDD;IACE,oBAAA;GfqtDD;EettDD;IACE,WAAA;GfwtDD;EeztDD;IACE,oBAAA;Gf2tDD;Ee5tDD;IACE,oBAAA;Gf8tDD;Ee/tDD;IACE,WAAA;GfiuDD;EeluDD;IACE,oBAAA;GfouDD;EeruDD;IACE,oBAAA;GfuuDD;EexuDD;IACE,WAAA;Gf0uDD;Ee3uDD;IACE,oBAAA;Gf6uDD;Ee9uDD;IACE,mBAAA;GfgvDD;Ee5uDD;IACE,YAAA;Gf8uDD;Ee9vDD;IACE,WAAA;GfgwDD;EejwDD;IACE,mBAAA;GfmwDD;EepwDD;IACE,mBAAA;GfswDD;EevwDD;IACE,UAAA;GfywDD;Ee1wDD;IACE,mBAAA;Gf4wDD;Ee7wDD;IACE,mBAAA;Gf+wDD;EehxDD;IACE,UAAA;GfkxDD;EenxDD;IACE,mBAAA;GfqxDD;EetxDD;IACE,mBAAA;GfwxDD;EezxDD;IACE,UAAA;Gf2xDD;Ee5xDD;IACE,mBAAA;Gf8xDD;Ee/xDD;IACE,kBAAA;GfiyDD;Ee7xDD;IACE,WAAA;Gf+xDD;EejxDD;IACE,kBAAA;GfmxDD;EepxDD;IACE,0BAAA;GfsxDD;EevxDD;IACE,0BAAA;GfyxDD;Ee1xDD;IACE,iBAAA;Gf4xDD;Ee7xDD;IACE,0BAAA;Gf+xDD;EehyDD;IACE,0BAAA;GfkyDD;EenyDD;IACE,iBAAA;GfqyDD;EetyDD;IACE,0BAAA;GfwyDD;EezyDD;IACE,0BAAA;Gf2yDD;Ee5yDD;IACE,iBAAA;Gf8yDD;Ee/yDD;IACE,0BAAA;GfizDD;EelzDD;IACE,yBAAA;GfozDD;EerzDD;IACE,gBAAA;GfuzDD;CACF;Aa/yDD;EE3CI;IACE,YAAA;Gf61DH;Eet1DD;IACE,YAAA;Gfw1DD;Eez1DD;IACE,oBAAA;Gf21DD;Ee51DD;IACE,oBAAA;Gf81DD;Ee/1DD;IACE,WAAA;Gfi2DD;Eel2DD;IACE,oBAAA;Gfo2DD;Eer2DD;IACE,oBAAA;Gfu2DD;Eex2DD;IACE,WAAA;Gf02DD;Ee32DD;IACE,oBAAA;Gf62DD;Ee92DD;IACE,oBAAA;Gfg3DD;Eej3DD;IACE,WAAA;Gfm3DD;Eep3DD;IACE,oBAAA;Gfs3DD;Eev3DD;IACE,mBAAA;Gfy3DD;Ee32DD;IACE,YAAA;Gf62DD;Ee92DD;IACE,oBAAA;Gfg3DD;Eej3DD;IACE,oBAAA;Gfm3DD;Eep3DD;IACE,WAAA;Gfs3DD;Eev3DD;IACE,oBAAA;Gfy3DD;Ee13DD;IACE,oBAAA;Gf43DD;Ee73DD;IACE,WAAA;Gf+3DD;Eeh4DD;IACE,oBAAA;Gfk4DD;Een4DD;IACE,oBAAA;Gfq4DD;Eet4DD;IACE,WAAA;Gfw4DD;Eez4DD;IACE,oBAAA;Gf24DD;Ee54DD;IACE,mBAAA;Gf84DD;Ee14DD;IACE,YAAA;Gf44DD;Ee55DD;IACE,WAAA;Gf85DD;Ee/5DD;IACE,mBAAA;Gfi6DD;Eel6DD;IACE,mBAAA;Gfo6DD;Eer6DD;IACE,UAAA;Gfu6DD;Eex6DD;IACE,mBAAA;Gf06DD;Ee36DD;IACE,mBAAA;Gf66DD;Ee96DD;IACE,UAAA;Gfg7DD;Eej7DD;IACE,mBAAA;Gfm7DD;Eep7DD;IACE,mBAAA;Gfs7DD;Eev7DD;IACE,UAAA;Gfy7DD;Ee17DD;IACE,mBAAA;Gf47DD;Ee77DD;IACE,kBAAA;Gf+7DD;Ee37DD;IACE,WAAA;Gf67DD;Ee/6DD;IACE,kBAAA;Gfi7DD;Eel7DD;IACE,0BAAA;Gfo7DD;Eer7DD;IACE,0BAAA;Gfu7DD;Eex7DD;IACE,iBAAA;Gf07DD;Ee37DD;IACE,0BAAA;Gf67DD;Ee97DD;IACE,0BAAA;Gfg8DD;Eej8DD;IACE,iBAAA;Gfm8DD;Eep8DD;IACE,0BAAA;Gfs8DD;Eev8DD;IACE,0BAAA;Gfy8DD;Ee18DD;IACE,iBAAA;Gf48DD;Ee78DD;IACE,0BAAA;Gf+8DD;Eeh9DD;IACE,yBAAA;Gfk9DD;Een9DD;IACE,gBAAA;Gfq9DD;CACF;Aa18DD;EE9CI;IACE,YAAA;Gf2/DH;Eep/DD;IACE,YAAA;Gfs/DD;Eev/DD;IACE,oBAAA;Gfy/DD;Ee1/DD;IACE,oBAAA;Gf4/DD;Ee7/DD;IACE,WAAA;Gf+/DD;EehgED;IACE,oBAAA;GfkgED;EengED;IACE,oBAAA;GfqgED;EetgED;IACE,WAAA;GfwgED;EezgED;IACE,oBAAA;Gf2gED;Ee5gED;IACE,oBAAA;Gf8gED;Ee/gED;IACE,WAAA;GfihED;EelhED;IACE,oBAAA;GfohED;EerhED;IACE,mBAAA;GfuhED;EezgED;IACE,YAAA;Gf2gED;Ee5gED;IACE,oBAAA;Gf8gED;Ee/gED;IACE,oBAAA;GfihED;EelhED;IACE,WAAA;GfohED;EerhED;IACE,oBAAA;GfuhED;EexhED;IACE,oBAAA;Gf0hED;Ee3hED;IACE,WAAA;Gf6hED;Ee9hED;IACE,oBAAA;GfgiED;EejiED;IACE,oBAAA;GfmiED;EepiED;IACE,WAAA;GfsiED;EeviED;IACE,oBAAA;GfyiED;Ee1iED;IACE,mBAAA;Gf4iED;EexiED;IACE,YAAA;Gf0iED;Ee1jED;IACE,WAAA;Gf4jED;Ee7jED;IACE,mBAAA;Gf+jED;EehkED;IACE,mBAAA;GfkkED;EenkED;IACE,UAAA;GfqkED;EetkED;IACE,mBAAA;GfwkED;EezkED;IACE,mBAAA;Gf2kED;Ee5kED;IACE,UAAA;Gf8kED;Ee/kED;IACE,mBAAA;GfilED;EellED;IACE,mBAAA;GfolED;EerlED;IACE,UAAA;GfulED;EexlED;IACE,mBAAA;Gf0lED;Ee3lED;IACE,kBAAA;Gf6lED;EezlED;IACE,WAAA;Gf2lED;Ee7kED;IACE,kBAAA;Gf+kED;EehlED;IACE,0BAAA;GfklED;EenlED;IACE,0BAAA;GfqlED;EetlED;IACE,iBAAA;GfwlED;EezlED;IACE,0BAAA;Gf2lED;Ee5lED;IACE,0BAAA;Gf8lED;Ee/lED;IACE,iBAAA;GfimED;EelmED;IACE,0BAAA;GfomED;EermED;IACE,0BAAA;GfumED;EexmED;IACE,iBAAA;Gf0mED;Ee3mED;IACE,0BAAA;Gf6mED;Ee9mED;IACE,yBAAA;GfgnED;EejnED;IACE,gBAAA;GfmnED;CACF;AgBvrED;EACE,8BAAA;ChByrED;AgBvrED;EACE,iBAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;ChByrED;AgBvrED;EACE,iBAAA;ChByrED;AgBnrED;EACE,YAAA;EACA,gBA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","file":"bootstrap.css","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n 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');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\002a\";\n}\n.glyphicon-plus:before {\n content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #fff;\n background-color: #333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .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-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .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-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .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-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #ddd;\n}\n.table .table {\n background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #ddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #fff;\n background-image: none;\n border: 1px solid #ccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999;\n}\n.form-control::-ms-expand {\n border: 0;\n background-color: transparent;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eeeeee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"].form-control,\n input[type=\"time\"].form-control,\n input[type=\"datetime-local\"].form-control,\n input[type=\"month\"].form-control {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n min-height: 34px;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.form-group-sm select.form-control {\n height: 30px;\n line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n min-height: 32px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.form-group-lg select.form-control {\n height: 46px;\n line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n min-height: 38px;\n padding: 11px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 11px;\n font-size: 18px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n font-size: 12px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n.btn-default {\n color: #333;\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n color: #333;\n background-color: #e6e6e6;\n border-color: #8c8c8c;\n}\n.btn-default:hover {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n color: #333;\n background-color: #d4d4d4;\n border-color: #8c8c8c;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default .badge {\n color: #fff;\n background-color: #333;\n}\n.btn-primary {\n color: #fff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n color: #fff;\n background-color: #286090;\n border-color: #122b40;\n}\n.btn-primary:hover {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n color: #fff;\n background-color: #204d74;\n border-color: #122b40;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.btn-success {\n color: #fff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n color: #fff;\n background-color: #449d44;\n border-color: #255625;\n}\n.btn-success:hover {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n color: #fff;\n background-color: #398439;\n border-color: #255625;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #fff;\n}\n.btn-info {\n color: #fff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n color: #fff;\n background-color: #31b0d5;\n border-color: #1b6d85;\n}\n.btn-info:hover {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n color: #fff;\n background-color: #269abc;\n border-color: #1b6d85;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #fff;\n}\n.btn-warning {\n color: #fff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n color: #fff;\n background-color: #ec971f;\n border-color: #985f0d;\n}\n.btn-warning:hover {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n color: #fff;\n background-color: #d58512;\n border-color: #985f0d;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #fff;\n}\n.btn-danger {\n color: #fff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n color: #fff;\n background-color: #c9302c;\n border-color: #761c19;\n}\n.btn-danger:hover {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n color: #fff;\n background-color: #ac2925;\n border-color: #761c19;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #fff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-top: 4px solid \\9;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #fff;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group .form-control:focus {\n z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #fff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777;\n}\n.navbar-default .navbar-link:hover {\n color: #333;\n}\n.navbar-default .btn-link {\n color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #ccc;\n}\n.navbar-inverse {\n background-color: #222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #fff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #fff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #fff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #fff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #ccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #fff;\n border: 1px solid #ddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n z-index: 2;\n color: #23527c;\n background-color: #eeeeee;\n border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 3;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #fff;\n border-color: #ddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #fff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #fff;\n line-height: 1;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding-top: 30px;\n padding-bottom: 30px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding-top: 48px;\n padding-bottom: 48px;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-object.img-thumbnail {\n max-width: none;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item,\nbutton.list-group-item {\n color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n text-decoration: none;\n color: #555;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #fff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #ddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #ddd;\n}\n.panel-default {\n border-color: #ddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #fff;\n border: 1px solid #999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 12px;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 14px;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #fff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #fff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #fff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n -moz-perspective: 1000px;\n perspective: 1000px;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #fff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n margin-top: -10px;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #fff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -10px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -10px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -10px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table !important;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table !important;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table !important;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table !important;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table !important;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS and IE text size adjust after device orientation change,\n// without disabling user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability of focused elements when they are also in an\n// active/hover state.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n box-sizing: content-box; //2\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\002a\"; } }\n.glyphicon-plus { &:before { content: \"\\002b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-btc { &:before { content: \"\\e227\"; } }\n.glyphicon-xbt { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-jpy { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-rub { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n// iOS \"clickable elements\" fix for role=\"button\"\n//\n// Fixes \"clickability\" issue (and more generally, the firing of events such as focus as well)\n// for traditionally non-focusable elements with role=\"button\"\n// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n[role=\"button\"] {\n cursor: pointer;\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // WebKit-specific. Other browsers will keep their default outline style.\n // (Initially tried to also force default via `outline: initial`,\n // but that seems to erroneously remove the outline in Firefox altogether.)\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @dl-horizontal-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n .text-uppercase();\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover,\n a&:focus {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover,\n a&:focus {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: floor((@gutter / 2));\n padding-right: ceil((@gutter / 2));\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: ceil((@gutter / -2));\n margin-right: floor((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: ceil((@grid-gutter-width / 2));\n padding-right: floor((@grid-gutter-width / 2));\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Unstyle the caret on ``\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n .opacity(.65);\n .box-shadow(none);\n }\n\n a& {\n &.disabled,\n fieldset[disabled] & {\n pointer-events: none; // Future-proof disabling of clicks on `` elements\n }\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 25%);\n }\n &:hover {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n\n &:hover,\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 17%);\n border-color: darken(@border, 25%);\n }\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus,\n &.focus {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n\n &.in { display: block; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base dashed;\n border-top: @caret-width-base solid ~\"\\9\"; // IE8\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base dashed;\n border-bottom: @caret-width-base solid ~\"\\9\"; // IE8\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn,\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n .border-top-radius(@btn-border-radius-base);\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n .border-top-radius(0);\n .border-bottom-radius(@btn-border-radius-base);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n\n &:focus {\n z-index: 3;\n }\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @input-border-radius;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @input-border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @input-border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n z-index: 2;\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 3;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n\n .btn-xs &,\n .btn-group-xs > .btn & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n\n .list-group-item > & {\n float: right;\n }\n\n .list-group-item > & + & {\n margin-right: 5px;\n }\n\n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding-top: @jumbotron-padding;\n padding-bottom: @jumbotron-padding;\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n\n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding-top: (@jumbotron-padding * 1.6);\n padding-bottom: (@jumbotron-padding * 1.6);\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: @jumbotron-heading-font-size;\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n\n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n\n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n\n // Fix collapse in webkit from max-width: 100% and display: table-cell.\n &.img-thumbnail {\n max-width: none;\n }\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on