diff --git a/.gitignore b/.gitignore index d5996225..0b008bcb 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,6 @@ Thumbs.db obj/ [Rr]elease*/ _ReSharper*/ -[Tt]est[Rr]esult* */packages/*/ **/.vs/* **/packages/*/ diff --git a/src/Server/Coderr.Server.Api.Client/Coderr.Server.Api.Client.csproj b/src/Server/Coderr.Server.Api.Client/Coderr.Server.Api.Client.csproj index 812663f5..7ef44123 100644 --- a/src/Server/Coderr.Server.Api.Client/Coderr.Server.Api.Client.csproj +++ b/src/Server/Coderr.Server.Api.Client/Coderr.Server.Api.Client.csproj @@ -1,28 +1,28 @@  netstandard2.0 - 1.1.0 + 2.1.3 true bin\$(Configuration)\$(TargetFramework)\Coderr.Server.Api.Client.xml + Coderr.Server.Api.Client + Coderr.Server.Api.Client Coderr.Server.Api.Client 1TCompany AB - API client for codeRR Server. + API client for Coderr Server. true Converted to vstudio 2017 csproj format Copyright 2017 © 1TCompany AB. All rights reserved. logger exceptions analysis .net-core netstandard - http://coderrapp.com/images/nuget_icon.png - https://github.com/coderr/coderr.server + https://coderr.io/images/nuget_icon.png + https://github.com/coderrio/coderr.server git - https://raw.githubusercontent.com/coderr/codeRR.Server/master/LICENSE - https://coderrapp.com - Coderr.Server.Api.Client - Coderr.Server.Api.Client + Apache-2.0 + https://coderr.io - + diff --git a/src/Server/Coderr.Server.Api/Coderr.Server.Api.csproj b/src/Server/Coderr.Server.Api/Coderr.Server.Api.csproj index 1100355d..5169e543 100644 --- a/src/Server/Coderr.Server.Api/Coderr.Server.Api.csproj +++ b/src/Server/Coderr.Server.Api/Coderr.Server.Api.csproj @@ -1,31 +1,31 @@  netstandard2.0 - 1.1.0 + 2.1.4 + bin\$(Configuration)\$(TargetFramework)\Coderr.Server.Api.xml + Added the whitelist API + + Coderr.Server.Api Coderr.Server.Api true - bin\$(Configuration)\$(TargetFramework)\codeRR.Server.Api.xml - - - codeRR.Server.Api + Coderr.Server.Api 1TCompany AB - CQRS API definition for codeRR Server. + API object definitions for Coderr Server, use the ApiClient package to communicate with the Coderr Server. true - First release - Copyright 2017 © 1TCompany AB. All rights reserved. + Copyright 2019 © 1TCompany AB. All rights reserved. logger exceptions analysis .net-core netstandard - http://coderrapp.com/images/nuget_icon.png - https://github.com/coderr/coderr.server + https://coderr.io/images/nuget_icon.png + https://github.com/coderrio/coderr.server git - https://raw.githubusercontent.com/coderr/codeRR.Server/master/LICENSE - https://coderrapp.com + Apache-2.0 + https://coderr.io 1701;1702;1705;1591 - + diff --git a/src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/AddEntry.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/AddEntry.cs new file mode 100644 index 00000000..172ed4c1 --- /dev/null +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/AddEntry.cs @@ -0,0 +1,24 @@ +namespace Coderr.Server.Api.Modules.Whitelists.Commands +{ + /// + /// Add a domain that may post error reports without using a shared secret (javascript applications) + /// + [Command] + public class AddEntry + { + /// + /// Applications that the domain is allowed for. + /// + public int[] ApplicationIds { get; set; } = new int[0]; + + /// + /// For instance yourdomain.com. + /// + public string DomainName { get; set; } + + /// + /// To manually specify which IP addresses the domain matches. + /// + public string[] IpAddresses { get; set; } = new string[0]; + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/EditEntry.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/EditEntry.cs new file mode 100644 index 00000000..f39fa81f --- /dev/null +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/EditEntry.cs @@ -0,0 +1,25 @@ +namespace Coderr.Server.Api.Modules.Whitelists.Commands +{ + /// + /// Edit a domain that may post error reports without using a shared secret (javascript applications) + /// + [Command] + public class EditEntry + { + /// + /// PK for the entry being edited. + /// + public int Id { get; set; } + + /// + /// Applications that the domain is allowed for. + /// + public int[] ApplicationIds { get; set; } = new int[0]; + + + /// + /// Only manually specified ip addresses. + /// + public string[] IpAddresses { get; set; } = new string[0]; + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Commands/RemoveEntry.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/RemoveEntry.cs similarity index 75% rename from src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Commands/RemoveEntry.cs rename to src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/RemoveEntry.cs index 5c586277..33d83a6b 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Commands/RemoveEntry.cs +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Commands/RemoveEntry.cs @@ -1,8 +1,9 @@ -namespace Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Whitelists.Commands +namespace Coderr.Server.Api.Modules.Whitelists.Commands { /// /// Remove a previously added white list entry /// + [Command] public class RemoveEntry { /// diff --git a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntries.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntries.cs similarity index 71% rename from src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntries.cs rename to src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntries.cs index 2da8271d..759c4c90 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntries.cs +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntries.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Coderr.Server.Api; -using DotNetCqs; +using DotNetCqs; -namespace Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Whitelists.Queries +namespace Coderr.Server.Api.Modules.Whitelists.Queries { /// /// Get whitelist either by application id or DomainName diff --git a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntriesResult.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResult.cs similarity index 72% rename from src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntriesResult.cs rename to src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResult.cs index 6835fe74..b6e709ae 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntriesResult.cs +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResult.cs @@ -1,4 +1,4 @@ -namespace Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Whitelists.Queries +namespace Coderr.Server.Api.Modules.Whitelists.Queries { /// /// Result for . diff --git a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntriesResultItem.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItem.cs similarity index 53% rename from src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntriesResultItem.cs rename to src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItem.cs index a008500e..27b726f2 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Queries/GetWhitelistEntriesResultItem.cs +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItem.cs @@ -1,4 +1,4 @@ -namespace Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Whitelists.Queries +namespace Coderr.Server.Api.Modules.Whitelists.Queries { /// /// Entry for @@ -6,7 +6,8 @@ public class GetWhitelistEntriesResultItem { public int Id { get; set; } - public int? ApplicationId { get; set; } + public GetWhitelistEntriesResultItemIp[] IpAddresses { get; set; } + public GetWhitelistEntriesResultItemApp[] Applications { get; set; } public string DomainName { get; set; } } } \ No newline at end of file diff --git a/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItemApp.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItemApp.cs new file mode 100644 index 00000000..b43a9e73 --- /dev/null +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItemApp.cs @@ -0,0 +1,8 @@ +namespace Coderr.Server.Api.Modules.Whitelists.Queries +{ + public class GetWhitelistEntriesResultItemApp + { + public int ApplicationId { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItemIp.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItemIp.cs new file mode 100644 index 00000000..97845852 --- /dev/null +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/GetWhitelistEntriesResultItemIp.cs @@ -0,0 +1,11 @@ +using System; + +namespace Coderr.Server.Api.Modules.Whitelists.Queries +{ + public class GetWhitelistEntriesResultItemIp + { + public string Address { get; set; } + public DateTime UpdatedAtUtc { get; set; } + public ResultItemIpType Type { get; set; } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/ResultItemIpType.cs b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/ResultItemIpType.cs new file mode 100644 index 00000000..89c4db4d --- /dev/null +++ b/src/Server/Coderr.Server.Api/Modules/Whitelists/Queries/ResultItemIpType.cs @@ -0,0 +1,23 @@ +namespace Coderr.Server.Api.Modules.Whitelists.Queries +{ + /// + /// Typ of stored IP record. + /// + public enum ResultItemIpType + { + /// + /// Added when doing a lookup for the domain + /// + Lookup = 0, + + /// + /// Manually specified by the user + /// + Manual = 1, + + /// + /// We got a request from this IP and a lookup didn't match it. + /// + Denied = 2 + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.App/Coderr.Server.App.csproj b/src/Server/Coderr.Server.App/Coderr.Server.App.csproj index 1456298e..9dfd8b79 100644 --- a/src/Server/Coderr.Server.App/Coderr.Server.App.csproj +++ b/src/Server/Coderr.Server.App/Coderr.Server.App.csproj @@ -21,10 +21,11 @@ NU1701 - + + - + diff --git a/src/Server/Coderr.Server.App/Core/Incidents/Commands/CloseIncidentHandler.cs b/src/Server/Coderr.Server.App/Core/Incidents/Commands/CloseIncidentHandler.cs index f666092e..a76febf9 100644 --- a/src/Server/Coderr.Server.App/Core/Incidents/Commands/CloseIncidentHandler.cs +++ b/src/Server/Coderr.Server.App/Core/Incidents/Commands/CloseIncidentHandler.cs @@ -42,7 +42,7 @@ public async Task HandleAsync(IMessageContext context, CloseIncident command) if (command == null) throw new ArgumentNullException("command"); var incident = await _repository.GetAsync(command.IncidentId); - incident.Close(command.UserId, command.Solution); + incident.Close(command.UserId, command.Solution, command.ApplicationVersion); if (command.ShareSolution) incident.ShareSolution(); diff --git a/src/Server/Coderr.Server.App/Modules/Whitelists/IWhitelistRepository.cs b/src/Server/Coderr.Server.App/Modules/Whitelists/IWhitelistRepository.cs new file mode 100644 index 00000000..0031c6af --- /dev/null +++ b/src/Server/Coderr.Server.App/Modules/Whitelists/IWhitelistRepository.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Coderr.Server.App.Modules.Whitelists +{ + /// + /// Whitelists is used for reports that don't use a shared secret + /// + public interface IWhitelistRepository + { + Task FindIp(int applicationId, IPAddress address); + Task> FindWhitelists(int applicationId); + + Task SaveIp(WhitelistedDomainIp entry); + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.App/Modules/Whitelists/IWhitelistService.cs b/src/Server/Coderr.Server.App/Modules/Whitelists/IWhitelistService.cs new file mode 100644 index 00000000..eafd580a --- /dev/null +++ b/src/Server/Coderr.Server.App/Modules/Whitelists/IWhitelistService.cs @@ -0,0 +1,27 @@ +using System.Net; +using System.Threading.Tasks; + +namespace Coderr.Server.App.Modules.Whitelists +{ + /// + /// Used to validate origin of inbound requests when a shared secret is not used. + /// + public interface IWhitelistService + { + /// + /// Is domain white listed? + /// + /// AppKey used when receiving error reports. + /// IP address of the client reporting the error. + /// + Task Validate(string appKey, IPAddress remoteAddress); + + /// + /// Is domain white listed? + /// + /// Application that the error is reported for. + /// IP address of the client reporting the error. + /// + Task Validate(int applicationId, IPAddress remoteAddress); + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.App/Modules/Whitelists/IpType.cs b/src/Server/Coderr.Server.App/Modules/Whitelists/IpType.cs new file mode 100644 index 00000000..fd1f9528 --- /dev/null +++ b/src/Server/Coderr.Server.App/Modules/Whitelists/IpType.cs @@ -0,0 +1,23 @@ +namespace Coderr.Server.App.Modules.Whitelists +{ + /// + /// Typ of stored IP record. + /// + public enum IpType + { + /// + /// Added when doing a lookup for the domain + /// + Lookup = 0, + + /// + /// Manually specified by the user + /// + Manual = 1, + + /// + /// We got a request from this IP and a lookup didn't match it. + /// + Denied = 2 + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.App/Modules/Whitelists/Whitelist.cs b/src/Server/Coderr.Server.App/Modules/Whitelists/Whitelist.cs new file mode 100644 index 00000000..1dcd0922 --- /dev/null +++ b/src/Server/Coderr.Server.App/Modules/Whitelists/Whitelist.cs @@ -0,0 +1,28 @@ +namespace Coderr.Server.App.Modules.Whitelists +{ + /// + /// Domain that is allowed to report errors without + /// + public class Whitelist + { + /// + /// Domain name, must be an exact match. Can also be an IP address + /// + public string DomainName { get; set; } + + /// + /// PK + /// + public int Id { get; set; } + + /// + /// Addresses that have been stored for this domain + /// + public WhitelistedDomainIp[] IpAddresses { get; set; } + + /// + /// Applications that this whitelist is allowed for + /// + public int[] ApplicationIds { get; set; } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.App/Modules/Whitelists/WhitelistService.cs b/src/Server/Coderr.Server.App/Modules/Whitelists/WhitelistService.cs new file mode 100644 index 00000000..842d72f1 --- /dev/null +++ b/src/Server/Coderr.Server.App/Modules/Whitelists/WhitelistService.cs @@ -0,0 +1,118 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Coderr.Server.Abstractions.Boot; +using Coderr.Server.Domain.Core.Applications; +using DnsClient; +using DnsClient.Protocol; + +namespace Coderr.Server.App.Modules.Whitelists +{ + /// + /// Used to validate origin of inbound requests when a shared secret is not used. + /// + [ContainerService] + public class WhitelistService : IWhitelistService + { + private readonly IWhitelistRepository _repository; + private readonly IApplicationRepository _applicationRepository; + + public WhitelistService(IWhitelistRepository repository, IApplicationRepository applicationRepository) + { + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + _applicationRepository = applicationRepository; + } + + public async Task Validate(string appKey, IPAddress remoteAddress) + { + var app = await _applicationRepository.GetByKeyAsync(appKey); + return await Validate(app.Id, remoteAddress); + } + + /// + /// Is domain white listed? + /// + /// Application that the error is reported for. + /// IP address of the client reporting the error. + /// + public async Task Validate(int applicationId, IPAddress remoteAddress) + { + var ipEntry = await _repository.FindIp(applicationId, remoteAddress); + if (ipEntry != null) return ipEntry.IpType != IpType.Denied; + + var domains = await _repository.FindWhitelists(applicationId); + + // Allow nothing if the whitelist is empty + if (!domains.Any()) + return false; + + foreach (var domain in domains) + { + var found = await Lookup(domain, remoteAddress); + if (found) + return true; + } + + foreach (var domain in domains) + { + await _repository.SaveIp(new WhitelistedDomainIp + { + IpAddress = remoteAddress, + DomainId = domain.Id, + IpType = IpType.Denied, + StoredAtUtc = DateTime.UtcNow + }); + } + + return false; + } + + private async Task Lookup(Whitelist domain, IPAddress remoteAddress) + { + var found = false; + var lookup = new LookupClient(); + var result = await lookup.QueryAsync(domain.DomainName, QueryType.ANY); + foreach (var record in result.AllRecords) + { + switch (record) + { + case ARecord ipRecord: + if (domain.IpAddresses.Any(x => Equals(x.IpAddress, ipRecord.Address))) + continue; + + + if (remoteAddress.Equals(ipRecord.Address)) + found = true; + + await _repository.SaveIp(new WhitelistedDomainIp + { + DomainId = domain.Id, + IpAddress = ipRecord.Address, + IpType = IpType.Lookup, + StoredAtUtc = DateTime.UtcNow + }); + break; + + case AaaaRecord ip6Record: + if (domain.IpAddresses.Any(x => Equals(x.IpAddress, ip6Record.Address))) + continue; + + if (remoteAddress.Equals(ip6Record.Address)) + found = true; + + await _repository.SaveIp(new WhitelistedDomainIp + { + DomainId = domain.Id, + IpAddress = ip6Record.Address, + IpType = IpType.Lookup, + StoredAtUtc = DateTime.UtcNow + }); + break; + } + } + + return found; + } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.App/Modules/Whitelists/WhitelistedDomainIp.cs b/src/Server/Coderr.Server.App/Modules/Whitelists/WhitelistedDomainIp.cs new file mode 100644 index 00000000..36ed90fc --- /dev/null +++ b/src/Server/Coderr.Server.App/Modules/Whitelists/WhitelistedDomainIp.cs @@ -0,0 +1,36 @@ +using System; +using System.Net; + +namespace Coderr.Server.App.Modules.Whitelists +{ + /// + /// IP address that we have looked up (DNS lookup) for the specified domain entry. + /// + public class WhitelistedDomainIp + { + /// + /// + /// + public int Id { get; set; } + + /// + /// Domain that this entry is for. + /// + public int DomainId { get; set; } + + /// + /// Address that we found + /// + public IPAddress IpAddress { get; set; } + + /// + /// How this IP should be treated + /// + public IpType IpType { get; set; } + + /// + /// When this entry was stored. + /// + public DateTime StoredAtUtc { get; set; } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.Domain/Core/Incidents/Incident.cs b/src/Server/Coderr.Server.Domain/Core/Incidents/Incident.cs index c4381692..2e56626c 100644 --- a/src/Server/Coderr.Server.Domain/Core/Incidents/Incident.cs +++ b/src/Server/Coderr.Server.Domain/Core/Incidents/Incident.cs @@ -168,20 +168,27 @@ public void Assign(int userId, DateTime? when = null) /// /// AccountId for whoever wrote the solution /// Actual solution + /// All future reports are ignored if they are reported for app versions less that the specified one. /// When was the incident closed by the user? /// solution /// solvedBy - public void Close(int solvedBy, string solution, DateTime? when = null) + public void Close(int solvedBy, string solution, string correctedInVersion, DateTime? when = null) { if (solution == null) throw new ArgumentNullException("solution"); if (solvedBy <= 0) throw new ArgumentOutOfRangeException("solvedBy", solvedBy, "Must specify a solver."); + IgnoredUntilVersion = correctedInVersion; Solution = new IncidentSolution(solvedBy, solution); UpdatedAtUtc = DateTime.UtcNow; SolvedAtUtc = when ?? DateTime.UtcNow; State = IncidentState.Closed; } + /// + /// Version that the error is corrected in. All inbound error reports will be ignored if they are from older application versions. + /// + public string IgnoredUntilVersion { get; set; } + /// /// Do not want to store reports or receive notifications for this incident. /// diff --git a/src/Server/Coderr.Server.Infrastructure/Coderr.Server.Infrastructure.csproj b/src/Server/Coderr.Server.Infrastructure/Coderr.Server.Infrastructure.csproj index f09721cc..c495196e 100644 --- a/src/Server/Coderr.Server.Infrastructure/Coderr.Server.Infrastructure.csproj +++ b/src/Server/Coderr.Server.Infrastructure/Coderr.Server.Infrastructure.csproj @@ -5,7 +5,7 @@ Coderr.Server.Infrastructure - + diff --git a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Commands/AddDomain.cs b/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Commands/AddDomain.cs deleted file mode 100644 index 708b75d2..00000000 --- a/src/Server/Coderr.Server.ReportAnalyzer.Abstractions/Inbound/Whitelists/Commands/AddDomain.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Whitelists.Commands -{ - /// - /// Add a domain that may post error reports without using a shared secret (javascript applications) - /// - public class AddDomain - { - /// - /// Application that the domain is allowed for. - /// - /// - /// - /// ´null if allowed for all applications - /// - /// - public int? ApplicationId { get; set; } - - /// - /// For instance yourdomain.com. - /// - public string DomainName { get; set; } - } -} \ No newline at end of file diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Coderr.Server.ReportAnalyzer.csproj b/src/Server/Coderr.Server.ReportAnalyzer/Coderr.Server.ReportAnalyzer.csproj index c1470ec1..ab75b8dd 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer/Coderr.Server.ReportAnalyzer.csproj +++ b/src/Server/Coderr.Server.ReportAnalyzer/Coderr.Server.ReportAnalyzer.csproj @@ -6,8 +6,8 @@ $(DefaultItemExcludes);**\*.DotSettings; - - + + diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Feedback/Handlers/StoreFeedbackFromNewReports.cs b/src/Server/Coderr.Server.ReportAnalyzer/Feedback/Handlers/StoreFeedbackFromNewReports.cs index fa56c97e..00fee56e 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer/Feedback/Handlers/StoreFeedbackFromNewReports.cs +++ b/src/Server/Coderr.Server.ReportAnalyzer/Feedback/Handlers/StoreFeedbackFromNewReports.cs @@ -26,13 +26,13 @@ public async Task HandleAsync(IMessageContext context, ReportAddedToIncident e) { try { - var userInfo = Enumerable.FirstOrDefault(e.Report.ContextCollections, x => x.Name == "UserSuppliedInformation"); + var userInfo = e.Report.ContextCollections.FirstOrDefault(x => x.Name == "UserSuppliedInformation"); if (userInfo == null) return; userInfo.Properties.TryGetValue("Description", out var description); userInfo.Properties.TryGetValue("Email", out var email); - _logger.Debug($"storing feedback for report {e.Report.ReportId}: {email} {description}"); + _logger.Debug($"queueing feedback attached to report {e.Report.ReportId}: {email} {description}"); var cmd = new SubmitFeedback(e.Report.ReportId, e.Report.RemoteAddress ?? "") { Feedback = description, diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Handlers/ProcessFeedbackHandler.cs b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Handlers/ProcessFeedbackHandler.cs index 2f472879..6b527a2b 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Handlers/ProcessFeedbackHandler.cs +++ b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Handlers/ProcessFeedbackHandler.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Coderr.Server.Api.Core.Feedback.Commands; using DotNetCqs; -using Coderr.Server.Abstractions.Boot; using Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Commands; using log4net; using Newtonsoft.Json; diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Handlers/Reports/ReportAnalyzer.cs b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Handlers/Reports/ReportAnalyzer.cs index 38148e15..dbd4e8e5 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Handlers/Reports/ReportAnalyzer.cs +++ b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Handlers/Reports/ReportAnalyzer.cs @@ -103,13 +103,6 @@ public async Task Analyze(IMessageContext context, ErrorReportEntity report) } else { - await _repository.StoreReportStats(new ReportMapping() - { - IncidentId = incident.Id, - ErrorId = report.ClientReportId, - ReceivedAtUtc = report.CreatedAtUtc - }); - if (incident.IsIgnored) { _logger.Info("Incident is ignored: " + JsonConvert.SerializeObject(report)); @@ -154,8 +147,15 @@ await _repository.StoreReportStats(new ReportMapping() report.IncidentId = incident.Id; _repository.CreateReport(report); _logger.Debug("saving report " + report.Id + " for incident " + incident.Id); - var appName = _repository.GetAppName(incident.ApplicationId); + await _repository.StoreReportStats(new ReportMapping() + { + IncidentId = incident.Id, + ErrorId = report.ClientReportId, + ReceivedAtUtc = report.CreatedAtUtc + }); + + var appName = _repository.GetAppName(incident.ApplicationId); var summary = new IncidentSummaryDTO(incident.Id, incident.Description) { ApplicationId = incident.ApplicationId, @@ -168,7 +168,6 @@ await _repository.StoreReportStats(new ReportMapping() }; var sw = new Stopwatch(); sw.Start(); - _logger.Debug("Publishing now: " + report.ClientReportId); var e = new ReportAddedToIncident(summary, ConvertToCoreReport(report, applicationVersion), isReOpened) { IsNewIncident = isNewIncident diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/SaveReportHandler.cs b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/SaveReportHandler.cs index 7c63b403..e0509ae9 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/SaveReportHandler.cs +++ b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/SaveReportHandler.cs @@ -22,6 +22,7 @@ namespace Coderr.Server.ReportAnalyzer.Inbound /// public class SaveReportHandler { + private readonly List> _filters = new List>(); private readonly ILog _logger = LogManager.GetLogger(typeof(SaveReportHandler)); private readonly IMessageQueue _queue; diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/AddDomainHandler.cs b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/AddDomainHandler.cs deleted file mode 100644 index 5aa504a0..00000000 --- a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/AddDomainHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Whitelists.Commands; -using DotNetCqs; -using Griffin.Data; -using Griffin.Data.Mapper; - -namespace Coderr.Server.ReportAnalyzer.Inbound.Whitelist -{ - class AddDomainHandler : IMessageHandler - { - IAdoNetUnitOfWork _uow; - - public AddDomainHandler(IAdoNetUnitOfWork uow) - { - _uow = uow; - } - - public async Task HandleAsync(IMessageContext context, AddDomain message) - { - await _uow.InsertAsync(new WhitelistedDomain(message.ApplicationId, message.DomainName)); - } - } -} diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/GetWhitelistEntriesHandler.cs b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/GetWhitelistEntriesHandler.cs deleted file mode 100644 index ffaa0376..00000000 --- a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/GetWhitelistEntriesHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Whitelists.Queries; -using DotNetCqs; -using Griffin.Data; -using Griffin.Data.Mapper; - -namespace Coderr.Server.ReportAnalyzer.Inbound.Whitelist -{ - public class GetWhitelistEntriesHandler : IQueryHandler - { - private readonly IAdoNetUnitOfWork _unitOfWork; - - public GetWhitelistEntriesHandler(IAdoNetUnitOfWork unitOfWork) - { - _unitOfWork = unitOfWork; - } - - public async Task HandleAsync(IMessageContext context, GetWhitelistEntries message) - { - List entries; - if (message.ApplicationId != null && !string.IsNullOrWhiteSpace(message.DomainName)) - entries = await _unitOfWork.ToListAsync( - "ApplicationId = @applicationId AND DomainName = @domainNAme", - new {message.ApplicationId, message.DomainName}); - else if (message.ApplicationId != null) - entries = - await _unitOfWork.ToListAsync( - "ApplicationId = @applicationId", - new {message.ApplicationId}); - else - entries = - await _unitOfWork.ToListAsync( - "DomanName = @domainName", - new {message.DomainName}); - - return new GetWhitelistEntriesResult {Entries = entries.ToArray()}; - } - } -} \ No newline at end of file diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/WhitelistedDomain.cs b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/WhitelistedDomain.cs deleted file mode 100644 index 174e3442..00000000 --- a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/WhitelistedDomain.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Coderr.Server.ReportAnalyzer.Inbound.Whitelist -{ - public class WhitelistedDomain - { - public WhitelistedDomain(int? applicationId, string domainName) - { - ApplicationId = applicationId; - DomainName = domainName; - } - - public int Id { get; private set; } - - public int? ApplicationId { get; private set; } - public string DomainName { get; private set; } - } -} \ No newline at end of file diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/WhitelistedDomainMapper.cs b/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/WhitelistedDomainMapper.cs deleted file mode 100644 index 9a2cd50c..00000000 --- a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/WhitelistedDomainMapper.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Griffin.Data.Mapper; - -namespace Coderr.Server.ReportAnalyzer.Inbound.Whitelist -{ - class WhitelistedDomainMapper : CrudEntityMapper - { - public WhitelistedDomainMapper() : base("ReportingWhitelistedDomains") - { - Property(x => x.Id) - .PrimaryKey(true); - - } - } -} diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Incidents/IncidentBeingAnalyzed.cs b/src/Server/Coderr.Server.ReportAnalyzer/Incidents/IncidentBeingAnalyzed.cs index a4ac5df8..58da569d 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer/Incidents/IncidentBeingAnalyzed.cs +++ b/src/Server/Coderr.Server.ReportAnalyzer/Incidents/IncidentBeingAnalyzed.cs @@ -206,13 +206,13 @@ public void AddReport(ErrorReportEntity entity) /// /// Check if this incident is ignored in the version that we received in the newest error report. /// - /// + /// Version in the report that we just received. /// - public bool IsReportIgnored(string applicationVersion) + public bool IsReportIgnored(string reportedVersion) { - if (applicationVersion == null) throw new ArgumentNullException(nameof(applicationVersion)); + if (reportedVersion == null) throw new ArgumentNullException(nameof(reportedVersion)); var comparer = new ApplicationVersionComparer(); - return comparer.Compare(IgnoredUntilVersion, applicationVersion) < 0; + return comparer.Compare(reportedVersion, IgnoredUntilVersion) < 0; } /// @@ -224,6 +224,7 @@ public void ReOpen() PreviousSolutionAtUtc = SolvedAtUtc; State = AnalyzedIncidentState.New; ReOpenedAtUtc = DateTime.UtcNow; + UpdatedAtUtc = DateTime.UtcNow; IsReOpened = true; } @@ -232,7 +233,7 @@ public void ReOpen() /// public void WasJustIgnored() { - UpdatedAtUtc = DateTime.UtcNow; + LastReportAtUtc = DateTime.UtcNow; ReportCount++; } } diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Similarities/Handlers/UpdateSimilaritiesFromNewReport.cs b/src/Server/Coderr.Server.ReportAnalyzer/Similarities/Handlers/UpdateSimilaritiesFromNewReport.cs index 71469b8e..10a46e5d 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer/Similarities/Handlers/UpdateSimilaritiesFromNewReport.cs +++ b/src/Server/Coderr.Server.ReportAnalyzer/Similarities/Handlers/UpdateSimilaritiesFromNewReport.cs @@ -23,12 +23,11 @@ public class UpdateSimilaritiesFromNewReport : IMessageHandler /// Creates a new instance of . /// - /// epos - /// similarityReposiotry + /// repository + /// similarityRepository public UpdateSimilaritiesFromNewReport(ISimilarityRepository similarityRepository) { - if (similarityRepository == null) throw new ArgumentNullException("similarityRepository"); - _similarityRepository = similarityRepository; + _similarityRepository = similarityRepository ?? throw new ArgumentNullException(nameof(similarityRepository)); } /// diff --git a/src/Server/Coderr.Server.SqlServer.Tests/Coderr.Server.SqlServer.Tests.csproj b/src/Server/Coderr.Server.SqlServer.Tests/Coderr.Server.SqlServer.Tests.csproj index e8477588..2f4ac55c 100644 --- a/src/Server/Coderr.Server.SqlServer.Tests/Coderr.Server.SqlServer.Tests.csproj +++ b/src/Server/Coderr.Server.SqlServer.Tests/Coderr.Server.SqlServer.Tests.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Server/Coderr.Server.SqlServer/Coderr.Server.SqlServer.csproj b/src/Server/Coderr.Server.SqlServer/Coderr.Server.SqlServer.csproj index f4b16ac6..f265a341 100644 --- a/src/Server/Coderr.Server.SqlServer/Coderr.Server.SqlServer.csproj +++ b/src/Server/Coderr.Server.SqlServer/Coderr.Server.SqlServer.csproj @@ -5,7 +5,8 @@ Coderr.Server.SqlServer - + + @@ -26,4 +27,8 @@ + + + + diff --git a/src/Server/Coderr.Server.SqlServer/Core/Environments/ResetEnvironmentHandler.cs b/src/Server/Coderr.Server.SqlServer/Core/Environments/ResetEnvironmentHandler.cs index f68f92ae..ebb9010a 100644 --- a/src/Server/Coderr.Server.SqlServer/Core/Environments/ResetEnvironmentHandler.cs +++ b/src/Server/Coderr.Server.SqlServer/Core/Environments/ResetEnvironmentHandler.cs @@ -2,12 +2,14 @@ using Coderr.Server.Api.Core.Environments.Commands; using DotNetCqs; using Griffin.Data; +using log4net; namespace Coderr.Server.SqlServer.Core.Environments { internal class ResetEnvironmentHandler : IMessageHandler { private readonly IAdoNetUnitOfWork _unitOfWork; + private ILog _loggr = LogManager.GetLogger(typeof(ResetEnvironmentHandler)); public ResetEnvironmentHandler(IAdoNetUnitOfWork unitOfWork) { @@ -16,6 +18,7 @@ public ResetEnvironmentHandler(IAdoNetUnitOfWork unitOfWork) public Task HandleAsync(IMessageContext context, ResetEnvironment message) { + var sql = @"WITH JustOurIncidents (IncidentId) AS ( select ie.IncidentId @@ -32,6 +35,7 @@ JOIN JustOurIncidents ON (JustOurIncidents.IncidentId = IncidentEnvironments.Inc WHERE IncidentEnvironments.EnvironmentId = @environmentId"; _unitOfWork.ExecuteNonQuery(sql, new {message.ApplicationId, message.EnvironmentId}); + _loggr.Info("Resetting environmentId " + message.EnvironmentId + " for app " + message.ApplicationId); return Task.CompletedTask; } } diff --git a/src/Server/Coderr.Server.SqlServer/Core/Feedback/SubmitFeedbackHandler.cs b/src/Server/Coderr.Server.SqlServer/Core/Feedback/SubmitFeedbackHandler.cs index 1364ac65..66fee2b9 100644 --- a/src/Server/Coderr.Server.SqlServer/Core/Feedback/SubmitFeedbackHandler.cs +++ b/src/Server/Coderr.Server.SqlServer/Core/Feedback/SubmitFeedbackHandler.cs @@ -47,7 +47,13 @@ public async Task HandleAsync(IMessageContext context, SubmitFeedback command) reportId = report.Id; } else + { report2 = await _reportsRepository.FindByErrorIdAsync(command.ErrorId); + if (report2 == null) + { + _logger.Warn("Failed to find report by error id: " + command.ErrorId); + } + } // storing it without connections as the report might not have been uploaded yet. if (report2 == null) @@ -69,6 +75,7 @@ public async Task HandleAsync(IMessageContext context, SubmitFeedback command) cmd.AddParameter("CreatedAtUtc", DateTime.UtcNow); cmd.ExecuteNonQuery(); } + _logger.Info("** STORING FEEDBACK"); } catch (Exception exception) { @@ -103,6 +110,7 @@ public async Task HandleAsync(IMessageContext context, SubmitFeedback command) }; await context.SendAsync(evt); + _logger.Info("** STORING FEEDBACK"); await cmd.ExecuteNonQueryAsync(); } } diff --git a/src/Server/Coderr.Server.SqlServer/Core/Incidents/IncidentRepository.cs b/src/Server/Coderr.Server.SqlServer/Core/Incidents/IncidentRepository.cs index 1204ab77..985efcb2 100644 --- a/src/Server/Coderr.Server.SqlServer/Core/Incidents/IncidentRepository.cs +++ b/src/Server/Coderr.Server.SqlServer/Core/Incidents/IncidentRepository.cs @@ -38,7 +38,8 @@ public async Task UpdateAsync(Incident incident) AssignedAtUtc = @AssignedAtUtc, State = @state, IgnoringReportsSinceUtc = @IgnoringReportsSinceUtc, - IgnoringRequestedBy = @IgnoringRequestedBy + IgnoringRequestedBy = @IgnoringRequestedBy, + IgnoredUntilVersion = @IgnoredUntilVersion WHERE Id = @id"; cmd.AddParameter("Id", incident.Id); cmd.AddParameter("ApplicationId", incident.ApplicationId); @@ -53,6 +54,7 @@ public async Task UpdateAsync(Incident incident) cmd.AddParameter("Solution", incident.Solution == null ? null : EntitySerializer.Serialize(incident.Solution)); cmd.AddParameter("IsSolutionShared", incident.IsSolutionShared); + cmd.AddParameter("IgnoredUntilVersion", incident.IgnoredUntilVersion); await cmd.ExecuteNonQueryAsync(); } } diff --git a/src/Server/Coderr.Server.SqlServer/Core/Reports/ReportMappingMapper.cs b/src/Server/Coderr.Server.SqlServer/Core/Reports/ReportMappingMapper.cs index 5e8b04bc..323da8fa 100644 --- a/src/Server/Coderr.Server.SqlServer/Core/Reports/ReportMappingMapper.cs +++ b/src/Server/Coderr.Server.SqlServer/Core/Reports/ReportMappingMapper.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Coderr.Server.Domain.Core.ErrorReports; +using Coderr.Server.Domain.Core.ErrorReports; using Griffin.Data.Mapper; namespace Coderr.Server.SqlServer.Core.Reports @@ -13,7 +10,7 @@ public ReportMappingMapper() : base("IncidentReports") Property(x => x.Id) .PrimaryKey(true); Property(x => x.ApplicationId) - .Ignore(); + .NotForCrud(); } } } diff --git a/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/AddEntryHandler.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/AddEntryHandler.cs new file mode 100644 index 00000000..253e6dbe --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/AddEntryHandler.cs @@ -0,0 +1,84 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Coderr.Server.Api.Modules.Whitelists.Commands; +using Coderr.Server.App.Modules.Whitelists; +using DnsClient; +using DnsClient.Protocol; +using DotNetCqs; +using Griffin.Data; +using Griffin.Data.Mapper; +using log4net; + +namespace Coderr.Server.SqlServer.Modules.Whitelists +{ + internal class AddEntryHandler : IMessageHandler + { + private readonly ILog _logger = LogManager.GetLogger(typeof(AddEntryHandler)); + private readonly IAdoNetUnitOfWork _uow; + + public AddEntryHandler(IAdoNetUnitOfWork uow) + { + _uow = uow; + } + + public async Task HandleAsync(IMessageContext context, AddEntry message) + { + var entry = new Whitelist {DomainName = message.DomainName}; + await _uow.InsertAsync(entry); + + foreach (var dto in message.ApplicationIds) + { + var entity = new WhitelistedDomainApplication {DomainId = entry.Id, ApplicationId = dto}; + await _uow.InsertAsync(entity); + } + + if (message.IpAddresses?.Length > 0) + { + foreach (var ip in message.IpAddresses) + { + var entity = new WhitelistedDomainIp + { + DomainId = entry.Id, + IpType = IpType.Manual, + IpAddress = IPAddress.Parse(ip), + StoredAtUtc = DateTime.UtcNow + }; + await _uow.InsertAsync(entity); + } + } + else + await LookupIps(message, entry); + } + + private async Task LookupIps(AddEntry message, Whitelist entry) + { + var lookup = new LookupClient(); + var result = await lookup.QueryAsync(message.DomainName, QueryType.ANY); + foreach (var record in result.AllRecords) + { + switch (record) + { + case ARecord ipRecord: + await _uow.InsertAsync(new WhitelistedDomainIp + { + DomainId = entry.Id, + IpAddress = ipRecord.Address, + IpType = IpType.Lookup, + StoredAtUtc = DateTime.UtcNow + }); + break; + case AaaaRecord ip6Record: + await _uow.InsertAsync(new WhitelistedDomainIp + { + DomainId = entry.Id, + IpAddress = ip6Record.Address, + IpType = IpType.Lookup, + StoredAtUtc = DateTime.UtcNow + }); + break; + } + } + } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/EditEntryHandler.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/EditEntryHandler.cs new file mode 100644 index 00000000..1540a3c9 --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/EditEntryHandler.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Coderr.Server.Api.Modules.Whitelists.Commands; +using Coderr.Server.Api.Modules.Whitelists.Queries; +using Coderr.Server.App.Modules.Whitelists; +using DnsClient; +using DnsClient.Protocol; +using DotNetCqs; +using Griffin.Data; +using Griffin.Data.Mapper; +using log4net; + +namespace Coderr.Server.SqlServer.Modules.Whitelists +{ + internal class EditEntryHandler : IMessageHandler + { + private readonly ILog _logger = LogManager.GetLogger(typeof(EditEntryHandler)); + private readonly IAdoNetUnitOfWork _uow; + + public EditEntryHandler(IAdoNetUnitOfWork uow) + { + _uow = uow; + } + + public async Task HandleAsync(IMessageContext context, EditEntry message) + { + var entry = await _uow.FirstAsync(new { message.Id }); + + await FetchIps(entry); + await FetchApplications(entry); + + await UpdateApplications(message, entry); + await UpdateIps(message, entry); + } + + private async Task FetchIps(Whitelist entry) + { + using (var cmd = _uow.CreateDbCommand()) + { + cmd.CommandText = + $"SELECT * FROM WhitelistedDomainIps WHERE DomainId = @domainId"; + cmd.AddParameter("domainId", entry.Id); + using (var reader = await cmd.ExecuteReaderAsync()) + { + var addresses = new List(); + while (await reader.ReadAsync()) + { + addresses.Add(new WhitelistedDomainIp() + { + DomainId = entry.Id, + IpAddress = IPAddress.Parse((string) reader["IpAddress"]), + IpType = (IpType) (int) reader["IpType"], + StoredAtUtc = (DateTime) reader["StoredAtUtc"] + }); + } + + entry.IpAddresses = addresses.ToArray(); + } + } + } + + private async Task FetchApplications(Whitelist entry) + { + using (var cmd = _uow.CreateDbCommand()) + { + cmd.CommandText = + $"SELECT * FROM WhitelistedDomainApps WHERE DomainId = @domainId"; + cmd.AddParameter("domainId", entry.Id); + using (var reader = await cmd.ExecuteReaderAsync()) + { + var apps = new List(); + while (await reader.ReadAsync()) + { + apps.Add((int) reader["ApplicationId"]); + } + + entry.ApplicationIds = apps.ToArray(); + } + } + } + + private async Task LookupIps(EditEntry message, Whitelist whitelist) + { + var lookup = new LookupClient(); + var result = await lookup.QueryAsync(whitelist.DomainName, QueryType.ANY); + foreach (var record in result.AllRecords) + { + switch (record) + { + case ARecord ipRecord: + await _uow.InsertAsync(new WhitelistedDomainIp + { + DomainId = whitelist.Id, + IpAddress = ipRecord.Address, + IpType = IpType.Lookup, + StoredAtUtc = DateTime.UtcNow + }); + break; + case AaaaRecord ip6Record: + await _uow.InsertAsync(new WhitelistedDomainIp + { + DomainId = whitelist.Id, + IpAddress = ip6Record.Address, + IpType = IpType.Lookup, + StoredAtUtc = DateTime.UtcNow + }); + break; + } + } + } + + private async Task UpdateApplications(EditEntry message, Whitelist entry) + { + var dbApps = await _uow.ToListAsync("DomainId = @id", new { id = message.Id }); + + //find new + var newApps = message.ApplicationIds.Except(dbApps.Select(x => x.ApplicationId)); + foreach (var newApp in newApps) + { + var entity = new WhitelistedDomainApplication { DomainId = entry.Id, ApplicationId = newApp }; + await _uow.InsertAsync(entity); + } + + //find removed + var removedApps = dbApps.Select(x => x.ApplicationId) + .Except(message.ApplicationIds) + .Select(x => dbApps.First(y => x == y.ApplicationId)); + foreach (var app in removedApps) await _uow.DeleteAsync(app); + } + + private async Task UpdateIps(EditEntry message, Whitelist whitelist) + { + var dbIps = await _uow.ToListAsync("DomainId = @id", new { id = message.Id }); + + //find new + var newIps = message.IpAddresses.Except(dbIps.Select(x => x.IpAddress.ToString())); + foreach (var newIp in newIps) + { + var entity = new WhitelistedDomainIp + { + DomainId = whitelist.Id, + IpType = IpType.Manual, + IpAddress = IPAddress.Parse(newIp), + StoredAtUtc = DateTime.UtcNow + }; + await _uow.InsertAsync(entity); + } + + //find removed + var removedEntries = dbIps.Select(x => x.IpAddress.ToString()) + .Except(message.IpAddresses) + .Select(x => dbIps.First(y => x == y.IpAddress.ToString())); + foreach (var entry in removedEntries) + await _uow.DeleteAsync(entry); + } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/GetWhitelistEntriesHandler.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/GetWhitelistEntriesHandler.cs new file mode 100644 index 00000000..f925eda3 --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/GetWhitelistEntriesHandler.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; +using Coderr.Server.Api.Modules.Whitelists.Queries; +using DotNetCqs; +using Griffin.Data; +using Griffin.Data.Mapper; + +namespace Coderr.Server.SqlServer.Modules.Whitelists +{ + public class GetWhitelistEntriesHandler : IQueryHandler + { + private readonly IAdoNetUnitOfWork _unitOfWork; + private readonly IEntityMapper _mapper = + new MirrorMapper(); + + + public GetWhitelistEntriesHandler(IAdoNetUnitOfWork unitOfWork) + { + _unitOfWork = unitOfWork; + } + + public async Task HandleAsync(IMessageContext context, GetWhitelistEntries message) + { + var items = new List(); + using (var cmd = _unitOfWork.CreateDbCommand()) + { + CreateSqlStatement(message, cmd); + + var appMap = new Dictionary>(); + using (var reader = await cmd.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + MapRow(reader, items, appMap); + } + + foreach (var map in appMap) + { + items.First(x => x.Id == map.Key).Applications = map.Value.ToArray(); + } + } + } + + if (!items.Any()) + return new GetWhitelistEntriesResult { Entries = new GetWhitelistEntriesResultItem[0] }; + + using (var cmd = _unitOfWork.CreateDbCommand()) + { + var entryIds = items.Select(x => x.Id).ToArray(); + cmd.CommandText = + $"SELECT * FROM WhitelistedDomainIps WHERE DomainId IN ({string.Join(", ", entryIds)})"; + using (var reader = await cmd.ExecuteReaderAsync()) + { + var map = new Dictionary>(); + while (await reader.ReadAsync()) + { + var domainId = (int)reader["DomainId"]; + if (!map.TryGetValue(domainId, out var ipItems)) + { + ipItems = new List(); + map[domainId] = ipItems; + } + ipItems.Add(new GetWhitelistEntriesResultItemIp + { + Address = (string)reader["IpAddress"], + Type = (ResultItemIpType)(int)reader["IpType"], + UpdatedAtUtc = (DateTime)reader["StoredAtUtc"] + }); + } + + foreach (var kvp in map) + { + items.First(x => x.Id == kvp.Key).IpAddresses = kvp.Value.ToArray(); + } + } + } + + return new GetWhitelistEntriesResult { Entries = items.ToArray() }; + } + + private static void CreateSqlStatement(GetWhitelistEntries message, DbCommand cmd) + { + cmd.CommandText = + @"SELECT WhitelistedDomains.*, WhitelistedDomainApps.ApplicationId, Applications.Name ApplicationName + FROM WhitelistedDomains + LEFT JOIN WhitelistedDomainApps ON (DomainId = WhitelistedDomains.Id) + LEFT JOIN Applications ON (ApplicationId = Applications.Id)"; + if (message.ApplicationId != null && !string.IsNullOrWhiteSpace(message.DomainName)) + { + cmd.CommandText += @" + WHERE ApplicationId = @applicationId + AND DomainName = @domainName"; + cmd.AddParameter("domainName", message.DomainName); + cmd.AddParameter("applicationId", message.ApplicationId.Value); + } + else if (message.ApplicationId != null) + { + cmd.CommandText += @" + WHERE ApplicationId = @applicationId"; + cmd.AddParameter("domainName", message.DomainName); + cmd.AddParameter("applicationId", message.ApplicationId.Value); + } + else if (!string.IsNullOrEmpty(message.DomainName)) + { + cmd.CommandText += @" + WHERE DomainName = @domainName"; + cmd.AddParameter("domainName", message.DomainName); + } + } + + private static void MapRow(IDataRecord record, ICollection items, + IDictionary> appMap) + { + var item = items.FirstOrDefault(x => x.Id == record.GetInt32(0)); + if (item == null) + { + item = new GetWhitelistEntriesResultItem + { + Id = record.GetInt32(0), + DomainName = (string)record["DomainName"] + }; + items.Add(item); + appMap[item.Id] = new List(); + } + + var appIdValue = record["ApplicationId"]; + if (appIdValue is DBNull) + return; + + var app = new GetWhitelistEntriesResultItemApp + { + ApplicationId = (int)appIdValue, + Name = (string)record["ApplicationName"] + }; + appMap[item.Id].Add(app); + } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/RemoveDomainHandler.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/RemoveDomainHandler.cs similarity index 63% rename from src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/RemoveDomainHandler.cs rename to src/Server/Coderr.Server.SqlServer/Modules/Whitelists/RemoveDomainHandler.cs index f5ed054c..05154f53 100644 --- a/src/Server/Coderr.Server.ReportAnalyzer/Inbound/Whitelist/RemoveDomainHandler.cs +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/RemoveDomainHandler.cs @@ -1,10 +1,10 @@ using System.Threading.Tasks; -using Coderr.Server.ReportAnalyzer.Abstractions.Inbound.Whitelists.Commands; +using Coderr.Server.Api.Modules.Whitelists.Commands; using DotNetCqs; using Griffin.Data; using Griffin.Data.Mapper; -namespace Coderr.Server.ReportAnalyzer.Inbound.Whitelist +namespace Coderr.Server.SqlServer.Modules.Whitelists { internal class RemoveDomainHandler : IMessageHandler { @@ -17,7 +17,10 @@ public RemoveDomainHandler(IAdoNetUnitOfWork uow) public async Task HandleAsync(IMessageContext context, RemoveEntry message) { - var item = await _uow.FirstAsync("Id = @id", new {message.Id}); + var item = await _uow.FirstOrDefaultAsync("Id = @id", new {message.Id}); + if (item == null) + return; + await _uow.DeleteAsync(item); } } diff --git a/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistRepository.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistRepository.cs new file mode 100644 index 00000000..a8b1f321 --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistRepository.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Coderr.Server.Abstractions.Boot; +using Coderr.Server.App.Modules.Whitelists; +using Griffin.Data; +using Griffin.Data.Mapper; + +namespace Coderr.Server.SqlServer.Modules.Whitelists +{ + [ContainerService] + public class WhitelistRepository : IWhitelistRepository + { + private readonly IAdoNetUnitOfWork _unitOfWork; + + public WhitelistRepository(IAdoNetUnitOfWork unitOfWork) + { + _unitOfWork = unitOfWork; + } + + public async Task FindIp(int applicationId, IPAddress address) + { + using (var cmd = _unitOfWork.CreateDbCommand()) + { + // ORDER BY appId desc = get specific one first, then the generic one. + cmd.CommandText = @"select ip.* + from WhitelistedDomains d + left join WhitelistedDomainIps ip ON (d.Id = ip.DomainId) + left join WhitelistedDomainApps app ON (d.Id = app.DomainId) + WHERE ip.IpAddress = @ip + AND (app.ApplicationId = @appId OR app.ApplicationId is NULL) + order by app.ApplicationId desc"; + cmd.AddParameter("ip", address.ToString()); + cmd.AddParameter("appId", applicationId); + + return await cmd.FirstOrDefaultAsync(); + } + } + + public async Task> FindWhitelists(int applicationId) + { + var domains = new List(); + using (var cmd = _unitOfWork.CreateDbCommand()) + { + // ORDER BY appId desc = get specific one first, then the generic one. + cmd.CommandText = @"select d.* + from WhitelistedDomains d + left join WhitelistedDomainApps app ON (d.Id = app.DomainId) + WHERE app.ApplicationId = @appId OR app.ApplicationId is NULL + order by app.ApplicationId desc"; + cmd.AddParameter("appId", applicationId); + + var entries= await cmd.ToListAsync(); + foreach (var entry in entries) + { + if (domains.Any(x => x.Id == entry.Id)) + continue; + domains.Add(entry); + } + } + + if (!domains.Any()) + return domains; + + var ids = string.Join(", ", domains.Select(x => x.Id)); + var ips = await _unitOfWork.ToListAsync($"SELECT * FROM WhitelistedDomainIps WHERE DomainId IN ({ids})"); + var ipsPerDomain = ips.GroupBy(x => x.DomainId); + foreach (var domainIps in ipsPerDomain) + { + var domain = domains.First(x => x.Id == domainIps.Key); + domain.IpAddresses = domainIps.ToArray(); + } + + return domains; + } + + public async Task SaveIp(WhitelistedDomainIp entry) + { + await _unitOfWork.InsertAsync(entry); + } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainApplication.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainApplication.cs new file mode 100644 index 00000000..8617998f --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainApplication.cs @@ -0,0 +1,10 @@ +namespace Coderr.Server.SqlServer.Modules.Whitelists +{ + public class WhitelistedDomainApplication + { + public int ApplicationId { get; set; } + + public int DomainId { get; set; } + public int Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainApplicationMapper.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainApplicationMapper.cs new file mode 100644 index 00000000..65b27083 --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainApplicationMapper.cs @@ -0,0 +1,11 @@ +using Griffin.Data.Mapper; + +namespace Coderr.Server.SqlServer.Modules.Whitelists +{ + public class WhitelistedDomainApplicationMapper : CrudEntityMapper + { + public WhitelistedDomainApplicationMapper() : base("WhitelistedDomainApps") + { + } + } +} \ No newline at end of file diff --git a/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainIpMapper.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainIpMapper.cs new file mode 100644 index 00000000..9650b2ac --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainIpMapper.cs @@ -0,0 +1,19 @@ +using System.Net; +using Coderr.Server.App.Modules.Whitelists; +using Griffin.Data.Mapper; + +namespace Coderr.Server.SqlServer.Modules.Whitelists +{ + class WhitelistedDomainIpMapper : CrudEntityMapper + { + public WhitelistedDomainIpMapper() : base("WhitelistedDomainIps") + { + Property(x => x.IpAddress) + .ToColumnValue(x => x.ToString()) + .ToPropertyValue(x => IPAddress.Parse((string)x)); + Property(x => x.IpType) + .ToColumnValue(x => (int)x) + .ToPropertyValue(x => (IpType)x); + } + } +} diff --git a/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainMapper.cs b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainMapper.cs new file mode 100644 index 00000000..b6a08fdd --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Modules/Whitelists/WhitelistedDomainMapper.cs @@ -0,0 +1,17 @@ +using Griffin.Data.Mapper; + +namespace Coderr.Server.SqlServer.Modules.Whitelists +{ + class WhitelistMapper : CrudEntityMapper + { + public WhitelistMapper() : base("WhitelistedDomains") + { + Property(x => x.Id) + .PrimaryKey(true); + Property(x => x.IpAddresses) + .Ignore(); + Property(x => x.ApplicationIds) + .Ignore(); + } + } +} diff --git a/src/Server/Coderr.Server.SqlServer/ReportAnalyzer/AnalyticsRepository.cs b/src/Server/Coderr.Server.SqlServer/ReportAnalyzer/AnalyticsRepository.cs index 7d66c5ac..3a2e2242 100644 --- a/src/Server/Coderr.Server.SqlServer/ReportAnalyzer/AnalyticsRepository.cs +++ b/src/Server/Coderr.Server.SqlServer/ReportAnalyzer/AnalyticsRepository.cs @@ -119,7 +119,8 @@ WHERE NOT EXISTS (SELECT IncidentId, EnvironmentId FROM IncidentEnvironments end;"; cmd.AddParameter("incidentId", incidentId); cmd.AddParameter("name", environmentName); - cmd.ExecuteNonQuery(); + var rows = cmd.ExecuteNonQuery(); + _logger.Debug($"saved environment {environmentName} for incident {incidentId}, affected: {rows}"); } } diff --git a/src/Server/Coderr.Server.SqlServer/Schema/Coderr.v21.sql b/src/Server/Coderr.Server.SqlServer/Schema/Coderr.v21.sql new file mode 100644 index 00000000..28d52a75 --- /dev/null +++ b/src/Server/Coderr.Server.SqlServer/Schema/Coderr.v21.sql @@ -0,0 +1,21 @@ +create table WhitelistedDomains +( + Id int not null identity primary key, + DomainName varchar(255) not null +); + +create table WhitelistedDomainApps +( + Id int not null identity primary key, + DomainId int not null constraint FK_WhitelistedDomainApps_WhitelistedDomains foreign key references WhitelistedDomains(Id) ON DELETE CASCADE, + ApplicationId int not null constraint FK_WhitelistedDomainApps_Applications foreign key references Applications(Id) ON DELETE CASCADE, +); + +create table WhitelistedDomainIps +( + Id int not null identity primary key, + DomainId int not null constraint FK_WhitelistedDomainIps_WhitelistedDomains foreign key references WhitelistedDomains(Id) ON DELETE CASCADE, + IpAddress varchar(36) not null, + IpType int not null, + StoredAtUtc datetime not null +); diff --git a/src/Server/Coderr.Server.SqlServer/Web/Feedback/Queries/GetIncidentFeedbackItemsHandler.cs b/src/Server/Coderr.Server.SqlServer/Web/Feedback/Queries/GetIncidentFeedbackItemsHandler.cs index 99fed0b0..a471f5b2 100644 --- a/src/Server/Coderr.Server.SqlServer/Web/Feedback/Queries/GetIncidentFeedbackItemsHandler.cs +++ b/src/Server/Coderr.Server.SqlServer/Web/Feedback/Queries/GetIncidentFeedbackItemsHandler.cs @@ -6,12 +6,15 @@ using DotNetCqs; using Coderr.Server.ReportAnalyzer.Abstractions; using Griffin.Data; +using log4net; namespace Coderr.Server.SqlServer.Web.Feedback.Queries { public class GetIncidentFeedbackHandler : IQueryHandler { private readonly IAdoNetUnitOfWork _unitOfWork; + private ILog _logger = LogManager.GetLogger(typeof(GetIncidentFeedbackHandler)); + public GetIncidentFeedbackHandler(IAdoNetUnitOfWork unitOfWork) { @@ -30,6 +33,7 @@ public async Task HandleAsync(IMessageContext context cmd.AddParameter("id", query.IncidentId); cmd.CommandText += " ORDER BY IncidentFeedback.CreatedAtUtc DESC"; + _logger.Info("** Retreiving"); using (var reader = await cmd.ExecuteReaderAsync()) { var emails = new List(); diff --git a/src/Server/Coderr.Server.Web/Boot/Modules/BackgroundJobs.cs b/src/Server/Coderr.Server.Web/Boot/Modules/BackgroundJobs.cs index 33f3f091..3d141c40 100644 --- a/src/Server/Coderr.Server.Web/Boot/Modules/BackgroundJobs.cs +++ b/src/Server/Coderr.Server.Web/Boot/Modules/BackgroundJobs.cs @@ -22,7 +22,7 @@ public void Start(StartContext context) _backgroundJobManager.ExecuteSequentially = true; _backgroundJobManager.JobFailed += OnBackgroundJobFailed; _backgroundJobManager.StartInterval = TimeSpan.FromSeconds(Debugger.IsAttached ? 0 : 10); - _backgroundJobManager.ExecuteInterval = TimeSpan.FromMinutes(3); + _backgroundJobManager.ExecuteInterval = TimeSpan.FromSeconds(Debugger.IsAttached ? 0 : 30); _backgroundJobManager.ScopeClosing += OnBackgroundJobScopeClosing; _backgroundJobManager.Start(); } diff --git a/src/Server/Coderr.Server.Web/ClientApp/boot.ts b/src/Server/Coderr.Server.Web/ClientApp/boot.ts index 995608ed..62c60dcb 100644 --- a/src/Server/Coderr.Server.Web/ClientApp/boot.ts +++ b/src/Server/Coderr.Server.Web/ClientApp/boot.ts @@ -238,6 +238,16 @@ const routes = [ name: "manageWhitelistedDomains", path: "domains/whitelist/", component: require("./components/manage/system/whitelist/home.vue.html").default + }, + { + name: "addWhitelistedDomain", + path: "domains/add/whitelist/", + component: require("./components/manage/system/whitelist/add.vue.html").default + }, + { + name: "editWhitelistedDomain", + path: "domains/edit/whitelist/:id", + component: require("./components/manage/system/whitelist/edit.vue.html").default } ] }, @@ -262,9 +272,9 @@ const routes = [ ]; var hooks = { - mounted: function(instance:Vue) {}, - created: function(instance:Vue) {}, - afterRoute: function (to: string, from: string) {} + mounted: function (instance: Vue) { }, + created: function (instance: Vue) { }, + afterRoute: function (to: string, from: string) { } }; // Hack for tests diff --git a/src/Server/Coderr.Server.Web/ClientApp/components/discover/incidents/search.ts b/src/Server/Coderr.Server.Web/ClientApp/components/discover/incidents/search.ts index 4b5545a8..5c2e5dfa 100644 --- a/src/Server/Coderr.Server.Web/ClientApp/components/discover/incidents/search.ts +++ b/src/Server/Coderr.Server.Web/ClientApp/components/discover/incidents/search.ts @@ -151,13 +151,14 @@ export default class IncidentSearchComponent extends Vue { } } else { this.highlightActiveApps(); + this.showApplicationColumn = true; } this.highlightActiveTags(); this.highlightActiveEnvironments(); this.drawSearchUi(); this.highlightIncidentState(this.incidentState); - this.search(true); + this.searchInternal(true); }); } @@ -228,10 +229,14 @@ export default class IncidentSearchComponent extends Vue { } this.drawSearchUi(); - this.search(); + this.searchInternal(); } - search(byCode?: boolean) { + public search() { + // Required, or vue will pass the mouse event to the method. + this.searchInternal(false); + } + private searchInternal(byCode?: boolean) { var query = new FindIncidents(); query.FreeText = this.freeText; @@ -260,7 +265,11 @@ export default class IncidentSearchComponent extends Vue { query.ApplicationIds = this.activeApplications; } - if (!byCode) { + if (this.activeEnvironment.length > 0) { + query.EnvironmentIds = this.activeEnvironment; + } + + if (byCode !== true) { AppRoot.Instance.storeState({ name: 'incident-search', component: this, @@ -317,7 +326,7 @@ export default class IncidentSearchComponent extends Vue { this.incidentService$.assignToMe(parseInt(elem.value)); } AppRoot.notify('All selected incidents have been assigned to you. Click on the "Analyze" menu to start working with them.'); - setTimeout(() => { this.search() }, 1000); + setTimeout(() => { this.searchInternal() }, 1000); } deleteSelectedIncidents() { @@ -327,7 +336,7 @@ export default class IncidentSearchComponent extends Vue { this.incidentService$.delete(parseInt(elem.value), "yes"); } AppRoot.notify('All selected incidents have deleted.'); - setTimeout(() => { this.search() }, 1000); + setTimeout(() => { this.searchInternal() }, 1000); } close(incidentId: number) { @@ -358,7 +367,7 @@ export default class IncidentSearchComponent extends Vue { this.showApplicationColumn = false; } - this.search(); + this.searchInternal(); } private drawSearchUi() { var els = document.querySelectorAll('.search-head th i'); diff --git a/src/Server/Coderr.Server.Web/ClientApp/components/home/navmenu/navmenu.ts b/src/Server/Coderr.Server.Web/ClientApp/components/home/navmenu/navmenu.ts index 5aa8d556..6c015bf2 100644 --- a/src/Server/Coderr.Server.Web/ClientApp/components/home/navmenu/navmenu.ts +++ b/src/Server/Coderr.Server.Web/ClientApp/components/home/navmenu/navmenu.ts @@ -1,7 +1,7 @@ import { PubSubService, MessageContext } from "../../../services/PubSub"; import * as MenuApi from "../../../services/menu/MenuApi"; import { AppRoot } from "../../../services/AppRoot"; -import { ApplicationSummary } from "../../../services/applications/ApplicationService"; +import { AppEvents } from "../../../services/applications/ApplicationService"; import Vue from 'vue'; import { Component, Watch } from 'vue-property-decorator'; import * as Router from "vue-router"; @@ -91,6 +91,12 @@ export default class NavMenuComponent extends Vue { var msg = ctx.message.body; this.changeApplication(msg.applicationId); }); + PubSubService.Instance.subscribe(AppEvents.Removed, ctx => { + this.myApplications = this.myApplications.filter(x => x.tag !== ctx.message.body); + if (this.currentApplicationId === ctx.message.body) { + this.changeApplication(null); + } + }); } mounted() { @@ -104,7 +110,7 @@ export default class NavMenuComponent extends Vue { } } - changeApplication(applicationId: number) { + changeApplication(applicationId: number|null) { if (applicationId == null) { this.updateCurrent(0); } else { diff --git a/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/environments/environments.ts b/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/environments/environments.ts index 9619bf47..3fabb07f 100644 --- a/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/environments/environments.ts +++ b/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/environments/environments.ts @@ -25,10 +25,10 @@ export default class ManageEnvironmentsComponent extends Vue { this.load(); } - resetEnvironment(environmentId: number) { + resetEnvironment() { var cmd = new ResetEnvironment(); cmd.ApplicationId = this.applicationId; - cmd.EnvironmentId = environmentId; + cmd.EnvironmentId = this.selectedResetEnvironment; AppRoot.Instance.apiClient.command(cmd) .then(result => { AppRoot.notify('All incidents were deleted in that environment.'); diff --git a/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/settings/settings.ts b/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/settings/settings.ts index fc874b92..b8e29562 100644 --- a/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/settings/settings.ts +++ b/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/settings/settings.ts @@ -35,6 +35,14 @@ export default class ManageAppSettingsComponent extends Vue { }); } + deleteApp() { + var result = confirm("Do you really want to delete '" + this.applicationName + "'."); + if (result) { + AppRoot.Instance.applicationService.delete(this.applicationId); + AppRoot.notify("Application have been queued for deletion. Might take time depending on the number of incidents.", "fa-info", "success"); + this.$router.push('manageHome'); + } + } updateApp() { AppRoot.Instance.applicationService.update(this.applicationId, this.applicationName); AppRoot.notify('Application settings have been saved.'); diff --git a/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/settings/settings.vue.html b/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/settings/settings.vue.html index fdbf9ba1..a526b438 100644 --- a/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/settings/settings.vue.html +++ b/src/Server/Coderr.Server.Web/ClientApp/components/manage/application/settings/settings.vue.html @@ -3,7 +3,9 @@
-
Application settings
+
+ Application settings +
@@ -13,10 +15,11 @@
+
- Create new application +
diff --git a/src/Server/Coderr.Server.Web/ClientApp/components/manage/system/menu.vue.html b/src/Server/Coderr.Server.Web/ClientApp/components/manage/system/menu.vue.html index eb04d7d3..d736beb6 100644 --- a/src/Server/Coderr.Server.Web/ClientApp/components/manage/system/menu.vue.html +++ b/src/Server/Coderr.Server.Web/ClientApp/components/manage/system/menu.vue.html @@ -8,12 +8,12 @@ Api keys - + + Billing + -->