diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14cf135d5b..2dfee7279e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Anyone wishing to contribute to the **[Exceptionless/Exceptionless](https://gith 1. Always update to the most recent master release; the bug may already be resolved. -2. Search for similar issues on the [Exceptionless uservoice forum][m]; it may already be an identified problem. +2. Search for similar [issues](https://github.com/exceptionless/Exceptionless/issues?q=is%3Aopen+is%3Aissue+label%3Abug); it may already be an identified problem. 3. If this is a bug or problem that **requires any kind of extended discussion -- open [a topic on uservoice][m] about it**. Do *not* open a bug on GitHub. @@ -17,7 +17,7 @@ Anyone wishing to contribute to the **[Exceptionless/Exceptionless](https://gith ## Requesting New Features -1. Do not submit a feature request on GitHub; all feature requests on GitHub will be closed. Instead, visit the **[Exceptionless uservoice forum][m]**, and search this list for similar feature requests. It's possible somebody has already asked for this feature or provided a pull request that we're still discussing. +1. Search for similar [feature requests](https://github.com/exceptionless/Exceptionless/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement). It's possible somebody has already asked for this feature or provided a pull request that we're still discussing. 2. Provide a clear and detailed explanation of the feature you want and why it's important to add. The feature must apply to a wide array of users of Exceptionless. You may also want to provide us with some advance documentation on the feature, which will help the community to better understand where it will fit. @@ -32,7 +32,7 @@ Anyone wishing to contribute to the **[Exceptionless/Exceptionless](https://gith 2. Follow the Coding Conventions * Adhere to common conventions you see in the existing code * Include tests, and ensure they pass - * Search to see if your new functionality has been discussed on [the Exceptionless uservoice forum](http://exceptionless.uservoice.com), and include updates as appropriate + * Search to see if your new functionality has been discussed on [the Exceptionless issues](https://github.com/exceptionless/Exceptionless/issues), and include updates as appropriate * four spaces, no tabs * no trailing whitespaces, blank lines should have no spaces * use spaces around operators, after commas, colons, semicolons, around `{` and before `}` @@ -49,8 +49,6 @@ Anyone wishing to contribute to the **[Exceptionless/Exceptionless](https://gith 10. Responding to Feedback The Exceptionless team may recommend adjustments to your code. Part of interacting with a healthy open-source community requires you to be open to learning new techniques and strategies; *don't get discouraged!* Remember: if the Exceptionless team suggest changes to your code, **they care enough about your work that they want to include it**, and hope that you can assist by implementing those revisions on your own. - -[m]: https://exceptionless.uservoice.com ## Appreciation diff --git a/Exceptionless.ServerOnly.sln b/Exceptionless.ServerOnly.sln index 1ac36a3abd..ad6949813a 100644 --- a/Exceptionless.ServerOnly.sln +++ b/Exceptionless.ServerOnly.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30110.0 +VisualStudioVersion = 12.0.30324.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.App", "Source\App\Exceptionless.App.csproj", "{75B70E03-6F3D-4B05-979F-8E0BDAAD732C}" EndProject @@ -25,8 +25,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.Membership", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{98A5A2F7-BBA9-4B65-91C9-88E18D474341}" ProjectSection(SolutionItems) = preProject + CONTRIBUTING.md = CONTRIBUTING.md default.ps1 = default.ps1 Source\Jobs.config = Source\Jobs.config + LICENSE.txt = LICENSE.txt + Source\nlog.config = Source\nlog.config + README.md = README.md + WebEssentials-Settings.json = WebEssentials-Settings.json EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.WebApi", "Source\Clients\WebApi\Exceptionless.WebApi.csproj", "{D6E6EE87-5869-4A62-8F67-7A685205BCAD}" diff --git a/Exceptionless.sln b/Exceptionless.sln index 5d5df0e237..c21da4c3c7 100644 --- a/Exceptionless.sln +++ b/Exceptionless.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30324.0 +VisualStudioVersion = 12.0.30723.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.App", "Source\App\Exceptionless.App.csproj", "{75B70E03-6F3D-4B05-979F-8E0BDAAD732C}" EndProject diff --git a/Exceptionless.sln.DotSettings b/Exceptionless.sln.DotSettings index a8660b9cde..a1a29f4138 100644 --- a/Exceptionless.sln.DotSettings +++ b/Exceptionless.sln.DotSettings @@ -369,6 +369,7 @@ <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> $object$_On$event$ <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> diff --git a/Libraries/SignalR/Microsoft.AspNet.SignalR.Redis.XML b/Libraries/SignalR/Microsoft.AspNet.SignalR.Redis.XML deleted file mode 100644 index 44746a94c1..0000000000 --- a/Libraries/SignalR/Microsoft.AspNet.SignalR.Redis.XML +++ /dev/null @@ -1,69 +0,0 @@ - - - - Microsoft.AspNet.SignalR.Redis - - - - - Use Redis as the messaging backplane for scaling out of ASP.NET SignalR applications in a web farm. - - The dependency resolver. - The Redis server address. - The Redis server port. - The Redis server password. - The Redis event key to use. - The dependency resolver. - - - - Use Redis as the messaging backplane for scaling out of ASP.NET SignalR applications in a web farm. - - The dependency resolver - The Redis scale-out configuration options. - The dependency resolver. - - - - Uses Redis pub-sub to scale-out SignalR applications in web farms. - - - - - Settings for the Redis scale-out message bus implementation. - - - - - The Redis database instance to use. - Defaults to 0. - - - - - The Redis event key to use. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Encountered an error while reading a message sent over Redis.. - - - - diff --git a/Libraries/SignalR/Microsoft.AspNet.SignalR.Redis.dll b/Libraries/SignalR/Microsoft.AspNet.SignalR.Redis.dll deleted file mode 100644 index 00f0ecafb5..0000000000 Binary files a/Libraries/SignalR/Microsoft.AspNet.SignalR.Redis.dll and /dev/null differ diff --git a/README.md b/README.md index c1c3fc15a6..846bd3de29 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Exceptionless [![Build Status](http://teamcity.codesmithtools.com/app/rest/builds/buildType:\(id:bt27\)/statusIcon)](http://teamcity.codesmithtools.com/project.html?projectId=Exceptionless) +# Exceptionless [![Build Status](http://teamcity.codesmithtools.com/app/rest/builds/buildType:\(id:Exceptionless_Maintenance\)/statusIcon)](http://teamcity.codesmithtools.com/project.html?projectId=Exceptionless) The definition of the word exceptionless is: to be without exception. [Exceptionless](http://exceptionless.com) provides real-time .NET error reporting for your ASP.NET, Web API, WebForms, WPF, Console, and MVC apps. It organizes the gathered information into simple actionable data that will help your app become exceptionless! diff --git a/Source/App/App_Start/AutoMapperConfig.cs b/Source/App/App_Start/AutoMapperConfig.cs index 982f3335ff..8f29469445 100644 --- a/Source/App/App_Start/AutoMapperConfig.cs +++ b/Source/App/App_Start/AutoMapperConfig.cs @@ -49,6 +49,12 @@ public static void CreateMappings() { esr.Is404 = es.SignatureInfo.ContainsKey("Path") && !es.SignatureInfo.ContainsKey("ExceptionType"); }); + Mapper.CreateMap() + .AfterMap((o, oim) => { + oim.IsOverHourlyLimit = o.IsOverHourlyLimit(); + oim.IsOverMonthlyLimit = o.IsOverMonthlyLimit(); + }); + Mapper.CreateMap(); Mapper.CreateMap().AfterMap((si, igm) => { igm.Id = si.Id.Substring(3); }); diff --git a/Source/App/App_Start/WebApiConfig.cs b/Source/App/App_Start/WebApiConfig.cs index 62b5bbc094..39724b3d72 100644 --- a/Source/App/App_Start/WebApiConfig.cs +++ b/Source/App/App_Start/WebApiConfig.cs @@ -12,11 +12,9 @@ using System; using System.Net.Http.Formatting; using System.Web.Http; -using Exceptionless.Core; using Exceptionless.Core.Controllers; using Exceptionless.Core.Extensions; using Exceptionless.Core.Web; -using ServiceStack.Redis; namespace Exceptionless.App { public static class WebApiConfig { @@ -26,9 +24,14 @@ public static void Register(HttpConfiguration config) { config.MessageHandlers.Add(new XHttpMethodOverrideDelegatingHandler()); config.MessageHandlers.Add(new EncodingDelegatingHandler()); + // Reject error posts in orgs over their max daily error limit. + config.MessageHandlers.Add(config.DependencyResolver.GetService(typeof(BasicAuthenticationHandler)) as BasicAuthenticationHandler); + // Throttle api calls to X every 15 minutes by IP address. - var clientsManager = config.DependencyResolver.GetService(typeof(IRedisClientsManager)) as IRedisClientsManager; - config.MessageHandlers.Add(new ThrottlingHandler(clientsManager, userIdentifier => Settings.Current.ApiThrottleLimit, TimeSpan.FromMinutes(15))); + config.MessageHandlers.Add(config.DependencyResolver.GetService(typeof(ThrottlingHandler)) as ThrottlingHandler); + + // Reject error posts in orgs over their max daily error limit. + config.MessageHandlers.Add(config.DependencyResolver.GetService(typeof(OverageHandler)) as OverageHandler); config.Formatters.Clear(); //config.Formatters.Remove(config.Formatters.JsonFormatter); diff --git a/Source/App/Content/exceptionless.less b/Source/App/Content/exceptionless.less index 1f2e19cc43..36b6e49ea9 100644 --- a/Source/App/Content/exceptionless.less +++ b/Source/App/Content/exceptionless.less @@ -159,6 +159,11 @@ a:hover { text-decoration: none; } +.alert-danger, +.alert-error { + background-color: #fff4f4; +} + .tabbable .table { margin-bottom: 0px; } diff --git a/Source/App/Controllers/API/ErrorController.cs b/Source/App/Controllers/API/ErrorController.cs index 32eb8d5e96..0ba44af085 100644 --- a/Source/App/Controllers/API/ErrorController.cs +++ b/Source/App/Controllers/API/ErrorController.cs @@ -61,7 +61,7 @@ public override Error Get(string id) { return base.Get(id).ToProjectLocalTime(_projectRepository); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.UserOrClient)] + [Authorize(Roles = AuthorizationRoles.UserOrClient)] public override HttpResponseMessage Post(Error value) { if (value == null) return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid error posted."); diff --git a/Source/App/Controllers/API/OrganizationController.cs b/Source/App/Controllers/API/OrganizationController.cs index b71a64febf..9d0ac57e2f 100644 --- a/Source/App/Controllers/API/OrganizationController.cs +++ b/Source/App/Controllers/API/OrganizationController.cs @@ -37,7 +37,7 @@ namespace Exceptionless.App.Controllers.API { [ConfigurationResponseFilter] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public class OrganizationController : RepositoryApiController { private readonly ICacheClient _cacheClient; private readonly IUserRepository _userRepository; @@ -128,14 +128,10 @@ public IHttpActionResult Payments(string id, int page = 1, int pageSize = 12) { pageSize = GetPageSize(pageSize); int skip = GetSkip(page, pageSize); - // TODO: implement proper paging once it's supported by the api. - var limit = pageSize * skip; - if (limit > 100) - limit = 100; - var invoiceService = new StripeInvoiceService(); - List invoices = invoiceService.List(limit, organization.StripeCustomerId).Select(Mapper.Map).ToList(); - return Ok(new PagedResult(invoices.Skip(skip).Take(pageSize).ToList()) { + var invoices = invoiceService.List(new StripeInvoiceListOptions { CustomerId = organization.StripeCustomerId, Limit = 100 }).Select(Mapper.Map).ToList(); + + return Ok(new PagedResult(invoices.Skip(skip).Take(pageSize).ToList(), invoices.Count) { Page = page, PageSize = pageSize }); @@ -186,7 +182,7 @@ protected override bool CanUpdateEntity(Organization original, Delta t.MaxErrorsPerDay) && original.MaxErrorsPerDay != organization.MaxErrorsPerDay) + if ((value.ContainsChangedProperty(t => t.MaxErrorsPerMonth) && original.MaxErrorsPerMonth != organization.MaxErrorsPerMonth) || (value.ContainsChangedProperty(t => t.LastErrorDate) && original.LastErrorDate != organization.LastErrorDate) || (value.ContainsChangedProperty(t => t.StripeCustomerId) && original.StripeCustomerId != organization.StripeCustomerId) || (value.ContainsChangedProperty(t => t.PlanId) && original.PlanId != organization.PlanId) @@ -194,7 +190,7 @@ protected override bool CanUpdateEntity(Organization original, Delta t.SubscribeDate) && original.SubscribeDate != organization.SubscribeDate) || (value.ContainsChangedProperty(t => t.BillingChangeDate) && original.BillingChangeDate != organization.BillingChangeDate) || (value.ContainsChangedProperty(t => t.RetentionDays) && original.RetentionDays != organization.RetentionDays) - || (value.ContainsChangedProperty(t => t.MaxErrorsPerDay) && original.MaxErrorsPerDay != organization.MaxErrorsPerDay) + || (value.ContainsChangedProperty(t => t.MaxErrorsPerMonth) && original.MaxErrorsPerMonth != organization.MaxErrorsPerMonth) || (value.ContainsChangedProperty(t => t.MaxProjects) && original.MaxProjects != organization.MaxProjects) || (value.ContainsChangedProperty(t => t.ProjectCount) && original.ProjectCount != organization.ProjectCount) || (value.ContainsChangedProperty(t => t.StackCount) && original.StackCount != organization.StackCount) @@ -260,8 +256,10 @@ protected override void DeleteEntity(Organization value) { if (!String.IsNullOrEmpty(value.StripeCustomerId)) { Log.Info().Message("Canceling stripe subscription for the organization '{0}' with Id: '{1}'.", value.Name, value.Id).Write(); - var customerService = new StripeCustomerService(); - customerService.CancelSubscription(value.StripeCustomerId); + var subscriptionService = new StripeSubscriptionService(); + var subs = subscriptionService.List(value.StripeCustomerId).Where(s => !s.CanceledAt.HasValue); + foreach (var sub in subs) + subscriptionService.Cancel(value.StripeCustomerId, sub.Id); } List users = _userRepository.GetByOrganizationId(value.Id).ToList(); diff --git a/Source/App/Controllers/API/ProjectController.cs b/Source/App/Controllers/API/ProjectController.cs index 24aedabcd5..c49cdb4912 100644 --- a/Source/App/Controllers/API/ProjectController.cs +++ b/Source/App/Controllers/API/ProjectController.cs @@ -48,7 +48,7 @@ public ProjectController(IProjectRepository repository, _dataHelper = dataHelper; } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override IEnumerable Get() { return base.Get().Select(p => { NotificationSettings settings = p.GetNotificationSettings(User.UserEntity.Id); @@ -61,7 +61,7 @@ public override IEnumerable Get() { }); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override Project Get(string id) { Project project = base.Get(id); NotificationSettings settings = project.GetNotificationSettings(User.UserEntity.Id); @@ -73,29 +73,29 @@ public override Project Get(string id) { return project; } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override HttpResponseMessage Post(Project value) { return base.Post(value); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override HttpResponseMessage Put(string id, Delta value) { return base.Put(id, value); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override HttpResponseMessage Patch(string id, Delta value) { // TODO: We need to add support for array item level patching (E.G., API Keys and Promoted tabs). return base.Patch(id, value); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override HttpResponseMessage Delete(string id) { return base.Delete(id); } [HttpGet] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public IEnumerable List(int page = 1, int pageSize = 100) { pageSize = GetPageSize(pageSize); int skip = GetSkip(page, pageSize); @@ -109,7 +109,7 @@ public IEnumerable List(int page = 1, int pageSize = 100) { } [HttpGet] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public IHttpActionResult GetByOrganizationId(string organizationId, int page = 1, int pageSize = 10) { if (String.IsNullOrEmpty(organizationId) || !User.CanAccessOrganization(organizationId)) return NotFound(); @@ -181,7 +181,7 @@ protected override void DeleteEntity(Project value) { } [HttpGet] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public string GetOrAddKey(string projectId) { Project project = Get(projectId); if (project.ApiKeys.Count > 0) @@ -191,7 +191,7 @@ public string GetOrAddKey(string projectId) { } [HttpPost] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public string ManageApiKeys(string projectId) { string apiKey = Guid.NewGuid().ToString("N").ToLower(); @@ -204,7 +204,7 @@ public string ManageApiKeys(string projectId) { } [HttpDelete] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public HttpResponseMessage ManageApiKeys(string projectId, string apiKey) { if (String.IsNullOrEmpty(apiKey)) return BadRequestErrorResponseMessage(); @@ -229,7 +229,7 @@ public HttpResponseMessage ManageApiKeys(string projectId, string apiKey) { /// The Project Id /// The Configuration for a specific project. [HttpGet] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.UserOrClient)] + [Authorize(Roles = AuthorizationRoles.UserOrClient)] public IHttpActionResult Config(string id = null) { // TODO: Only the client should be using this.. if (User.Identity.AuthenticationType.Equals("ApiKey")) @@ -246,7 +246,7 @@ public IHttpActionResult Config(string id = null) { } [HttpGet] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public void ResetData(string id) { if (String.IsNullOrEmpty(id)) return; @@ -260,7 +260,7 @@ public void ResetData(string id) { // TODO: Move this to use patch. [HttpPut] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public HttpResponseMessage Notification(string id, string userId, NotificationSettings settings) { if (String.IsNullOrEmpty(id) || String.IsNullOrEmpty(userId) || settings == null) return BadRequestErrorResponseMessage(); diff --git a/Source/App/Controllers/API/ProjectHookController.cs b/Source/App/Controllers/API/ProjectHookController.cs index e91e1f0341..4dad04ef3f 100644 --- a/Source/App/Controllers/API/ProjectHookController.cs +++ b/Source/App/Controllers/API/ProjectHookController.cs @@ -25,7 +25,7 @@ using Newtonsoft.Json.Linq; namespace Exceptionless.App.Controllers.API { - [ExceptionlessAuthorize] + [Authorize] public class ProjectHookController : RepositoryApiController { private readonly IProjectRepository _projectRepository; private readonly BillingManager _billingManager; @@ -40,7 +40,7 @@ public override IEnumerable Get() { throw new NotSupportedException(); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public IEnumerable GetByProject(string projectId) { if (!IsInProject(projectId)) throw new HttpResponseException(NotFoundErrorResponseMessage(projectId)); @@ -48,7 +48,7 @@ public IEnumerable GetByProject(string projectId) { return _repository.GetByProjectId(projectId); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override ProjectHook Get(string id) { ProjectHook entity = base.Get(id); @@ -58,7 +58,7 @@ public override ProjectHook Get(string id) { return entity; } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override HttpResponseMessage Post(ProjectHook value) { if (!IsInProject(value.ProjectId)) throw new HttpResponseException(BadRequestErrorResponseMessage()); @@ -70,7 +70,7 @@ public override HttpResponseMessage Post(ProjectHook value) { return base.Post(value); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override HttpResponseMessage Put(string id, Delta value) { ProjectHook original = GetEntity(id); if (original == null || !IsInProject(original.ProjectId)) @@ -79,7 +79,7 @@ public override HttpResponseMessage Put(string id, Delta value) { return base.Put(id, value); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override HttpResponseMessage Patch(string id, Delta value) { ProjectHook original = GetEntity(id); if (original == null || !IsInProject(original.ProjectId)) @@ -88,7 +88,7 @@ public override HttpResponseMessage Patch(string id, Delta value) { return base.Patch(id, value); } - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public override HttpResponseMessage Delete(string id) { ProjectHook original = GetEntity(id); if (original == null || !IsInProject(original.ProjectId)) @@ -103,7 +103,7 @@ public override HttpResponseMessage Delete(string id) { /// /// [HttpPost] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.UserOrClient)] + [Authorize(Roles = AuthorizationRoles.UserOrClient)] public HttpResponseMessage Subscribe(JObject data) { var targetUrl = data.GetValue("target_url").Value(); var eventType = data.GetValue("event").Value(); @@ -144,7 +144,7 @@ public HttpResponseMessage Unsubscribe(JObject data) { /// [HttpPost] [HttpGet] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.UserOrClient)] + [Authorize(Roles = AuthorizationRoles.UserOrClient)] public HttpResponseMessage Test() { return Request.CreateResponse(HttpStatusCode.OK, new[] { new { id = 1, Message = "Test message 1." }, diff --git a/Source/App/Controllers/API/StackController.cs b/Source/App/Controllers/API/StackController.cs index 0ee3907d60..31b9ac3fba 100644 --- a/Source/App/Controllers/API/StackController.cs +++ b/Source/App/Controllers/API/StackController.cs @@ -35,7 +35,7 @@ namespace Exceptionless.App.Controllers.API { [ConfigurationResponseFilter] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public class StackController : RepositoryOwnedByOrganizationApiController { private readonly IMessageFactory _messageFactory; private readonly BillingManager _billingManager; @@ -169,7 +169,7 @@ public void Promote(string id) { /// /// [HttpPost] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.UserOrClient)] + [Authorize(Roles = AuthorizationRoles.UserOrClient)] public HttpResponseMessage MarkFixed(JObject data) { var id = data.GetValue("ErrorStack").Value(); if (String.IsNullOrEmpty(id)) @@ -203,7 +203,7 @@ public HttpResponseMessage MarkFixed(JObject data) { /// /// [HttpPost] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.UserOrClient)] + [Authorize(Roles = AuthorizationRoles.UserOrClient)] public HttpResponseMessage AddLink(JObject data) { var id = data.GetValue("ErrorStack").Value(); if (String.IsNullOrEmpty(id)) diff --git a/Source/App/Controllers/API/StatsController.cs b/Source/App/Controllers/API/StatsController.cs index e0f5205125..916521e790 100644 --- a/Source/App/Controllers/API/StatsController.cs +++ b/Source/App/Controllers/API/StatsController.cs @@ -27,7 +27,7 @@ using ServiceStack.CacheAccess; namespace Exceptionless.App.Controllers.API { - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public class StatsController : ExceptionlessApiController { private readonly ErrorStatsHelper _statsHelper; private readonly IOrganizationRepository _organizationRepository; @@ -198,16 +198,24 @@ public IHttpActionResult Plans() { List smallOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.SmallPlan.Id) && o.BillingPrice > 0).ToList(); List mediumOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.MediumPlan.Id) && o.BillingPrice > 0).ToList(); List largeOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.LargePlan.Id) && o.BillingPrice > 0).ToList(); + List extraLargeOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.ExtraLargePlan.Id) && o.BillingPrice > 0).ToList(); + List enterpriseOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.EnterprisePlan.Id) && o.BillingPrice > 0).ToList(); decimal monthlyTotalPaid = smallOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice) + mediumOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice) - + largeOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice); + + largeOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice) + + extraLargeOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice) + + enterpriseOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice); List smallYearlyOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.SmallYearlyPlan.Id) && o.BillingPrice > 0).ToList(); List mediumYearlyOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.MediumYearlyPlan.Id) && o.BillingPrice > 0).ToList(); List largeYearlyOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.LargeYearlyPlan.Id) && o.BillingPrice > 0).ToList(); + List extraLargeYearlyOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.ExtraLargeYearlyPlan.Id) && o.BillingPrice > 0).ToList(); + List enterpriseYearlyOrganizations = results.Where(o => String.Equals(o.PlanId, BillingManager.EnterpriseYearlyPlan.Id) && o.BillingPrice > 0).ToList(); decimal yearlyTotalPaid = smallYearlyOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice) + mediumYearlyOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice) - + largeYearlyOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice); + + largeYearlyOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice) + + extraLargeYearlyOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice) + + enterpriseYearlyOrganizations.Where(o => !o.IsSuspended && o.BillingStatus == BillingStatus.Active).Sum(o => o.BillingPrice); return Ok(new BillingPlanStatsModel { SmallTotal = smallOrganizations.Count, @@ -216,10 +224,14 @@ public IHttpActionResult Plans() { MediumYearlyTotal = mediumYearlyOrganizations.Count, LargeTotal = largeOrganizations.Count, LargeYearlyTotal = largeYearlyOrganizations.Count, + ExtraLargeTotal = extraLargeOrganizations.Count, + ExtraLargeYearlyTotal = extraLargeYearlyOrganizations.Count, + EnterpriseTotal = enterpriseOrganizations.Count, + EnterpriseYearlyTotal = enterpriseYearlyOrganizations.Count, MonthlyTotal = monthlyTotalPaid + (yearlyTotalPaid / 12), YearlyTotal = (monthlyTotalPaid * 12) + yearlyTotalPaid, - MonthlyTotalAccounts = smallOrganizations.Count + mediumOrganizations.Count + largeOrganizations.Count, - YearlyTotalAccounts = smallYearlyOrganizations.Count + mediumYearlyOrganizations.Count + largeYearlyOrganizations.Count, + MonthlyTotalAccounts = smallOrganizations.Count + mediumOrganizations.Count + largeOrganizations.Count + extraLargeOrganizations.Count + enterpriseOrganizations.Count, + YearlyTotalAccounts = smallYearlyOrganizations.Count + mediumYearlyOrganizations.Count + largeYearlyOrganizations.Count + extraLargeYearlyOrganizations.Count + enterpriseYearlyOrganizations.Count, FreeAccounts = results.Count(o => String.Equals(o.PlanId, BillingManager.FreePlan.Id)), PaidAccounts = results.Count(o => !String.Equals(o.PlanId, BillingManager.FreePlan.Id) && o.BillingPrice > 0), FreeloaderAccounts = results.Count(o => !String.Equals(o.PlanId, BillingManager.FreePlan.Id) && o.BillingPrice <= 0), diff --git a/Source/App/Controllers/API/UserController.cs b/Source/App/Controllers/API/UserController.cs index d28b2d13a1..e570bfab74 100644 --- a/Source/App/Controllers/API/UserController.cs +++ b/Source/App/Controllers/API/UserController.cs @@ -32,7 +32,7 @@ public UserController(IUserRepository userRepository, IOrganizationRepository or } [HttpPut] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.GlobalAdmin)] + [Authorize(Roles = AuthorizationRoles.GlobalAdmin)] public HttpResponseMessage UpdateAdminRole(string id) { if (String.IsNullOrEmpty(id)) return BadRequestErrorResponseMessage(); @@ -51,7 +51,7 @@ public HttpResponseMessage UpdateAdminRole(string id) { } [HttpGet] - [ExceptionlessAuthorize(Roles = AuthorizationRoles.User)] + [Authorize(Roles = AuthorizationRoles.User)] public IHttpActionResult GetByOrganizationId(string organizationId, int page = 1, int pageSize = 10) { if (String.IsNullOrEmpty(organizationId) || !User.CanAccessOrganization(organizationId)) return NotFound(); diff --git a/Source/App/Controllers/AccountController.cs b/Source/App/Controllers/AccountController.cs index 57d6ea3b4a..95d0b68904 100644 --- a/Source/App/Controllers/AccountController.cs +++ b/Source/App/Controllers/AccountController.cs @@ -21,6 +21,7 @@ using Exceptionless.App.Hubs; using Exceptionless.App.Models.Account; using Exceptionless.App.Models.Common; +using Exceptionless.App.Models.Organization; using Exceptionless.App.Models.Project; using Exceptionless.App.Models.User; using Exceptionless.Core; @@ -97,7 +98,7 @@ public ActionResult Init(string projectId = null, string organizationId = null) }, EnableBilling = Settings.Current.EnableBilling, BillingInfo = BillingManager.Plans, - Organizations = organizations, + Organizations = organizations.Select(Mapper.Map), Projects = projects.Select(Mapper.Map), }, JsonRequestBehavior.AllowGet); } diff --git a/Source/App/Controllers/HomeController.cs b/Source/App/Controllers/HomeController.cs index 1602d8827d..2e927bbc25 100644 --- a/Source/App/Controllers/HomeController.cs +++ b/Source/App/Controllers/HomeController.cs @@ -10,28 +10,10 @@ #endregion using System; -using System.Linq; -using System.Net; using System.Web.Mvc; -using Exceptionless.App.Hubs; -using Exceptionless.Core; -using ServiceStack.CacheAccess; -using ServiceStack.Redis; namespace Exceptionless.App.Controllers { public class HomeController : Controller { - private readonly ICacheClient _cacheClient; - private readonly IRedisClientsManager _clientsManager; - private readonly NotificationSender _notificationSender; - private readonly IUserRepository _userRepository; - - public HomeController(ICacheClient cacheClient, IRedisClientsManager clientsManager, NotificationSender notificationSender, IUserRepository userRepository) { - _cacheClient = cacheClient; - _clientsManager = clientsManager; - _notificationSender = notificationSender; - _userRepository = userRepository; - } - public ActionResult Index() { return RedirectToAction("Index", "Project"); } @@ -72,28 +54,5 @@ public ActionResult Boom() { public ActionResult Maintenance() { return View(); } - - public ActionResult Status() { - try { - if (_cacheClient.Get("__PING__") != null) - return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Cache Not Working"); - } catch (Exception ex) { - return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Cache Not Working: " + ex.Message); - } - - try { - if (!GlobalApplication.IsDbUpToDate()) - return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Mongo DB Schema Outdated"); - - var user = _userRepository.All().Take(1).FirstOrDefault(); - } catch (Exception ex) { - return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Mongo Not Working: " + ex.Message); - } - - if (!_notificationSender.IsListening()) - return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Ping Not Received"); - - return new ContentResult { Content = "All Systems Check" }; - } } } \ No newline at end of file diff --git a/Source/App/Controllers/OrganizationController.cs b/Source/App/Controllers/OrganizationController.cs index 5ffdd1a792..757d41a953 100644 --- a/Source/App/Controllers/OrganizationController.cs +++ b/Source/App/Controllers/OrganizationController.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Linq; using System.Web.Mvc; using Exceptionless.App.Hubs; using Exceptionless.App.Models.Organization; @@ -53,7 +54,7 @@ public ActionResult Manage(string id) { } [HttpPost] - public JsonResult ChangePlan(string organizationId, string planId, string stripeToken, string last4) { + public JsonResult ChangePlan(string organizationId, string planId, string stripeToken, string last4, string couponId = null) { if (String.IsNullOrEmpty(organizationId) || !User.CanAccessOrganization(organizationId)) throw new ArgumentException("Invalid organization id.", "organizationId"); // TODO: These should probably throw http Response exceptions. @@ -77,12 +78,16 @@ public JsonResult ChangePlan(string organizationId, string planId, string stripe return Json(new { Success = false, Message = message }); var customerService = new StripeCustomerService(); + var subscriptionService = new StripeSubscriptionService(); try { // If they are on a paid plan and then downgrade to a free plan then cancel their stripe subscription. if (!String.Equals(organization.PlanId, BillingManager.FreePlan.Id) && String.Equals(plan.Id, BillingManager.FreePlan.Id)) { - if (!String.IsNullOrEmpty(organization.StripeCustomerId)) - customerService.CancelSubscription(organization.StripeCustomerId); + if (!String.IsNullOrEmpty(organization.StripeCustomerId)) { + var subs = subscriptionService.List(organization.StripeCustomerId).Where(s => !s.CanceledAt.HasValue); + foreach (var sub in subs) + subscriptionService.Cancel(organization.StripeCustomerId, sub.Id); + } organization.BillingStatus = BillingStatus.Trialing; organization.RemoveSuspension(); @@ -92,11 +97,17 @@ public JsonResult ChangePlan(string organizationId, string planId, string stripe organization.SubscribeDate = DateTime.Now; - StripeCustomer customer = customerService.Create(new StripeCustomerCreateOptions { + var createCustomer = new StripeCustomerCreateOptions { TokenId = stripeToken, PlanId = planId, - Description = organization.Name - }); + Description = organization.Name, + Email = User.UserEntity.EmailAddress + }; + + if (!String.IsNullOrWhiteSpace(couponId)) + createCustomer.CouponId = couponId; + + StripeCustomer customer = customerService.Create(createCustomer); organization.BillingStatus = BillingStatus.Active; organization.RemoveSuspension(); @@ -104,17 +115,26 @@ public JsonResult ChangePlan(string organizationId, string planId, string stripe if (customer.StripeCardList.StripeCards.Count > 0) organization.CardLast4 = customer.StripeCardList.StripeCards[0].Last4; } else { - var update = new StripeCustomerUpdateSubscriptionOptions { - PlanId = planId - }; + var update = new StripeSubscriptionUpdateOptions { PlanId = planId }; + var create = new StripeSubscriptionCreateOptions(); bool cardUpdated = false; if (!String.IsNullOrEmpty(stripeToken)) { update.TokenId = stripeToken; + create.TokenId = stripeToken; cardUpdated = true; } - - customerService.UpdateSubscription(organization.StripeCustomerId, update); + + var subscription = subscriptionService.List(organization.StripeCustomerId).FirstOrDefault(s => !s.CanceledAt.HasValue); + if (subscription != null) + subscriptionService.Update(organization.StripeCustomerId, subscription.Id, update); + else + subscriptionService.Create(organization.StripeCustomerId, planId, create); + + customerService.Update(organization.StripeCustomerId, new StripeCustomerUpdateOptions { + Email = User.UserEntity.EmailAddress + }); + if (cardUpdated) organization.CardLast4 = last4; @@ -169,4 +189,4 @@ public ActionResult Suspended(string id = null) { return RedirectToAction("Index", "Project"); } } -} \ No newline at end of file +} diff --git a/Source/App/Controllers/StatusController.cs b/Source/App/Controllers/StatusController.cs new file mode 100644 index 0000000000..6766ff345b --- /dev/null +++ b/Source/App/Controllers/StatusController.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; +using System.Net; +using System.Web.Mvc; +using Exceptionless.App.Hubs; +using Exceptionless.Core; +using ServiceStack.CacheAccess; + +namespace Exceptionless.App.Controllers { + public class StatusController : Controller { + private readonly ICacheClient _cacheClient; + private readonly NotificationSender _notificationSender; + private readonly IUserRepository _userRepository; + + public StatusController(ICacheClient cacheClient, NotificationSender notificationSender, IUserRepository userRepository) { + _cacheClient = cacheClient; + _notificationSender = notificationSender; + _userRepository = userRepository; + } + + [HttpGet] + public ActionResult Index() { + try { + if (_cacheClient.Get("__PING__") != null) + return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Cache Not Working"); + } catch (Exception ex) { + return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Cache Not Working: " + ex.Message); + } + + try { + if (!GlobalApplication.IsDbUpToDate()) + return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Mongo DB Schema Outdated"); + + var user = _userRepository.All().Take(1).FirstOrDefault(); + } catch (Exception ex) { + return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Mongo Not Working: " + ex.Message); + } + + //if (!_notificationSender.IsListening()) + // return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable, "Ping Not Received"); + + return new ContentResult { Content = "All Systems Check" }; + } + } +} \ No newline at end of file diff --git a/Source/App/Exceptionless.App.csproj b/Source/App/Exceptionless.App.csproj index c37efbb037..908098253d 100644 --- a/Source/App/Exceptionless.App.csproj +++ b/Source/App/Exceptionless.App.csproj @@ -27,6 +27,7 @@ /assemblyCompareMode:StrongNameIgnoringVersion 1.0 + 3c428564 true @@ -55,32 +56,34 @@ - ..\..\packages\AjaxMin.5.8.5172.27710\lib\net40\AjaxMin.dll + ..\..\packages\AjaxMin.5.11.5295.12309\lib\net40\AjaxMin.dll + True ..\..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll - - False - ..\..\packages\AutoMapper.3.1.1\lib\net40\AutoMapper.dll + + ..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.dll + True - - False - ..\..\packages\AutoMapper.3.1.1\lib\net40\AutoMapper.Net4.dll + + ..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.Net4.dll + True False ..\..\packages\BookSleeve.1.3.41\lib\BookSleeve.dll - - False - ..\..\packages\BundleTransformer.Core.1.8.23\lib\net40\BundleTransformer.Core.dll + + ..\..\packages\BundleTransformer.Core.1.9.25\lib\net40\BundleTransformer.Core.dll - - ..\..\packages\BundleTransformer.Less.1.8.25\lib\net40\BundleTransformer.Less.dll + + False + ..\..\packages\BundleTransformer.Less.1.9.25\lib\net40\BundleTransformer.Less.dll + True - ..\..\packages\BundleTransformer.MicrosoftAjax.1.8.26\lib\net40\BundleTransformer.MicrosoftAjax.dll + ..\..\packages\BundleTransformer.MicrosoftAjax.1.9.25\lib\net40\BundleTransformer.MicrosoftAjax.dll ..\..\packages\DataAnnotationsExtensions.1.1.0.0\lib\NETFramework40\DataAnnotationsExtensions.dll @@ -117,56 +120,57 @@ ..\..\packages\GravatarHelper.3.2.3\lib\Net40\GravatarHelper.dll - ..\..\packages\JavaScriptEngineSwitcher.Core.1.1.3\lib\net40\JavaScriptEngineSwitcher.Core.dll - True + ..\..\packages\JavaScriptEngineSwitcher.Core.1.2.0\lib\net40\JavaScriptEngineSwitcher.Core.dll - - ..\..\packages\JavaScriptEngineSwitcher.Jurassic.1.1.3\lib\net40\JavaScriptEngineSwitcher.Jurassic.dll + + False + ..\..\packages\JavaScriptEngineSwitcher.Jurassic.1.2.0\lib\net40\JavaScriptEngineSwitcher.Jurassic.dll + True False - ..\..\packages\JavaScriptEngineSwitcher.Jurassic.1.1.3\lib\net40\Jurassic.dll + ..\..\packages\JavaScriptEngineSwitcher.Jurassic.1.2.0\lib\net40\Jurassic.dll - + False - ..\..\packages\Microsoft.AspNet.SignalR.Core.2.0.3\lib\net45\Microsoft.AspNet.SignalR.Core.dll + ..\..\packages\Microsoft.AspNet.SignalR.Core.2.1.2\lib\net45\Microsoft.AspNet.SignalR.Core.dll - + False - ..\..\Libraries\SignalR\Microsoft.AspNet.SignalR.Redis.dll + ..\..\packages\Microsoft.AspNet.SignalR.Redis.2.1.2\lib\net45\Microsoft.AspNet.SignalR.Redis.dll - + False - ..\..\packages\Microsoft.AspNet.SignalR.SystemWeb.2.0.3\lib\net45\Microsoft.AspNet.SignalR.SystemWeb.dll + ..\..\packages\Microsoft.AspNet.SignalR.SystemWeb.2.1.2\lib\net45\Microsoft.AspNet.SignalR.SystemWeb.dll + True - + False - ..\..\packages\Microsoft.Data.Edm.5.6.1\lib\net40\Microsoft.Data.Edm.dll + ..\..\packages\Microsoft.Data.Edm.5.6.3\lib\net40\Microsoft.Data.Edm.dll - + False - ..\..\packages\Microsoft.Data.OData.5.6.1\lib\net40\Microsoft.Data.OData.dll + ..\..\packages\Microsoft.Data.OData.5.6.3\lib\net40\Microsoft.Data.OData.dll - - False - ..\..\packages\Microsoft.Owin.2.1.0\lib\net45\Microsoft.Owin.dll + + ..\..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll - - False - ..\..\packages\Microsoft.Owin.Host.SystemWeb.2.1.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + ..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + True False - ..\..\packages\Microsoft.Owin.Security.2.1.0\lib\net45\Microsoft.Owin.Security.dll + ..\..\packages\Microsoft.Owin.Security.3.0.0\lib\net45\Microsoft.Owin.Security.dll - + False - ..\..\packages\mongocsharpdriver.1.9.0\lib\net35\MongoDB.Bson.dll + ..\..\packages\mongocsharpdriver.1.9.2\lib\net35\MongoDB.Bson.dll + True - - False - ..\..\packages\mongocsharpdriver.1.9.0\lib\net35\MongoDB.Driver.dll + + ..\..\packages\mongocsharpdriver.1.9.2\lib\net35\MongoDB.Driver.dll ..\..\packages\DotNetOpenAuth.OpenId.Core.4.3.4.13329\lib\net45-full\Mono.Math.dll @@ -176,18 +180,18 @@ False - ..\..\packages\Newtonsoft.Json.6.0.1\lib\net45\Newtonsoft.Json.dll + ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll - + False - ..\..\packages\NLog.2.1.0\lib\net45\NLog.dll - - - ..\..\packages\NLog.Mongo.1.1.0.16\lib\net45\NLog.Mongo.dll + ..\..\packages\NLog.3.1.0.0\lib\net45\NLog.dll - + False - ..\..\packages\NLog.Xml.2.1.0.24\lib\net45\NLog.Xml.dll + ..\..\packages\NLog.Mongo.3.0.0.21\lib\net45\NLog.Mongo.dll + + + ..\..\packages\NLog.Xml.3.1.0.30\lib\net45\NLog.Xml.dll ..\..\packages\DotNetOpenAuth.OpenId.Core.4.3.4.13329\lib\net45-full\Org.Mentalis.Security.Cryptography.dll @@ -219,31 +223,35 @@ False ..\..\packages\ServiceStack.Text.3.9.71\lib\net35\ServiceStack.Text.dll - + False - ..\..\packages\SimpleInjector.2.5.0\lib\net45\SimpleInjector.dll + ..\..\packages\SimpleInjector.2.6.1\lib\net45\SimpleInjector.dll - - ..\..\packages\SimpleInjector.2.5.0\lib\net45\SimpleInjector.Diagnostics.dll + + False + ..\..\packages\SimpleInjector.2.6.1\lib\net45\SimpleInjector.Diagnostics.dll - - ..\..\packages\SimpleInjector.Extensions.ExecutionContextScoping.2.5.0\lib\net45\SimpleInjector.Extensions.ExecutionContextScoping.dll + + False + ..\..\packages\SimpleInjector.Extensions.ExecutionContextScoping.2.6.1\lib\net45\SimpleInjector.Extensions.ExecutionContextScoping.dll - ..\..\packages\SimpleInjector.Integration.Web.2.5.0\lib\net40\SimpleInjector.Integration.Web.dll + ..\..\packages\SimpleInjector.Integration.Web.2.6.1\lib\net40\SimpleInjector.Integration.Web.dll - ..\..\packages\SimpleInjector.Integration.Web.Mvc.2.5.0\lib\net40\SimpleInjector.Integration.Web.Mvc.dll + ..\..\packages\SimpleInjector.Integration.Web.Mvc.2.6.1\lib\net40\SimpleInjector.Integration.Web.Mvc.dll + True - - ..\..\packages\SimpleInjector.Integration.WebApi.2.5.0\lib\net45\SimpleInjector.Integration.WebApi.dll + + False + ..\..\packages\SimpleInjector.Integration.WebApi.2.6.1\lib\net45\SimpleInjector.Integration.WebApi.dll - ..\..\packages\SimpleInjector.Packaging.2.5.0\lib\net40-client\SimpleInjector.Packaging.dll + ..\..\packages\SimpleInjector.Packaging.2.6.1\lib\net40-client\SimpleInjector.Packaging.dll - + False - ..\..\packages\Stripe.net.2.0.0\lib\net40\Stripe.net.dll + ..\..\packages\Stripe.net.2.3.2\lib\net40\Stripe.net.dll True @@ -253,73 +261,73 @@ - + False - ..\..\packages\Microsoft.Net.Http.2.2.19\lib\net45\System.Net.Http.Extensions.dll + ..\..\packages\Microsoft.Net.Http.2.2.28\lib\net45\System.Net.Http.Extensions.dll - + False - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.1.2\lib\net45\System.Net.Http.Formatting.dll - True + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll - - ..\..\packages\Microsoft.Net.Http.2.2.19\lib\net45\System.Net.Http.Primitives.dll + + False + ..\..\packages\Microsoft.Net.Http.2.2.28\lib\net45\System.Net.Http.Primitives.dll - + False - ..\..\packages\System.Spatial.5.6.1\lib\net40\System.Spatial.dll + ..\..\packages\System.Spatial.5.6.3\lib\net40\System.Spatial.dll - - ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.Helpers.dll - True + + False + ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.1.2\lib\net45\System.Web.Http.dll - True + + False + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + - ..\..\packages\Microsoft.AspNet.WebApi.OData.5.1.2\lib\net45\System.Web.Http.OData.dll - True + ..\..\packages\Microsoft.AspNet.WebApi.OData.5.3.1\lib\net45\System.Web.Http.OData.dll False - ..\..\packages\Microsoft.AspNet.WebApi.Tracing.5.1.2\lib\net45\System.Web.Http.Tracing.dll + ..\..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.2\lib\net45\System.Web.Http.Tracing.dll - - ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.1.2\lib\net45\System.Web.Http.WebHost.dll - True + + False + ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.2\lib\net45\System.Web.Http.WebHost.dll - - - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.1.2\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.2\lib\net45\System.Web.Mvc.dll False ..\..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll - - ..\..\packages\Microsoft.AspNet.Razor.3.1.2\lib\net45\System.Web.Razor.dll - True + + False + ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll - - ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.dll - True + + False + ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll - - ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Deployment.dll + + False + ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll True - - ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Razor.dll - True + + False + ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll @@ -330,8 +338,9 @@ - - ..\..\packages\UAParser.1.0.2.0\lib\net40\UAParser.dll + + False + ..\..\packages\UAParser.1.2.0.0\lib\net35-Client\UAParser.dll False @@ -347,17 +356,13 @@ False - ..\..\packages\Microsoft.AspNet.WebPages.Data.3.1.2\lib\net45\WebMatrix.Data.dll - True + ..\..\packages\Microsoft.AspNet.WebPages.Data.3.2.2\lib\net45\WebMatrix.Data.dll False - ..\..\packages\Microsoft.AspNet.WebPages.WebData.3.1.2\lib\net45\WebMatrix.WebData.dll + ..\..\packages\Microsoft.AspNet.WebPages.WebData.3.2.2\lib\net45\WebMatrix.WebData.dll True - - ..\..\packages\YamlDotNet.3.0.0\lib\net35\YamlDotNet.dll - @@ -380,6 +385,7 @@ + @@ -404,6 +410,7 @@ + @@ -457,7 +464,6 @@ toastr.css - @@ -468,11 +474,13 @@ - - - - + + + + + + @@ -492,7 +500,6 @@ - @@ -599,6 +606,7 @@ + @@ -793,6 +801,9 @@ + + + 11.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) @@ -856,9 +867,12 @@ copy "$(SolutionDir)Source\nlog.config" "$(ProjectDir)nlog.config" - - - - + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + \ No newline at end of file diff --git a/Source/App/Global.asax.cs b/Source/App/Global.asax.cs index ceb8b68439..67c3afdba7 100644 --- a/Source/App/Global.asax.cs +++ b/Source/App/Global.asax.cs @@ -132,10 +132,6 @@ public void MarkDbDown() { } private void CheckDbOrCacheDown() { - // make sure we are still listening for events - var notificationSender = DependencyResolver.Current.GetService(); - notificationSender.EnsureListening(); - // check if the cache is down every 5 seconds or every request if it's currently marked as down if (_isCacheDown || DateTime.Now.Subtract(_lastCacheCheck).TotalSeconds > 5) { try { @@ -143,14 +139,19 @@ private void CheckDbOrCacheDown() { var ping = cache.Get("__PING__"); _isCacheDown = false; _lastCacheCheck = DateTime.Now; - } catch (RedisException) { + } catch { _isCacheDown = true; _lastCacheCheck = DateTime.Now; } } - if (!_isDbDown && !_isCacheDown) + if (!_isDbDown && !_isCacheDown) { + // make sure we are still listening for events + var notificationSender = DependencyResolver.Current.GetService(); + notificationSender.EnsureListening(); + return; + } // check every 10 seconds to see if the db is back up if (_isDbDown && DateTime.Now.Subtract(_lastDbCheck).TotalSeconds > 10) { diff --git a/Source/App/Hubs/Notifier.cs b/Source/App/Hubs/Notifier.cs index 27ea4c05e2..6e0e9fa646 100644 --- a/Source/App/Hubs/Notifier.cs +++ b/Source/App/Hubs/Notifier.cs @@ -12,11 +12,11 @@ using System; using System.Threading; using System.Threading.Tasks; -using CodeSmith.Core.Extensions; using Exceptionless.Core; using Exceptionless.Core.Authorization; using Exceptionless.Core.Pipeline; using Microsoft.AspNet.SignalR; +using NLog.Fluent; using ServiceStack.CacheAccess; using ServiceStack.Redis; @@ -44,42 +44,78 @@ public NotificationSender(ICacheClient cacheClient, IRedisClientsManager redisCl _redisClientsManager = redisClientsManager; } - public event EventHandler Ping; - public void Listen() { Task.Factory.StartNew(() => { - using (IRedisClient client = _redisClientsManager.GetReadOnlyClient()) { - using (IRedisSubscription subscription = client.CreateSubscription()) { - subscription.OnMessage = (channel, msg) => { - if (msg == "ping" && Ping != null) - Ping(this, EventArgs.Empty); + restart: + try { + using (var client = _redisClientsManager.GetReadOnlyClient()) { + using (var subscription = client.CreateSubscription()) { + subscription.OnMessage = (ch, msg) => { + try { + OnMessage(ch, msg); + } catch (Exception ex) { + Log.Error().Exception(ex).Message("Error processing message \"{0}\": {1}", msg, ex.Message).Write(); + } + }; + subscription.SubscribeToChannels(NotifySignalRAction.NOTIFICATION_CHANNEL_KEY); + } + } + } catch (Exception ex) { + Log.Error().Exception(ex).Message("Error in listener: {0}", ex.Message).Write(); + goto restart; + } + }); + } - string[] parts = msg.Split(':'); - if (parts.Length != 6) - return; + private void OnMessage(string channel, string msg) { + string[] parts = msg.Split(':'); + if (parts.Length < 1) + return; - bool isHidden; - Boolean.TryParse(parts[3], out isHidden); + switch (parts[0]) { + case "ping": + OnPing(); + break; + case "overlimit": + if (parts.Length != 3) + return; - bool isFixed; - Boolean.TryParse(parts[4], out isFixed); + if (parts[1] == "hr") + WentOverHourlyLimit(parts[2]); + else + WentOverMonthlyLimit(parts[2]); - bool is404; - Boolean.TryParse(parts[5], out is404); + break; + default: // error occurred + if (parts.Length != 6) + return; - NewError(parts[0], parts[1], parts[2], isHidden, isFixed, is404); - }; - RetryUtil.Retry(() => subscription.SubscribeToChannels(NotifySignalRAction.NOTIFICATION_CHANNEL_KEY)); - } - } - }); + bool isHidden; + Boolean.TryParse(parts[3], out isHidden); + + bool isFixed; + Boolean.TryParse(parts[4], out isFixed); + + bool is404; + Boolean.TryParse(parts[5], out is404); + + NewError(parts[0], parts[1], parts[2], isHidden, isFixed, is404); + break; + } + } + + public event EventHandler Ping; + + private void OnPing() { + if (Ping != null) + Ping(this, EventArgs.Empty); } private static DateTime _lastListenerCheck; public void EnsureListening() { - // Check if the notifier listener is listening every 10 seconds. - if (!(DateTime.Now.Subtract(_lastListenerCheck).TotalSeconds > 10)) + // Check if the notifier listener is listening every 60 seconds. + if (!(DateTime.Now.Subtract(_lastListenerCheck).TotalSeconds > 60)) return; if (!IsListening()) @@ -90,17 +126,18 @@ public void EnsureListening() { public bool IsListening() { try { - bool gotPing = false; - EventHandler handler = (sender, e) => gotPing = true; + var resetEvent = new AutoResetEvent(false); + EventHandler handler = (sender, e) => resetEvent.Set(); + Ping -= handler; Ping += handler; using (IRedisClient client = _redisClientsManager.GetClient()) client.PublishMessage(NotifySignalRAction.NOTIFICATION_CHANNEL_KEY, "ping"); - Thread.Sleep(50); + bool success = resetEvent.WaitOne(2000); Ping -= handler; - return gotPing; + return success; } catch (Exception) { return false; } @@ -206,5 +243,35 @@ public void NewError(string organizationId, string projectId, string stackId, bo context.Clients.Group(organizationId).newError(projectId, stackId, isHidden, isFixed, is404); _cacheClient.Set(String.Concat("SignalR.Org.", organizationId), DateTime.Now); } + + public void WentOverHourlyLimit(string organizationId) { + if (!Settings.Current.EnableSignalR) + return; + + if (GlobalHost.ConnectionManager == null) + return; + + IHubContext context = GlobalHost.ConnectionManager.GetHubContext(); + + if (context == null) + return; + + context.Clients.Group(organizationId).wentOverHourlyLimit(organizationId); + } + + public void WentOverMonthlyLimit(string organizationId) { + if (!Settings.Current.EnableSignalR) + return; + + if (GlobalHost.ConnectionManager == null) + return; + + IHubContext context = GlobalHost.ConnectionManager.GetHubContext(); + + if (context == null) + return; + + context.Clients.Group(organizationId).wentOverMonthlyLimit(organizationId); + } } } \ No newline at end of file diff --git a/Source/App/Models/Organization/OrganizationInfoModel.cs b/Source/App/Models/Organization/OrganizationInfoModel.cs new file mode 100644 index 0000000000..67f40a3ad0 --- /dev/null +++ b/Source/App/Models/Organization/OrganizationInfoModel.cs @@ -0,0 +1,30 @@ +using System; +using Exceptionless.Models; + +namespace Exceptionless.App.Models.Organization { + public class OrganizationInfoModel { + public string Id { get; set; } + public string Name { get; set; } + public string StripeCustomerId { get; set; } + public string PlanId { get; set; } + public string CardLast4 { get; set; } + public DateTime? SubscribeDate { get; set; } + public DateTime? BillingChangeDate { get; set; } + public string BillingChangedByUserId { get; set; } + public BillingStatus BillingStatus { get; set; } + public decimal BillingPrice { get; set; } + public bool IsSuspended { get; set; } + public string SuspensionCode { get; set; } + public string SuspensionNotes { get; set; } + public DateTime? SuspensionDate { get; set; } + public string SuspendedByUserId { get; set; } + public int ProjectCount { get; set; } + public long StackCount { get; set; } + public long ErrorCount { get; set; } + public DateTime LastErrorDate { get; set; } + public long TotalErrorCount { get; set; } + + public bool IsOverHourlyLimit { get; set; } + public bool IsOverMonthlyLimit { get; set; } + } +} \ No newline at end of file diff --git a/Source/App/Models/Project/ProjectModel.cs b/Source/App/Models/Project/ProjectModel.cs index bccb8b1b95..6f0e6247f9 100644 --- a/Source/App/Models/Project/ProjectModel.cs +++ b/Source/App/Models/Project/ProjectModel.cs @@ -46,6 +46,9 @@ public class ProjectModel { [Display(Name = "Configuration Settings")] public ClientConfiguration Configuration { get; set; } + [Display(Name = "Automatically delete bot data")] + public bool DeleteBotDataEnabled { get; set; } + public NotificationMode Mode { get; set; } public bool SendDailySummary { get; set; } public bool ReportCriticalErrors { get; set; } diff --git a/Source/App/Models/Stats/BillingPlanStatsModel.cs b/Source/App/Models/Stats/BillingPlanStatsModel.cs index 12f23b5211..af1d07bdee 100644 --- a/Source/App/Models/Stats/BillingPlanStatsModel.cs +++ b/Source/App/Models/Stats/BillingPlanStatsModel.cs @@ -19,6 +19,10 @@ public class BillingPlanStatsModel { public int MediumYearlyTotal { get; set; } public int LargeTotal { get; set; } public int LargeYearlyTotal { get; set; } + public int ExtraLargeTotal { get; set; } + public int ExtraLargeYearlyTotal { get; set; } + public int EnterpriseTotal { get; set; } + public int EnterpriseYearlyTotal { get; set; } public decimal MonthlyTotal { get; set; } public decimal YearlyTotal { get; set; } public int MonthlyTotalAccounts { get; set; } diff --git a/Source/App/Scripts/App/Account/BillingPlanModal.ts b/Source/App/Scripts/App/Account/BillingPlanModal.ts index fe6de3989b..4f1ef42a40 100644 --- a/Source/App/Scripts/App/Account/BillingPlanModal.ts +++ b/Source/App/Scripts/App/Account/BillingPlanModal.ts @@ -39,6 +39,7 @@ module exceptionless.account { var cardNumber = $('#cardNumber').val(); var cardName = $('#cardName').val(); var cardCvc = $('#cardCVC').val(); + var couponId = $('#couponId').val(); if (this.billingInfo.selectedOrganization.planId === Constants.FREE_PLAN_ID && planId === Constants.FREE_PLAN_ID) { $("#change-plan-button").prop('disabled', false); @@ -78,11 +79,11 @@ module exceptionless.account { $(".payment-message").text(response.error.message); $("#change-plan-button").prop('disabled', false); } else { - this.changePlan(false, organizationId, planId, response.id, response.card.last4); + this.changePlan(false, organizationId, planId, response.id, response.card.last4, couponId); } }); } else { - this.changePlan(false, organizationId, planId); + this.changePlan(false, organizationId, planId, null, null, couponId); } return false; @@ -93,11 +94,11 @@ module exceptionless.account { }); } - private changePlan(hasAdminRole: boolean, organizationId: string, planId: string, stripeToken?: string, last4?: string) { + private changePlan(hasAdminRole: boolean, organizationId: string, planId: string, stripeToken?: string, last4?: string, couponId?: string) { $.ajax({ type: 'POST', url: hasAdminRole ? '/admin/changeplan' : '/organization/changeplan', - data: { organizationId: organizationId, planId: planId, stripeToken: stripeToken, last4: last4 }, + data: { organizationId: organizationId, planId: planId, stripeToken: stripeToken, last4: last4, couponId: couponId }, success: (data) => { if (!data.Success) { App.showErrorNotification('Error changing your plan.
Message: ' + data.Message); @@ -133,7 +134,8 @@ module exceptionless.account { this.cardMode = !org.cardLast4 ? 'new' : 'existing'; var currentPlan: account.BillingPlan = ko.utils.arrayFirst(App.plans(), (plan: account.BillingPlan) => plan.id === this.selectedOrganizationPlan.id); - var upsell: account.BillingPlan = ko.utils.arrayFirst(App.plans(), (plan: account.BillingPlan) => plan.price > this.selectedOrganizationPlan.price); + var currentPlanIndex: number = App.plans.indexOf(currentPlan); + var upsell: account.BillingPlan = App.plans().length > currentPlanIndex + 1 ? App.plans()[currentPlanIndex + 1] : currentPlan; this.selectedPlan = upsell ? upsell : currentPlan ? currentPlan : this.plans[1]; }); diff --git a/Source/App/Scripts/App/Admin/DashboardViewModel.ts b/Source/App/Scripts/App/Admin/DashboardViewModel.ts index 014eca6ea9..98ecc264d3 100644 --- a/Source/App/Scripts/App/Admin/DashboardViewModel.ts +++ b/Source/App/Scripts/App/Admin/DashboardViewModel.ts @@ -11,6 +11,10 @@ module exceptionless.admin { mediumYearlyTotal = ko.observable(0); largeTotal = ko.observable(0); largeYearlyTotal = ko.observable(0); + extraLargeTotal = ko.observable(0); + extraLargeYearlyTotal = ko.observable(0); + enterpriseTotal = ko.observable(0); + enterpriseYearlyTotal = ko.observable(0); monthlyTotal = ko.observable(0); yearlyTotal = ko.observable(0); @@ -40,6 +44,10 @@ module exceptionless.admin { this.mediumYearlyTotal(data.MediumYearlyTotal); this.largeTotal(data.LargeTotal); this.largeYearlyTotal(data.LargeYearlyTotal); + this.extraLargeTotal(data.ExtraLargeTotal); + this.extraLargeYearlyTotal(data.ExtraLargeYearlyTotal); + this.enterpriseTotal(data.EnterpriseTotal); + this.enterpriseYearlyTotal(data.EnterpriseYearlyTotal); this.monthlyTotal(data.MonthlyTotal); this.yearlyTotal(data.YearlyTotal); diff --git a/Source/App/Scripts/App/App.ts b/Source/App/Scripts/App/App.ts index fc67f22329..40d42ef86b 100644 --- a/Source/App/Scripts/App/App.ts +++ b/Source/App/Scripts/App/App.ts @@ -13,6 +13,8 @@ module exceptionless { public static onProjectUpdated = ko.observable<{ projectId: string; }>(); public static onStackUpdated = ko.observable<{ projectId: string; id: string; isHidden: boolean; isFixed: boolean; is404: boolean; }>(); public static onNewError = ko.observable<{ projectId: string; stackId: string; isHidden: boolean; isFixed: boolean; is404: boolean; }>(); + public static onWentOverHourlyLimit = ko.observable<{ organizationId: string; }>(); + public static onWentOverMonthlyLimit = ko.observable<{ organizationId: string; }>(); public static organizations = ko.observableArray([]); private static _previousOrganization: models.Organization; @@ -87,8 +89,8 @@ module exceptionless { } }); - (App).selectedOrganization.subscribe(o => App._previousOrganization = o, null, 'beforeChange'); - (App).selectedOrganization.subscribe((o)=> { + App.selectedOrganization.subscribe(o => App._previousOrganization = o, null, 'beforeChange'); + App.selectedOrganization.subscribe((o)=> { if (App._previousOrganization && !StringUtil.isNullOrEmpty(App._previousOrganization.id) && o @@ -97,11 +99,29 @@ module exceptionless { && (App._previousOrganization.isSuspended !== o.isSuspended)) { location.reload(); } + + $('#free-plan-notification').hide(); + $('#monthly-limit-notification').hide(); + $('#hourly-limit-notification').hide(); + + if (o.isOverMonthlyLimit) + $('#monthly-limit-notification').show(); + else if (o.isOverHourlyLimit) + $('#hourly-limit-notification').show(); + else if (o.planId === Constants.FREE_PLAN_ID) + $('#free-plan-notification').show(); + }); + + App.onNewError.subscribe(() => { + $('#hourly-limit-notification').hide(); + $('#monthly-limit-notification').hide(); }); App.onPlanChanged.subscribe(() => App.refreshViewModelData()); App.onOrganizationUpdated.subscribe(() => App.refreshViewModelData()); App.onProjectUpdated.subscribe(() => App.refreshViewModelData()); + App.onWentOverHourlyLimit.subscribe(() => App.refreshViewModelData()); + App.onWentOverMonthlyLimit.subscribe(() => App.refreshViewModelData()); App.refreshViewModelData(); @@ -143,10 +163,9 @@ module exceptionless { var plans: account.BillingPlan[] = []; $.each(data.BillingInfo, (index, p) => plans.push(new account.BillingPlan(p.Id, p.Name, p.Description, p.Price, p.HasPremiumFeatures, p.MaxProjects, p.MaxErrors, p.MaxPerStack, p.MaxUsers, p.StatRetention, p.IsHidden))); App.plans(plans); - App.plans.sort((a: account.BillingPlan, b: account.BillingPlan) => { return a.price > b.price ? 1 : -1; }); var organizations: models.Organization[] = []; - $.each(data.Organizations, (index, o) => organizations.push(new models.Organization(o.Id, o.Name, o.ProjectCount, o.StackCount, o.ErrorCount, o.TotalErrorCount, o.LastErrorDate, o.SubscribeDate, o.BillingChangeDate, o.BillingChangedByUserId, o.BillingStatus, o.BillingPrice, o.PlanId, o.CardLast4, o.StripeCustomerId, o.IsSuspended, o.SuspensionCode, o.SuspensionDate, o.SuspendedByUserId, o.SuspensionNotes))); + $.each(data.Organizations, (index, o) => organizations.push(new models.Organization(o.Id, o.Name, o.ProjectCount, o.StackCount, o.ErrorCount, o.TotalErrorCount, o.LastErrorDate, o.SubscribeDate, o.BillingChangeDate, o.BillingChangedByUserId, o.BillingStatus, o.BillingPrice, o.PlanId, o.CardLast4, o.StripeCustomerId, o.IsSuspended, o.SuspensionCode, o.SuspensionDate, o.SuspendedByUserId, o.SuspensionNotes, o.IsOverHourlyLimit, o.IsOverMonthlyLimit))); App.organizations(organizations); App.organizations.sort((a: models.Organization, b: models.Organization) => { return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1; }); @@ -524,6 +543,16 @@ module exceptionless { App.onNewError({ projectId: projectId, stackId: stackId, isHidden: isHidden, isFixed: isFixed, is404: is404 }); }; + + notifier.client.wentOverHourlyLimit = (organizationId: string) => { + App.onWentOverHourlyLimit({ organizationId: organizationId }); + }; + + + notifier.client.wentOverMonthlyLimit = (organizationId: string) => { + App.onWentOverMonthlyLimit({ organizationId: organizationId }); + }; + $.connection.hub.start(); } } diff --git a/Source/App/Scripts/App/Error/DashboardViewModel.ts b/Source/App/Scripts/App/Error/DashboardViewModel.ts index 528ae39e6c..3ab205cdf6 100644 --- a/Source/App/Scripts/App/Error/DashboardViewModel.ts +++ b/Source/App/Scripts/App/Error/DashboardViewModel.ts @@ -22,12 +22,6 @@ module exceptionless.error { this._navigationViewModel = new NavigationViewModel(navigationElementId, null, defaultProjectId); App.onPlanChanged.subscribe(() => window.location.reload()); - App.selectedPlan.subscribe((plan: account.BillingPlan) => { - $('#free-plan-notification').hide(); - if (plan.id === Constants.FREE_PLAN_ID) - $('#free-plan-notification').show(); - }); - window.addEventListener('hashchange', (hashChangeEvent: any) => { this.currentTabHash(location.hash); }); diff --git a/Source/App/Scripts/App/Error/OccurrenceNotFoundViewModel.ts b/Source/App/Scripts/App/Error/OccurrenceNotFoundViewModel.ts index d42c542d59..4326d204f5 100644 --- a/Source/App/Scripts/App/Error/OccurrenceNotFoundViewModel.ts +++ b/Source/App/Scripts/App/Error/OccurrenceNotFoundViewModel.ts @@ -9,13 +9,6 @@ module exceptionless.error { this._navigationViewModel = new NavigationViewModel(navigationElementId, null, defaultProjectId); App.onPlanChanged.subscribe(() => window.location.reload()); - - App.selectedPlan.subscribe((plan: account.BillingPlan) => { - $('#free-plan-notification').hide(); - if (plan.id === Constants.FREE_PLAN_ID) - $('#free-plan-notification').show(); - }); - this.applyBindings(); } } diff --git a/Source/App/Scripts/App/Models/Organization.ts b/Source/App/Scripts/App/Models/Organization.ts index a66036e329..cd3f22c13c 100644 --- a/Source/App/Scripts/App/Models/Organization.ts +++ b/Source/App/Scripts/App/Models/Organization.ts @@ -26,7 +26,10 @@ module exceptionless.models { suspendedByUserId: string; suspensionNotes: string; - constructor(id: string, name: string, projectCount: number, stackCount: number, errorCount: number, totalErrorCount: number, lastErrorDate?: Date, subscribeDate?: Date, billingChangeDate?: Date, billingChangedByUserId?: string, billingStatus?: number, billingPrice?:number, planId?: string, cardLast4?: string, stripeCustomerId?: string, isSuspended?: boolean, suspensionCode?: string, suspensionDate?: Date, suspendedByUserId?: string, suspensionNotes?: string) { + isOverHourlyLimit: boolean; + isOverMonthlyLimit: boolean; + + constructor(id: string, name: string, projectCount: number, stackCount: number, errorCount: number, totalErrorCount: number, lastErrorDate?: Date, subscribeDate?: Date, billingChangeDate?: Date, billingChangedByUserId?: string, billingStatus?: number, billingPrice?: number, planId?: string, cardLast4?: string, stripeCustomerId?: string, isSuspended?: boolean, suspensionCode?: string, suspensionDate?: Date, suspendedByUserId?: string, suspensionNotes?: string, isOverHourlyLimit?: boolean, isOverMonthlyLimit?: boolean) { this.id = id; this.name = name; this.projectCount = projectCount; @@ -59,6 +62,9 @@ module exceptionless.models { if (suspensionDate) this.suspensionDate = suspensionDate; + this.isOverHourlyLimit = isOverHourlyLimit == true; + this.isOverMonthlyLimit = isOverMonthlyLimit == true; + ko.track(this); } diff --git a/Source/App/Scripts/App/Organization/EditViewModel.ts b/Source/App/Scripts/App/Organization/EditViewModel.ts index 8f513b4e62..1938556eb0 100644 --- a/Source/App/Scripts/App/Organization/EditViewModel.ts +++ b/Source/App/Scripts/App/Organization/EditViewModel.ts @@ -20,11 +20,19 @@ module exceptionless.organization { App.organizations.subscribe((organizations) => { $('#free-plan-notification').hide(); + $('#hourly-limit-notification').hide(); + $('#monthly-limit-notification').hide(); var org = ko.utils.arrayFirst(organizations, (o) => (o).id === organizationId); - if (org != null && org.planId === Constants.FREE_PLAN_ID) { + if (org == null) + return; + + if (org.isOverHourlyLimit) + $('#monthly-limit-notification').show(); + else if (org.isOverHourlyLimit) + $('#hourly-limit-notification').show(); + else if (org.planId === Constants.FREE_PLAN_ID) $('#free-plan-notification').show(); - } }); // TODO Optmize this into only loading the data when the tab changes and or consolidate it into one request. @@ -60,6 +68,9 @@ module exceptionless.organization { this.applyBindings(); this.populateViewModel(data); + + if (data) + this.loading(false); } public populateViewModel(data?: any) { diff --git a/Source/App/Scripts/App/Project/DashboardViewModel.ts b/Source/App/Scripts/App/Project/DashboardViewModel.ts index dbdc7548ed..6f300aa6b5 100644 --- a/Source/App/Scripts/App/Project/DashboardViewModel.ts +++ b/Source/App/Scripts/App/Project/DashboardViewModel.ts @@ -33,6 +33,8 @@ module exceptionless.project { this.refreshViewModelData(); }); + App.onNewError.subscribe(() => $(Constants.NOTIFICATION_SYSTEM_ID).html('')); + this._pagedRecentErrorsViewModel = new error.PagedRecentErrorsViewModel(recentErrorStackElementId, this.projectListViewModel, this.filterViewModel, pageSize, autoUpdate); this._pagedFrequentErrorStackStatsViewModel = new stats.PagedFrequentErrorStackStatsViewModel(frequentErrorStackElementId, this.projectListViewModel, this.filterViewModel, pageSize, autoUpdate, this._frequentErrorStacks); diff --git a/Source/App/Scripts/App/Project/ManageViewModel.ts b/Source/App/Scripts/App/Project/ManageViewModel.ts index f9c0b4dbef..2c6956ba81 100644 --- a/Source/App/Scripts/App/Project/ManageViewModel.ts +++ b/Source/App/Scripts/App/Project/ManageViewModel.ts @@ -16,6 +16,7 @@ module exceptionless.project { reportKnownBotErrors = ko.observable(false); releases = ko.observableArray([]); apiKeys = ko.observableArray([]); + deleteBotDataEnabled = ko.observable(true); configuration = ko.observableArray([]); dataExclusions = ko.observable(''); @@ -49,12 +50,6 @@ module exceptionless.project { this._navigationViewModel = new NavigationViewModel(navigationElementId, null, projectId); TabUtil.init(tabElementId); - App.selectedPlan.subscribe((plan: account.BillingPlan) => { - $('#free-plan-notification').hide(); - if (plan.id === Constants.FREE_PLAN_ID) - $('#free-plan-notification').show(); - }); - this.saveDirtyFlag = new ko.DirtyFlag([this.name, this.customContent]); this.saveCommand = ko.asyncCommand({ canExecute: (isExecuting) => { @@ -104,7 +99,7 @@ module exceptionless.project { return !isExecuting && ko.utils.arrayFirst(this.configuration(), (item: models.KeyValuePair) => !item.isValid) === null; }, execute: (complete) => { - var data = { Configuration: { Settings: {} } }; + var data = { DeleteBotDataEnabled: this.deleteBotDataEnabled(), Configuration: { Settings: {} } }; ko.utils.arrayForEach(this.configuration(), (item: models.KeyValuePair) => { if (!item.isValid) return; @@ -237,6 +232,7 @@ module exceptionless.project { } this.apiKeys(apiKeys); + this.deleteBotDataEnabled(data.DeleteBotDataEnabled); var settings: models.KeyValuePair[] = []; for (var name in data.Configuration.Settings) { if (name === '@@DataExclusions') { diff --git a/Source/App/Scripts/App/ViewModels/Base/PagedReportViewModelBase.ts b/Source/App/Scripts/App/ViewModels/Base/PagedReportViewModelBase.ts index a5c7447482..d06cd23ac7 100644 --- a/Source/App/Scripts/App/ViewModels/Base/PagedReportViewModelBase.ts +++ b/Source/App/Scripts/App/ViewModels/Base/PagedReportViewModelBase.ts @@ -85,12 +85,6 @@ module exceptionless { App.onStackUpdated.subscribe((stack) => this.onStackUpdated(stack)); App.onNewError.subscribe((error) => this.onNewError(error)); - - App.selectedPlan.subscribe((plan: account.BillingPlan) => { - $('#free-plan-notification').hide(); - if (plan.id === Constants.FREE_PLAN_ID) - $('#free-plan-notification').show(); - }); } public onStackUpdated(stack) { diff --git a/Source/App/Scripts/App/ViewModels/Base/ReportViewModelBase.ts b/Source/App/Scripts/App/ViewModels/Base/ReportViewModelBase.ts index e64107b9a0..78d9de11ed 100644 --- a/Source/App/Scripts/App/ViewModels/Base/ReportViewModelBase.ts +++ b/Source/App/Scripts/App/ViewModels/Base/ReportViewModelBase.ts @@ -20,12 +20,6 @@ module exceptionless { App.onStackUpdated.subscribe((stack) => this.onStackUpdated(stack)); App.onNewError.subscribe((error) => this.onNewError(error)); - - App.selectedPlan.subscribe((plan: account.BillingPlan) => { - $('#free-plan-notification').hide(); - if (plan.id === Constants.FREE_PLAN_ID) - $('#free-plan-notification').show(); - }); } public onStackUpdated(stack) { diff --git a/Source/App/Scripts/_references.js b/Source/App/Scripts/_references.js index a94a8a6d40..c943fcbc78 100644 Binary files a/Source/App/Scripts/_references.js and b/Source/App/Scripts/_references.js differ diff --git a/Source/App/Scripts/jquery-2.1.0.min.js b/Source/App/Scripts/jquery-2.1.0.min.js deleted file mode 100644 index cbe6abe59a..0000000000 --- a/Source/App/Scripts/jquery-2.1.0.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v2.1.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m=a.document,n="2.1.0",o=function(a,b){return new o.fn.init(a,b)},p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};o.fn=o.prototype={jquery:n,constructor:o,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=o.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return o.each(this,a,b)},map:function(a){return this.pushStack(o.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},o.extend=o.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||o.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(o.isPlainObject(d)||(e=o.isArray(d)))?(e?(e=!1,f=c&&o.isArray(c)?c:[]):f=c&&o.isPlainObject(c)?c:{},g[b]=o.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},o.extend({expando:"jQuery"+(n+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===o.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isPlainObject:function(a){if("object"!==o.type(a)||a.nodeType||o.isWindow(a))return!1;try{if(a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(b){return!1}return!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=o.trim(a),a&&(1===a.indexOf("use strict")?(b=m.createElement("script"),b.text=a,m.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":k.call(a)},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?o.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),o.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||o.guid++,f):void 0},now:Date.now,support:l}),o.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=o.type(a);return"function"===c||o.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);o.find=t,o.expr=t.selectors,o.expr[":"]=o.expr.pseudos,o.unique=t.uniqueSort,o.text=t.getText,o.isXMLDoc=t.isXML,o.contains=t.contains;var u=o.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(o.isFunction(b))return o.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return o.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return o.filter(b,a,c);b=o.filter(b,a)}return o.grep(a,function(a){return g.call(b,a)>=0!==c})}o.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?o.find.matchesSelector(d,a)?[d]:[]:o.find.matches(a,o.grep(b,function(a){return 1===a.nodeType}))},o.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(o(a).filter(function(){for(b=0;c>b;b++)if(o.contains(e[b],this))return!0}));for(b=0;c>b;b++)o.find(a,e[b],d);return d=this.pushStack(c>1?o.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?o(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=o.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof o?b[0]:b,o.merge(this,o.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:m,!0)),v.test(c[1])&&o.isPlainObject(b))for(c in b)o.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=m.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=m,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):o.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(o):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),o.makeArray(a,this))};A.prototype=o.fn,y=o(m);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};o.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&o(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),o.fn.extend({has:function(a){var b=o(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(o.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?o(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&o.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?o.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(o(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(o.unique(o.merge(this.get(),o(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}o.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return o.dir(a,"parentNode")},parentsUntil:function(a,b,c){return o.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return o.dir(a,"nextSibling")},prevAll:function(a){return o.dir(a,"previousSibling")},nextUntil:function(a,b,c){return o.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return o.dir(a,"previousSibling",c)},siblings:function(a){return o.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return o.sibling(a.firstChild)},contents:function(a){return a.contentDocument||o.merge([],a.childNodes)}},function(a,b){o.fn[a]=function(c,d){var e=o.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=o.filter(d,e)),this.length>1&&(C[a]||o.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return o.each(a.match(E)||[],function(a,c){b[c]=!0}),b}o.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):o.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){o.each(b,function(b,c){var d=o.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&o.each(arguments,function(a,b){var c;while((c=o.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?o.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},o.extend({Deferred:function(a){var b=[["resolve","done",o.Callbacks("once memory"),"resolved"],["reject","fail",o.Callbacks("once memory"),"rejected"],["notify","progress",o.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return o.Deferred(function(c){o.each(b,function(b,f){var g=o.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&o.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?o.extend(a,d):d}},e={};return d.pipe=d.then,o.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&o.isFunction(a.promise)?e:0,g=1===f?a:o.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&o.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;o.fn.ready=function(a){return o.ready.promise().done(a),this},o.extend({isReady:!1,readyWait:1,holdReady:function(a){a?o.readyWait++:o.ready(!0)},ready:function(a){(a===!0?--o.readyWait:o.isReady)||(o.isReady=!0,a!==!0&&--o.readyWait>0||(H.resolveWith(m,[o]),o.fn.trigger&&o(m).trigger("ready").off("ready")))}});function I(){m.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),o.ready()}o.ready.promise=function(b){return H||(H=o.Deferred(),"complete"===m.readyState?setTimeout(o.ready):(m.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},o.ready.promise();var J=o.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===o.type(c)){e=!0;for(h in c)o.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,o.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(o(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};o.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=o.expando+Math.random()}K.uid=1,K.accepts=o.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,o.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(o.isEmptyObject(f))o.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,o.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{o.isArray(b)?d=b.concat(b.map(o.camelCase)):(e=o.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!o.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?o.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}o.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),o.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length; -while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=o.camelCase(d.slice(5)),P(f,d,e[d]));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=o.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),o.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||o.isArray(c)?d=L.access(a,b,o.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=o.queue(a,b),d=c.length,e=c.shift(),f=o._queueHooks(a,b),g=function(){o.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:o.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),o.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";l.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return m.activeElement}catch(a){}}o.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=o.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof o!==U&&o.event.triggered!==b.type?o.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n&&(l=o.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=o.event.special[n]||{},k=o.extend({type:n,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&o.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(n,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),o.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n){l=o.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||o.removeEvent(a,n,r.handle),delete i[n])}else for(n in i)o.event.remove(a,n+b[j],c,d,!0);o.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,p=[d||m],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||m,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+o.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[o.expando]?b:new o.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:o.makeArray(c,[b]),n=o.event.special[q]||{},e||!n.trigger||n.trigger.apply(d,c)!==!1)){if(!e&&!n.noBubble&&!o.isWindow(d)){for(i=n.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||m)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:n.bindType||q,l=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),l&&l.apply(g,c),l=k&&g[k],l&&l.apply&&o.acceptData(g)&&(b.result=l.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||n._default&&n._default.apply(p.pop(),c)!==!1||!o.acceptData(d)||k&&o.isFunction(d[q])&&!o.isWindow(d)&&(h=d[k],h&&(d[k]=null),o.event.triggered=q,d[q](),o.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=o.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=o.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=o.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((o.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?o(e,this).index(i)>=0:o.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return o.nodeName(a,"table")&&o.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)o.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=o.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&o.nodeName(a,b)?o.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}o.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=o.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||o.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,n=a.length;n>m;m++)if(e=a[m],e||0===e)if("object"===o.type(e))o.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;o.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===o.inArray(e,d))&&(i=o.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f,g,h=o.event.special,i=0;void 0!==(c=a[i]);i++){if(o.acceptData(c)&&(f=c[L.expando],f&&(b=L.cache[f]))){if(d=Object.keys(b.events||{}),d.length)for(g=0;void 0!==(e=d[g]);g++)h[e]?o.event.remove(c,e):o.removeEvent(c,e,b.handle);L.cache[f]&&delete L.cache[f]}delete M.cache[c[M.expando]]}}}),o.fn.extend({text:function(a){return J(this,function(a){return void 0===a?o.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?o.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||o.cleanData(ob(c)),c.parentNode&&(b&&o.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(o.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return o.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(o.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,o.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,n=k-1,p=a[0],q=o.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(c=o.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=o.map(ob(c,"script"),kb),g=f.length;k>j;j++)h=c,j!==n&&(h=o.clone(h,!0,!0),g&&o.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,o.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&o.contains(i,h)&&(h.src?o._evalUrl&&o._evalUrl(h.src):o.globalEval(h.textContent.replace(hb,"")))}return this}}),o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){o.fn[a]=function(a){for(var c,d=[],e=o(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),o(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d=o(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:o.css(d[0],"display");return d.detach(),e}function tb(a){var b=m,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||o("