diff --git a/example/SeqTail/SeqTail.csproj b/example/SeqTail/SeqTail.csproj index c5676ab..7dc54c5 100644 --- a/example/SeqTail/SeqTail.csproj +++ b/example/SeqTail/SeqTail.csproj @@ -24,7 +24,6 @@ - diff --git a/example/SignalCopy/Program.cs b/example/SignalCopy/Program.cs index b831266..539afe6 100644 --- a/example/SignalCopy/Program.cs +++ b/example/SignalCopy/Program.cs @@ -5,7 +5,7 @@ using Seq.Api.Model.Signals; using System.Collections.Generic; -namespace SeqQuery +namespace SignalCopy { class Program { @@ -76,10 +76,9 @@ static async Task Run(string src, string srcKey, string dst, string dstKey) foreach (var signal in await srcConnection.Signals.ListAsync()) { - SignalEntity target; - if (dstSignals.TryGetValue(signal.Title, out target)) + if (dstSignals.TryGetValue(signal.Title, out var target)) { - if (target.IsRestricted) + if (target.IsProtected) { Console.WriteLine($"Skipping restricted signal '{signal.Title}' ({target.Id})"); continue; diff --git a/src/Seq.Api/Client/SeqApiClient.cs b/src/Seq.Api/Client/SeqApiClient.cs index b684fbf..bb5abfc 100644 --- a/src/Seq.Api/Client/SeqApiClient.cs +++ b/src/Seq.Api/Client/SeqApiClient.cs @@ -26,7 +26,7 @@ public class SeqApiClient : IDisposable // Future versions of Seq may not completely support v1 features, however // providing this as an Accept header will ensure what compatibility is available // can be utilised. - const string SeqApiV5MediaType = "application/vnd.datalust.seq.v5+json"; + const string SeqApiV6MediaType = "application/vnd.datalust.seq.v6+json"; readonly HttpClient _httpClient; readonly CookieContainer _cookies = new CookieContainer(); @@ -36,14 +36,18 @@ public class SeqApiClient : IDisposable Converters = { new StringEnumConverter(), new LinkCollectionConverter() } }); - public SeqApiClient(string serverUrl, string apiKey = null) + public SeqApiClient(string serverUrl, string apiKey = null, bool useDefaultCredentials = true) { ServerUrl = serverUrl ?? throw new ArgumentNullException(nameof(serverUrl)); if (!string.IsNullOrEmpty(apiKey)) _apiKey = apiKey; - var handler = new HttpClientHandler { CookieContainer = _cookies, UseDefaultCredentials = true }; + var handler = new HttpClientHandler + { + CookieContainer = _cookies, + UseDefaultCredentials = useDefaultCredentials + }; var baseAddress = serverUrl; if (!baseAddress.EndsWith("/")) @@ -103,6 +107,13 @@ public async Task PostReadStringAsync(ILinked entity, string li return await new StreamReader(stream).ReadToEndAsync(); } + public async Task PostReadStreamAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null) + { + var linkUri = ResolveLink(entity, link, parameters); + var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; + return await HttpSendAsync(request).ConfigureAwait(false); + } + public async Task PutAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null) { var linkUri = ResolveLink(entity, link, parameters); @@ -170,7 +181,7 @@ async Task HttpSendAsync(HttpRequestMessage request) if (_apiKey != null) request.Headers.Add("X-Seq-ApiKey", _apiKey); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(SeqApiV5MediaType)); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(SeqApiV6MediaType)); var response = await _httpClient.SendAsync(request).ConfigureAwait(false); var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); @@ -186,11 +197,10 @@ async Task HttpSendAsync(HttpRequestMessage request) // ReSharper disable once EmptyGeneralCatchClause catch { } - object error; - if (payload != null && payload.TryGetValue("Error", out error) && error != null) - throw new SeqApiException($"{(int)response.StatusCode} - {error}"); + if (payload != null && payload.TryGetValue("Error", out var error) && error != null) + throw new SeqApiException($"{(int)response.StatusCode} - {error}", response.StatusCode); - throw new SeqApiException($"The Seq request failed ({(int)response.StatusCode})."); + throw new SeqApiException($"The Seq request failed ({(int)response.StatusCode}).", response.StatusCode); } HttpContent MakeJsonContent(object content) diff --git a/src/Seq.Api/Client/SeqApiException.cs b/src/Seq.Api/Client/SeqApiException.cs index 18a9e31..71d5966 100644 --- a/src/Seq.Api/Client/SeqApiException.cs +++ b/src/Seq.Api/Client/SeqApiException.cs @@ -1,12 +1,27 @@ using System; +using System.Net; namespace Seq.Api.Client { + /// + /// Thrown when an action cannot be performed. + /// public class SeqApiException : Exception { - public SeqApiException(string message) + /// + /// Construct a with the given message and status code. + /// + /// A message describing the error. + /// The corresponding status code returned from Seq, if available. + public SeqApiException(string message, HttpStatusCode? statusCode) : base(message) { + StatusCode = statusCode; } + + /// + /// The status code returned from Seq, if available. + /// + public HttpStatusCode? StatusCode { get; } } } \ No newline at end of file diff --git a/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs b/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs index 4032d9e..3ce4b36 100644 --- a/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs +++ b/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs @@ -1,28 +1,24 @@ using System.Collections.Generic; using Seq.Api.Model.LogEvents; +using Seq.Api.Model.Security; using Seq.Api.Model.Signals; namespace Seq.Api.Model.Inputs { public class ApiKeyEntity : Entity { - public ApiKeyEntity() - { - AppliedProperties = new List(); - InputFilter = new SignalFilterPart(); - Metrics = new ApiKeyMetricsPart(); - } - public string Title { get; set; } public string Token { get; set; } - public List AppliedProperties { get; set; } - public SignalFilterPart InputFilter { get; set; } - public bool CanActAsPrincipal { get; set; } + public string TokenPrefix { get; set; } + public List AppliedProperties { get; set; } = new List(); + public SignalFilterPart InputFilter { get; set; } = new SignalFilterPart(); public LogEventLevel? MinimumLevel { get; set; } public bool UseServerTimestamps { get; set; } public int InfluxPerMinute { get; set; } public int ArrivalsPerMinute { get; set; } - public ApiKeyMetricsPart Metrics { get; set; } + public ApiKeyMetricsPart Metrics { get; set; } = new ApiKeyMetricsPart(); public bool IsDefault { get; set; } + public string OwnerId { get; set; } + public HashSet AssignedPermissions { get; set; } = new HashSet(); } } diff --git a/src/Seq.Api/Model/Permalinks/PermalinkEntity.cs b/src/Seq.Api/Model/Permalinks/PermalinkEntity.cs index 74ac2c9..4111112 100644 --- a/src/Seq.Api/Model/Permalinks/PermalinkEntity.cs +++ b/src/Seq.Api/Model/Permalinks/PermalinkEntity.cs @@ -6,6 +6,12 @@ namespace Seq.Api.Model.Permalinks { public class PermalinkEntity : Entity { + /// + /// When retrieving an event that _may_ be permalinked (backwards compatibility), + /// this hint is given by specifiying `permalinkId=unknown` in the API call. + /// + public const string UnknownId = "unknown"; + public string OwnerId { get; set; } public string EventId { get; set; } public DateTime CreatedUtc { get; set; } diff --git a/src/Seq.Api/Model/Security/Permission.cs b/src/Seq.Api/Model/Security/Permission.cs new file mode 100644 index 0000000..88486a0 --- /dev/null +++ b/src/Seq.Api/Model/Security/Permission.cs @@ -0,0 +1,41 @@ +namespace Seq.Api.Model.Security +{ + /// + /// A permission is an access right 1) held by a principal, and 2) demanded by an endpoint. Permissions + /// may be broad, such as the permission to modify administrative settings, or narrow (e.g. currently the + /// permission to ingest events). + /// + public enum Permission + { + /// + /// A sentinel value to detect uninitialized permissions. + /// + Undefined, + + /// + /// Access to publicly-visible assets - the API root/metadata, HTML, JavaScript, CSS, information necessary + /// to initiate the login process, and so-on. + /// + Public, + + /// + /// Add events to the event store. + /// + Ingest, + + /// + /// Query events, dashboards, signals, app instances. + /// + Read, + + /// + /// Write-access to signals, alerts, preferences etc. + /// + Write, + + /// + /// Access to administrative features of Seq, management of other users, app installation, backups. + /// + Setup, + } +} diff --git a/src/Seq.Api/Model/Security/RoleEntity.cs b/src/Seq.Api/Model/Security/RoleEntity.cs new file mode 100644 index 0000000..2d88ce2 --- /dev/null +++ b/src/Seq.Api/Model/Security/RoleEntity.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Seq.Api.Model.Security +{ + public class RoleEntity : Entity + { + public string Title { get; set; } + public HashSet Permissions { get; set; } = new HashSet(); + } +} diff --git a/src/Seq.Api/Model/Security/WellKnownRole.cs b/src/Seq.Api/Model/Security/WellKnownRole.cs new file mode 100644 index 0000000..19678c6 --- /dev/null +++ b/src/Seq.Api/Model/Security/WellKnownRole.cs @@ -0,0 +1,8 @@ +namespace Seq.Api.Model.Security +{ + public static class WellKnownRole + { + public const string AdministratorRoleId = "role-administrator"; + public const string UserRoleId = "role-user"; + } +} diff --git a/src/Seq.Api/Model/Shared/ErrorPart.cs b/src/Seq.Api/Model/Shared/ErrorPart.cs new file mode 100644 index 0000000..b16328f --- /dev/null +++ b/src/Seq.Api/Model/Shared/ErrorPart.cs @@ -0,0 +1,7 @@ +namespace Seq.Api.Model.Shared +{ + public class ErrorPart + { + public string Error { get; set; } + } +} diff --git a/src/Seq.Api/Model/Signals/SignalEntity.cs b/src/Seq.Api/Model/Signals/SignalEntity.cs index d8c4004..c7aa3c4 100644 --- a/src/Seq.Api/Model/Signals/SignalEntity.cs +++ b/src/Seq.Api/Model/Signals/SignalEntity.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Seq.Api.Model.Signals { @@ -21,7 +22,11 @@ public SignalEntity() public bool IsWatched { get; set; } - public bool IsRestricted { get; set; } + [Obsolete("This member has been renamed `IsProtected` to better reflect its purpose.")] + // ReSharper disable once UnusedMember.Global + public bool? IsRestricted { get; set; } + + public bool IsProtected { get; set; } public SignalGrouping Grouping { get; set; } diff --git a/src/Seq.Api/Model/Users/UserEntity.cs b/src/Seq.Api/Model/Users/UserEntity.cs index 2c186f2..4481e61 100644 --- a/src/Seq.Api/Model/Users/UserEntity.cs +++ b/src/Seq.Api/Model/Users/UserEntity.cs @@ -9,9 +9,9 @@ public class UserEntity : Entity public string DisplayName { get; set; } public string EmailAddress { get; set; } public Dictionary Preferences { get; set; } - public bool IsAdministrator { get; set; } public string NewPassword { get; set; } public SignalFilterPart ViewFilter { get; set; } public bool MustChangePassword { get; set; } + public HashSet RoleIds { get; set; } = new HashSet(); } } diff --git a/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs b/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs index cc52a1a..6047ff2 100644 --- a/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs @@ -18,9 +18,10 @@ public async Task FindAsync(string id) return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(string ownerId = null) { - return await GroupListAsync("Items").ConfigureAwait(false); + var parameters = new Dictionary { { "ownerId", ownerId } }; + return await GroupListAsync("Items", parameters).ConfigureAwait(false); } public async Task TemplateAsync() @@ -30,7 +31,7 @@ public async Task TemplateAsync() public async Task AddAsync(ApiKeyEntity entity) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await GroupCreateAsync(entity).ConfigureAwait(false); } public async Task RemoveAsync(ApiKeyEntity entity) diff --git a/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs b/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs index 42cc2e9..23cf62b 100644 --- a/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Threading.Tasks; using Seq.Api.Client; using Seq.Api.Model; @@ -55,6 +56,12 @@ protected async Task GroupPostReadStringAsync(string link, TEnt return await Client.PostReadStringAsync(group, link, content, parameters).ConfigureAwait(false); } + protected async Task GroupPostReadBytesAsync(string link, TEntity content, IDictionary parameters = null) + { + var group = await LoadGroupAsync().ConfigureAwait(false); + return await Client.PostReadStreamAsync(group, link, content, parameters).ConfigureAwait(false); + } + protected async Task GroupPostAsync(string link, TEntity content, IDictionary parameters = null) { var group = await LoadGroupAsync().ConfigureAwait(false); @@ -83,5 +90,25 @@ protected string GetLink(TEntity entity, string link, string orElse) wh { return entity.Links.ContainsKey(link) ? link : orElse; } + + protected async Task GroupCreateAsync(TEntity entity, + IDictionary parameters = null) where TEntity : ILinked + { + ILinked resource; + string link; + + if (entity.Links.ContainsKey("Create")) + { + resource = entity; + link = "Create"; + } + else + { + resource = await LoadGroupAsync().ConfigureAwait(false); + link = "Items"; + } + + return await Client.PostAsync(resource, link, entity, parameters).ConfigureAwait(false); + } } } diff --git a/src/Seq.Api/ResourceGroups/BackupsResourceGroup.cs b/src/Seq.Api/ResourceGroups/BackupsResourceGroup.cs index 3740359..734d983 100644 --- a/src/Seq.Api/ResourceGroups/BackupsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/BackupsResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Seq.Api.Model.Backups; @@ -22,5 +23,10 @@ public async Task> ListAsync() { return await GroupListAsync("Items").ConfigureAwait(false); } + + public async Task DownloadImmediateAsync() + { + return await GroupPostReadBytesAsync("Immediate", new object()).ConfigureAwait(false); + } } } diff --git a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs index d8034f4..6a464f9 100644 --- a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs @@ -76,21 +76,16 @@ static void MakeParameters( }; if (rangeStartUtc != null) - { parameters.Add(nameof(rangeStartUtc), rangeStartUtc); - } + if (rangeEndUtc != null) - { parameters.Add(nameof(rangeEndUtc), rangeEndUtc.Value); - } + if (signal != null) - { parameters.Add(nameof(signal), signal.ToString()); - } + if (timeout != null) - { parameters.Add("timeoutMS", timeout.Value.TotalMilliseconds.ToString("0")); - } body = unsavedSignal ?? new SignalEntity(); } diff --git a/src/Seq.Api/ResourceGroups/EntityResourceGroup.cs b/src/Seq.Api/ResourceGroups/EntityResourceGroup.cs deleted file mode 100644 index 6bee541..0000000 --- a/src/Seq.Api/ResourceGroups/EntityResourceGroup.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Seq.Api.Model; - -namespace Seq.Api.ResourceGroups -{ - public class EntityResourceGroup : ApiResourceGroup - { - internal EntityResourceGroup(string name, ISeqConnection connection) : base(name, connection) - { - } - - protected async Task GroupCreateAsync(TEntity entity, - IDictionary parameters = null) where TEntity : ILinked - { - ILinked resource; - string link; - - if (entity.Links.ContainsKey("Create")) - { - resource = entity; - link = "Create"; - } - else - { - resource = await LoadGroupAsync().ConfigureAwait(false); - link = "Items"; - } - - return await Client.PostAsync(resource, link, entity, parameters).ConfigureAwait(false); - } - } -} diff --git a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs index 8468974..42d6862 100644 --- a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs @@ -16,10 +16,23 @@ internal EventsResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + /// Find an event, given its id. + /// The id of the event to retrieve. + /// If specified, the event's message template and properties will be rendered into its RenderedMessage property. + /// If the request is for a permalinked event, specifying the id of the permalink here will + /// allow events that have otherwise been deleted to be found. The special value `"unknown"` provides backwards compatibility + /// with versions prior to 5.0, which did not mark permalinks explicitly. + /// The event. + public async Task FindAsync( + string id, + bool render = false, + string permalinkId = null) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + + var parameters = new Dictionary {{"id", id}}; + + return await GroupGetAsync("Item", parameters).ConfigureAwait(false); } /// @@ -38,6 +51,9 @@ public async Task FindAsync(string id) /// If specified, the number of events after the first match to keep searching before a partial /// result set is returned. Used to improve responsiveness when the result is displayed in a user interface, not typically used in /// batch processing scenarios. + /// If the request is for a permalinked event, specifying the id of the permalink here will + /// allow events that have otherwise been deleted to be found. The special value `"unknown"` provides backwards compatibility + /// with versions prior to 5.0, which did not mark permalinks explicitly. /// The complete list of events, ordered from least to most recent. public async Task> ListAsync( SignalExpressionPart signal = null, @@ -48,7 +64,8 @@ public async Task> ListAsync( bool render = false, DateTime? fromDateUtc = null, DateTime? toDateUtc = null, - int? shortCircuitAfter = null) + int? shortCircuitAfter = null, + string permalinkId = null) { var parameters = new Dictionary { { "count", count } }; if (signal != null) { parameters.Add("signal", signal.ToString()); } @@ -59,6 +76,7 @@ public async Task> ListAsync( if (fromDateUtc != null) { parameters.Add("fromDateUtc", fromDateUtc.Value); } if (toDateUtc != null) { parameters.Add("toDateUtc", toDateUtc.Value); } if (shortCircuitAfter != null) { parameters.Add("shortCircuitAfter", shortCircuitAfter.Value); } + if (permalinkId != null) { parameters.Add("permalinkId", permalinkId); } var chunks = new List>(); var remaining = count; @@ -104,6 +122,9 @@ public async Task> ListAsync( /// If specified, the number of events after the first match to keep searching before a partial /// result set is returned. Used to improve responsiveness when the result is displayed in a user interface, not typically used in /// batch processing scenarios. + /// If the request is for a permalinked event, specifying the id of the permalink here will + /// allow events that have otherwise been deleted to be found. The special value `"unknown"` provides backwards compatibility + /// with versions prior to 5.0, which did not mark permalinks explicitly. /// The complete list of events, ordered from least to most recent. public async Task InSignalAsync( SignalEntity unsavedSignal = null, @@ -115,7 +136,8 @@ public async Task InSignalAsync( bool render = false, DateTime? fromDateUtc = null, DateTime? toDateUtc = null, - int? shortCircuitAfter = null) + int? shortCircuitAfter = null, + string permalinkId = null) { var parameters = new Dictionary{{ "count", count }}; if (signal != null) { parameters.Add("signal", signal.ToString()); } @@ -126,11 +148,32 @@ public async Task InSignalAsync( if (fromDateUtc != null) { parameters.Add("fromDateUtc", fromDateUtc.Value); } if (toDateUtc != null) { parameters.Add("toDateUtc", toDateUtc.Value); } if (shortCircuitAfter != null) { parameters.Add("shortCircuitAfter", shortCircuitAfter.Value); } + if (permalinkId != null) { parameters.Add("permalinkId", permalinkId); } var body = unsavedSignal ?? new SignalEntity(); return await GroupPostAsync("InSignal", body, parameters).ConfigureAwait(false); } + /// + /// Retrieve a list of events that match a set of conditions. The complete result is buffered into memory, + /// so if a large result set is expected, use InSignalAsync() and lastReadEventId to page the results. + /// + /// If provided, a signal expression describing the set of events that will be filtered for the result. + /// A strict Seq filter expression to match (text expressions must be in double quotes). To + /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use connection.Expressions.ToStrictAsync(). + /// The number of events to retrieve. If not specified will default to 30. + /// An event id from which to start searching (inclusively). + /// An event id to search after (exclusively). + /// If specified, the event's message template and properties will be rendered into its RenderedMessage property. + /// Earliest (inclusive) date/time from which to search. + /// Latest (exclusive) date/time from which to search. + /// If specified, the number of events after the first match to keep searching before a partial + /// result set is returned. Used to improve responsiveness when the result is displayed in a user interface, not typically used in + /// batch processing scenarios. + /// If the request is for a permalinked event, specifying the id of the permalink here will + /// allow events that have otherwise been deleted to be found. The special value `"unknown"` provides backwards compatibility + /// with versions prior to 5.0, which did not mark permalinks explicitly. + /// The complete list of events, ordered from least to most recent. public async Task InSignalAsync( SignalExpressionPart signal, string filter = null, @@ -140,7 +183,8 @@ public async Task InSignalAsync( bool render = false, DateTime? fromDateUtc = null, DateTime? toDateUtc = null, - int? shortCircuitAfter = null) + int? shortCircuitAfter = null, + string permalinkId = null) { if (signal == null) throw new ArgumentNullException(nameof(signal)); @@ -156,6 +200,7 @@ public async Task InSignalAsync( if (fromDateUtc != null) { parameters.Add("fromDateUtc", fromDateUtc.Value); } if (toDateUtc != null) { parameters.Add("toDateUtc", toDateUtc.Value); } if (shortCircuitAfter != null) { parameters.Add("shortCircuitAfter", shortCircuitAfter.Value); } + if (permalinkId != null) { parameters.Add("permalinkId", permalinkId); } return await GroupGetAsync("InSignal", parameters).ConfigureAwait(false); } diff --git a/src/Seq.Api/ResourceGroups/PermalinksResourceGroup.cs b/src/Seq.Api/ResourceGroups/PermalinksResourceGroup.cs index a98c098..a280d71 100644 --- a/src/Seq.Api/ResourceGroups/PermalinksResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/PermalinksResourceGroup.cs @@ -15,30 +15,26 @@ internal PermalinksResourceGroup(ISeqConnection connection) public async Task FindAsync( string id, bool includeEvent = false, - bool renderEvent = false, - bool includeUser = false) + bool renderEvent = false) { if (id == null) throw new ArgumentNullException(nameof(id)); var parameters = new Dictionary { {"id", id}, {"includeEvent", includeEvent}, - {"renderEvent", renderEvent}, - {"includeUser", includeUser} + {"renderEvent", renderEvent} }; return await GroupGetAsync("Item", parameters).ConfigureAwait(false); } public async Task> ListAsync( bool includeEvent = false, - bool renderEvent = false, - bool includeUser = false) + bool renderEvent = false) { var parameters = new Dictionary { {"includeEvent", includeEvent}, - {"renderEvent", renderEvent}, - {"includeUser", includeUser} + {"renderEvent", renderEvent} }; return await GroupListAsync("Items", parameters).ConfigureAwait(false); @@ -51,7 +47,7 @@ public async Task TemplateAsync() public async Task AddAsync(PermalinkEntity entity) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await GroupCreateAsync(entity).ConfigureAwait(false); } public async Task RemoveAsync(PermalinkEntity entity) @@ -64,4 +60,4 @@ public async Task UpdateAsync(PermalinkEntity entity) await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs b/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs index 9a4f178..cf68e2b 100644 --- a/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs @@ -5,7 +5,7 @@ namespace Seq.Api.ResourceGroups { - public class SignalsResourceGroup : EntityResourceGroup + public class SignalsResourceGroup : ApiResourceGroup { internal SignalsResourceGroup(ISeqConnection connection) : base("Signals", connection) diff --git a/src/Seq.Api/ResourceGroups/UsersResourceGroup.cs b/src/Seq.Api/ResourceGroups/UsersResourceGroup.cs index 83baf25..aa87ec1 100644 --- a/src/Seq.Api/ResourceGroups/UsersResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/UsersResourceGroup.cs @@ -73,7 +73,7 @@ public async Task LoginWindowsIntegratedAsync() var providers = await GroupGetAsync("AuthenticationProviders").ConfigureAwait(false); var provider = providers.Providers.SingleOrDefault(p => p.Name == "Integrated Windows Authentication"); if (provider == null) - throw new SeqApiException("The Integrated Windows Authentication provider is not available."); + throw new SeqApiException("The Integrated Windows Authentication provider is not available.", null); var response = await Client.HttpClient.GetAsync(provider.Url).ConfigureAwait(false); response.EnsureSuccessStatusCode(); return await FindCurrentAsync().ConfigureAwait(false); diff --git a/src/Seq.Api/Seq.Api.csproj b/src/Seq.Api/Seq.Api.csproj index ec9dff6..a1dddf3 100644 --- a/src/Seq.Api/Seq.Api.csproj +++ b/src/Seq.Api/Seq.Api.csproj @@ -1,37 +1,26 @@ - + Client library for the Seq HTTP API. - 4.2.3 + 5.0.0 Datalust;Contributors - netstandard1.3;net452 + netstandard1.3 $(NoWarn);CS1591 true true Seq.Api Seq.Api seq - Copyright © 2014-2017 Datalust Pty Ltd and Contributors + Copyright © 2014-2018 Datalust Pty Ltd and Contributors https://getseq.net/images/seq-nuget.png https://github.com/datalust/seq-api http://www.apache.org/licenses/LICENSE-2.0 Seq.Api - - true - - + + - - - - - - - - - - + \ No newline at end of file diff --git a/src/Seq.Api/SeqConnection.cs b/src/Seq.Api/SeqConnection.cs index 5d611b2..9789aa5 100644 --- a/src/Seq.Api/SeqConnection.cs +++ b/src/Seq.Api/SeqConnection.cs @@ -14,10 +14,10 @@ public class SeqConnection : ISeqConnection readonly ConcurrentDictionary> _resourceGroups = new ConcurrentDictionary>(); readonly Lazy> _root; - public SeqConnection(string serverUrl, string apiKey = null) + public SeqConnection(string serverUrl, string apiKey = null, bool useDefaultCredentials = true) { if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); - _client = new SeqApiClient(serverUrl, apiKey); + _client = new SeqApiClient(serverUrl, apiKey, useDefaultCredentials); _root = new Lazy>(() => _client.GetRootAsync()); } diff --git a/src/Seq.Api/Streams/ObservableStream.cs b/src/Seq.Api/Streams/ObservableStream.cs index ac96d4d..983c65c 100644 --- a/src/Seq.Api/Streams/ObservableStream.cs +++ b/src/Seq.Api/Streams/ObservableStream.cs @@ -208,7 +208,13 @@ public void Dispose() catch { } if (_run != null) - _run.ConfigureAwait(false).GetAwaiter().GetResult(); + { + try + { + _run.ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (TaskCanceledException) { } + } _socket.Dispose(); }