diff --git a/.gitignore b/.gitignore index 4ee83f6..ef76fc3 100644 --- a/.gitignore +++ b/.gitignore @@ -252,3 +252,4 @@ paket-files/ *.sln.iml .vscode/ +*.orig diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..2deb00d --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,6 @@ + + + + latest + + \ No newline at end of file diff --git a/README.md b/README.md index baaff62..dbcb791 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Navigate the "resource groups" exposed as properties of the `connnection`: var installedApps = await connection.Apps.ListAsync(); ``` -**To authenticate**, the `SeqConnection` constructor accepts an `apiKey` parameter (make sure the API key permits _user-level access_) or, if you want to log in with personal credentials you can `await connection.Users.Login(username, password)`. +**To authenticate**, the `SeqConnection` constructor accepts an `apiKey` parameter (make sure the API key permits _user-level access_) or, if you want to log in with personal credentials you can `await connection.Users.LoginAsync(username, password)`. For a more complete example, see the [seq-tail app included in the source](https://github.com/datalust/seq-api/blob/master/example/SeqTail/Program.cs). @@ -128,13 +128,13 @@ var events = await client.GetAsync(root, "EventsResources"); Use the client to navigate links from entity to entity: ```csharp -var matched = await client.List( +var matched = await client.ListAsync( events, "Items", new Dictionary{{"count", 10}, {"render", true}}); foreach (var match in matched) - Console.WriteLine(matched.RenderedMessage); + Console.WriteLine(match.RenderedMessage); ``` ### Package versioning diff --git a/Seq.Api.sln.DotSettings b/Seq.Api.sln.DotSettings deleted file mode 100644 index d50635d..0000000 --- a/Seq.Api.sln.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - AD \ No newline at end of file diff --git a/example/SeqTail/Program.cs b/example/SeqTail/Program.cs index 1504705..162fdd1 100644 --- a/example/SeqTail/Program.cs +++ b/example/SeqTail/Program.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using DocoptNet; using Seq.Api; 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..d5256c4 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("/")) @@ -56,88 +60,95 @@ public SeqApiClient(string serverUrl, string apiKey = null) public HttpClient HttpClient => _httpClient; - public Task GetRootAsync() + public Task GetRootAsync(CancellationToken cancellationToken = default) { - return HttpGetAsync("api"); + return HttpGetAsync("api", cancellationToken); } - public Task GetAsync(ILinked entity, string link, IDictionary parameters = null) + public Task GetAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); - return HttpGetAsync(linkUri); + return HttpGetAsync(linkUri, cancellationToken); } - public Task GetStringAsync(ILinked entity, string link, IDictionary parameters = null) + public Task GetStringAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); - return HttpGetStringAsync(linkUri); + return HttpGetStringAsync(linkUri, cancellationToken); } - public Task> ListAsync(ILinked entity, string link, IDictionary parameters = null) + public Task> ListAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); - return HttpGetAsync>(linkUri); + return HttpGetAsync>(linkUri, cancellationToken); } - public async Task PostAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null) + public async Task PostAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; - var stream = await HttpSendAsync(request).ConfigureAwait(false); + var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); new StreamReader(stream).ReadToEnd(); } - public async Task PostAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null) + public async Task PostAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; - var stream = await HttpSendAsync(request).ConfigureAwait(false); + var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); return _serializer.Deserialize(new JsonTextReader(new StreamReader(stream))); } - public async Task PostReadStringAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null) + public async Task PostReadStringAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; - var stream = await HttpSendAsync(request).ConfigureAwait(false); + var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); return await new StreamReader(stream).ReadToEndAsync(); } - public async Task PutAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null) + public async Task PostReadStreamAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) + { + var linkUri = ResolveLink(entity, link, parameters); + var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; + return await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); + } + + public async Task PutAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Put, linkUri) { Content = MakeJsonContent(content) }; - var stream = await HttpSendAsync(request).ConfigureAwait(false); + var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); new StreamReader(stream).ReadToEnd(); } - public async Task DeleteAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null) + public async Task DeleteAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Delete, linkUri) { Content = MakeJsonContent(content) }; - var stream = await HttpSendAsync(request).ConfigureAwait(false); + var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); new StreamReader(stream).ReadToEnd(); } - public async Task DeleteAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null) + public async Task DeleteAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Delete, linkUri) { Content = MakeJsonContent(content) }; - var stream = await HttpSendAsync(request).ConfigureAwait(false); + var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); return _serializer.Deserialize(new JsonTextReader(new StreamReader(stream))); } - public async Task> StreamAsync(ILinked entity, string link, IDictionary parameters = null) + public async Task> StreamAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default) { - return await WebSocketStreamAsync(entity, link, parameters, reader => _serializer.Deserialize(new JsonTextReader(reader))); + return await WebSocketStreamAsync(entity, link, parameters, reader => _serializer.Deserialize(new JsonTextReader(reader)), cancellationToken); } - public async Task> StreamTextAsync(ILinked entity, string link, IDictionary parameters = null) + public async Task> StreamTextAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default) { - return await WebSocketStreamAsync(entity, link, parameters, reader => reader.ReadToEnd()); + return await WebSocketStreamAsync(entity, link, parameters, reader => reader.ReadToEnd(), cancellationToken); } - async Task> WebSocketStreamAsync(ILinked entity, string link, IDictionary parameters, Func deserialize) + async Task> WebSocketStreamAsync(ILinked entity, string link, IDictionary parameters, Func deserialize, CancellationToken cancellationToken = default) { var linkUri = ResolveLink(entity, link, parameters); @@ -146,35 +157,35 @@ async Task> WebSocketStreamAsync(ILinked entity, string l if (_apiKey != null) socket.Options.SetRequestHeader("X-Seq-ApiKey", _apiKey); - await socket.ConnectAsync(new Uri(linkUri), CancellationToken.None); + await socket.ConnectAsync(new Uri(linkUri), cancellationToken); return new ObservableStream(socket, deserialize); } - async Task HttpGetAsync(string url) + async Task HttpGetAsync(string url, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Get, url); - var stream = await HttpSendAsync(request).ConfigureAwait(false); + var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); return _serializer.Deserialize(new JsonTextReader(new StreamReader(stream))); } - async Task HttpGetStringAsync(string url) + async Task HttpGetStringAsync(string url, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Get, url); - var stream = await HttpSendAsync(request).ConfigureAwait(false); + var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); return await new StreamReader(stream).ReadToEndAsync(); } - async Task HttpSendAsync(HttpRequestMessage request) + async Task HttpSendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { 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 response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - + if (response.IsSuccessStatusCode) return stream; @@ -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/AppInstances/AppInstanceEntity.cs b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs index f4921c5..cbae7ef 100644 --- a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs +++ b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; using Seq.Api.Model.Apps; using Seq.Api.Model.Signals; @@ -13,9 +14,7 @@ public AppInstanceEntity() InvocationOverridableSettings = new List(); InvocationOverridableSettingDefinitions = new List(); EventsPerSuppressionWindow = 1; -#pragma warning disable 618 - SignalIds = new List(); -#pragma warning restore 618 + Metrics = new AppInstanceMetricsPart(); } public string Title { get; set; } @@ -29,11 +28,10 @@ public AppInstanceEntity() public int ChannelCapacity { get; set; } public TimeSpan SuppressionTime { get; set; } public int EventsPerSuppressionWindow { get; set; } - public int? ProcessedEventsPerMinute { get; set; } - - [Obsolete("Replaced by InputSignalExpression.")] - public List SignalIds { get; set; } public List InvocationOverridableSettingDefinitions { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public AppInstanceMetricsPart Metrics { get; set; } } } diff --git a/src/Seq.Api/Model/AppInstances/AppInstanceMetricsPart.cs b/src/Seq.Api/Model/AppInstances/AppInstanceMetricsPart.cs new file mode 100644 index 0000000..0f94b74 --- /dev/null +++ b/src/Seq.Api/Model/AppInstances/AppInstanceMetricsPart.cs @@ -0,0 +1,10 @@ +namespace Seq.Api.Model.AppInstances +{ + public class AppInstanceMetricsPart + { + public int ReceivedEventsPerMinute { get; set; } + public int EmittedEventsPerMinute { get; set; } + public long ProcessWorkingSetBytes { get; set; } + public bool IsRunning { get; set; } + } +} diff --git a/src/Seq.Api/Model/Backups/BackupEntity.cs b/src/Seq.Api/Model/Backups/BackupEntity.cs index a59bd0b..4631a56 100644 --- a/src/Seq.Api/Model/Backups/BackupEntity.cs +++ b/src/Seq.Api/Model/Backups/BackupEntity.cs @@ -1,6 +1,4 @@ -using System; - -namespace Seq.Api.Model.Backups +namespace Seq.Api.Model.Backups { public class BackupEntity : Entity { diff --git a/src/Seq.Api/Model/Events/DeleteResultPart.cs b/src/Seq.Api/Model/Events/DeleteResultPart.cs index f9e6e43..ebab4f2 100644 --- a/src/Seq.Api/Model/Events/DeleteResultPart.cs +++ b/src/Seq.Api/Model/Events/DeleteResultPart.cs @@ -2,6 +2,5 @@ { public class DeleteResultPart { - public long DeletedEventCount { get; set; } } } diff --git a/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs b/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs index 4032d9e..058ffd3 100644 --- a/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs +++ b/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs @@ -1,28 +1,25 @@ using System.Collections.Generic; +using Newtonsoft.Json; 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 bool IsDefault { get; set; } + public string OwnerId { get; set; } + public HashSet AssignedPermissions { get; set; } = new HashSet(); + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public ApiKeyMetricsPart Metrics { get; set; } = new ApiKeyMetricsPart(); } } diff --git a/src/Seq.Api/Model/Inputs/ApiKeyMetricsPart.cs b/src/Seq.Api/Model/Inputs/ApiKeyMetricsPart.cs index b0c3dfd..5ac3870 100644 --- a/src/Seq.Api/Model/Inputs/ApiKeyMetricsPart.cs +++ b/src/Seq.Api/Model/Inputs/ApiKeyMetricsPart.cs @@ -4,5 +4,6 @@ public class ApiKeyMetricsPart { public int ArrivalsPerMinute { get; set; } public int InfluxPerMinute { get; set; } + public long IngestedBytesPerMinute { get; set; } } } \ No newline at end of file diff --git a/src/Seq.Api/Model/Monitoring/AlertPart.cs b/src/Seq.Api/Model/Monitoring/AlertPart.cs index 6e3b8c0..cb3a9fe 100644 --- a/src/Seq.Api/Model/Monitoring/AlertPart.cs +++ b/src/Seq.Api/Model/Monitoring/AlertPart.cs @@ -10,6 +10,7 @@ public class AlertPart public string Id { get; set; } public string Condition { get; set; } + public string Title { get; set; } public TimeSpan MeasurementWindow { get; set; } public TimeSpan StabilizationWindow { get; set; } = TimeSpan.FromSeconds(30); public TimeSpan SuppressionTime { get; set; } diff --git a/src/Seq.Api/Model/Monitoring/DashboardEntity.cs b/src/Seq.Api/Model/Monitoring/DashboardEntity.cs index a94b268..0e932ea 100644 --- a/src/Seq.Api/Model/Monitoring/DashboardEntity.cs +++ b/src/Seq.Api/Model/Monitoring/DashboardEntity.cs @@ -9,6 +9,8 @@ public class DashboardEntity : Entity public string Title { get; set; } + public bool IsProtected { get; set; } + public SignalExpressionPart SignalExpression { get; set; } public List Charts { get; set; } = new List(); diff --git a/src/Seq.Api/Model/Monitoring/MeasurementDisplayStylePart.cs b/src/Seq.Api/Model/Monitoring/MeasurementDisplayStylePart.cs index 4c47411..75209f4 100644 --- a/src/Seq.Api/Model/Monitoring/MeasurementDisplayStylePart.cs +++ b/src/Seq.Api/Model/Monitoring/MeasurementDisplayStylePart.cs @@ -6,6 +6,7 @@ public class MeasurementDisplayStylePart public bool LineFillToZeroY { get; set; } public bool LineShowMarkers { get; set; } = true; public bool BarOverlaySum { get; set; } + public bool SuppressLegend { get; set; } public MeasurementDisplayPalette Palette { get; set; } = MeasurementDisplayPalette.Default; } } 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/Settings/SettingName.cs b/src/Seq.Api/Model/Settings/SettingName.cs index d5cb31d..f9ab369 100644 --- a/src/Seq.Api/Model/Settings/SettingName.cs +++ b/src/Seq.Api/Model/Settings/SettingName.cs @@ -18,9 +18,12 @@ public enum SettingName LazilyFlushEventWrites, MasterKeyIsBackedUp, MinimumFreeStorageSpace, + NewUserShowSignalIds, + NewUserShowQueryIds, + NewUserShowDashboardIds, RequireApiKeyForWritingEvents, RawEventMaximumContentLength, RawPayloadMaximumContentLength, ThemeStyles } -} \ No newline at end of file +} 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..cab6ddd 100644 --- a/src/Seq.Api/Model/Signals/SignalEntity.cs +++ b/src/Seq.Api/Model/Signals/SignalEntity.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using Newtonsoft.Json; namespace Seq.Api.Model.Signals { @@ -19,12 +21,17 @@ public SignalEntity() public List TaggedProperties { get; set; } - public bool IsWatched { get; set; } + // ReSharper disable once UnusedMember.Global + [Obsolete("This member has been renamed `IsProtected` to better reflect its purpose.")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool? IsRestricted { get; set; } - public bool IsRestricted { get; set; } + public bool IsProtected { get; set; } public SignalGrouping Grouping { get; set; } public string ExplicitGroupName { get; set; } + + public string OwnerId { get; set; } } } diff --git a/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs b/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs index a09112a..289d4c1 100644 --- a/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs +++ b/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs @@ -14,6 +14,9 @@ public SqlQueryEntity() public string Sql { get; set; } - public bool Show { get; set; } + public bool IsProtected { get; set; } + + public string OwnerId { get; set; } + } -} \ No newline at end of file +} 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/Model/Workspaces/WorkspaceContentPart.cs b/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs new file mode 100644 index 0000000..bc32a5d --- /dev/null +++ b/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Seq.Api.Model.Workspaces +{ + public class WorkspaceContentPart + { + public List SignalIds { get; set; } = new List(); + public List QueryIds { get; set; } = new List(); + public List DashboardIds { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs b/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs new file mode 100644 index 0000000..27ed398 --- /dev/null +++ b/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs @@ -0,0 +1,12 @@ +namespace Seq.Api.Model.Workspaces +{ + public class WorkspaceEntity : Entity + { + public string Title { get; set; } + public string Description { get; set; } + public string OwnerId { get; set; } + public bool IsProtected { get; set; } + + public WorkspaceContentPart Content { get; set; } = new WorkspaceContentPart(); + } +} diff --git a/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs b/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs index cc52a1a..0771ced 100644 --- a/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Inputs; @@ -12,35 +13,36 @@ internal ApiKeysResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(string ownerId = null, CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + var parameters = new Dictionary { { "ownerId", ownerId } }; + return await GroupListAsync("Items", parameters, cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(ApiKeyEntity entity) + public async Task AddAsync(ApiKeyEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(ApiKeyEntity entity) + public async Task RemoveAsync(ApiKeyEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(ApiKeyEntity entity) + public async Task UpdateAsync(ApiKeyEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs b/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs index 42cc2e9..9c3ef4d 100644 --- a/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.IO; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Client; using Seq.Api.Model; @@ -18,70 +20,97 @@ internal ApiResourceGroup(string name, ISeqConnection connection) _connection = connection; } - protected SeqApiClient Client { get { return _connection.Client; } } + protected SeqApiClient Client => _connection.Client; - protected Task LoadGroupAsync() + protected Task LoadGroupAsync(CancellationToken cancellationToken = default) { - return _connection.LoadResourceGroupAsync(_name); + return _connection.LoadResourceGroupAsync(_name, cancellationToken); } - protected async Task GroupGetAsync(string link, IDictionary parameters = null) + protected async Task GroupGetAsync(string link, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - return await Client.GetAsync(group, link, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.GetAsync(group, link, parameters, cancellationToken).ConfigureAwait(false); } - protected async Task GroupGetStringAsync(string link, IDictionary parameters = null) + protected async Task GroupGetStringAsync(string link, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - return await Client.GetStringAsync(group, link, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.GetStringAsync(group, link, parameters, cancellationToken).ConfigureAwait(false); } - protected async Task> GroupListAsync(string link, IDictionary parameters = null) + protected async Task> GroupListAsync(string link, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - return await Client.ListAsync(group, link, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.ListAsync(group, link, parameters, cancellationToken).ConfigureAwait(false); } - protected async Task GroupPostAsync(string link, TEntity content, IDictionary parameters = null) + protected async Task GroupPostAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - await Client.PostAsync(group, link, content, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + await Client.PostAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false); } - protected async Task GroupPostReadStringAsync(string link, TEntity content, IDictionary parameters = null) + protected async Task GroupPostReadStringAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - return await Client.PostReadStringAsync(group, link, content, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.PostReadStringAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false); } - protected async Task GroupPostAsync(string link, TEntity content, IDictionary parameters = null) + protected async Task GroupPostReadBytesAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - return await Client.PostAsync(group, link, content, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.PostReadStreamAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false); } - protected async Task GroupPutAsync(string link, TEntity content, IDictionary parameters = null) + protected async Task GroupPostAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - await Client.PutAsync(group, link, content, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.PostAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false); } - protected async Task GroupDeleteAsync(string link, TEntity content, IDictionary parameters = null) + protected async Task GroupPutAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - await Client.DeleteAsync(group, link, content, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + await Client.PutAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false); } - protected async Task GroupDeleteAsync(string link, TEntity content, IDictionary parameters = null) + protected async Task GroupDeleteAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) { - var group = await LoadGroupAsync().ConfigureAwait(false); - return await Client.DeleteAsync(group, link, content, parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + await Client.DeleteAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false); + } + + protected async Task GroupDeleteAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default) + { + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.DeleteAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false); } protected string GetLink(TEntity entity, string link, string orElse) where TEntity : ILinked { return entity.Links.ContainsKey(link) ? link : orElse; } + + protected async Task GroupCreateAsync(TEntity entity, + IDictionary parameters = null, CancellationToken cancellationToken = default) + where TEntity : ILinked + { + ILinked resource; + string link; + + if (entity.Links.ContainsKey("Create")) + { + resource = entity; + link = "Create"; + } + else + { + resource = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + link = "Items"; + } + + return await Client.PostAsync(resource, link, entity, parameters, cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Seq.Api/ResourceGroups/AppInstancesResourceGroup.cs b/src/Seq.Api/ResourceGroups/AppInstancesResourceGroup.cs index fe64248..32327a9 100644 --- a/src/Seq.Api/ResourceGroups/AppInstancesResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/AppInstancesResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.AppInstances; @@ -12,48 +13,49 @@ internal AppInstancesResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync(string appId) + public async Task TemplateAsync(string appId, CancellationToken cancellationToken = default) { if (appId == null) throw new ArgumentNullException(nameof(appId)); - return await GroupGetAsync("Template", new Dictionary { { "appId", appId } }).ConfigureAwait(false); + return await GroupGetAsync("Template", new Dictionary { { "appId", appId } }, cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(AppInstanceEntity entity, bool runOnExisting = false) + public async Task AddAsync(AppInstanceEntity entity, bool runOnExisting = false, CancellationToken cancellationToken = default) { if (entity == null) throw new ArgumentNullException(nameof(entity)); - return await Client.PostAsync(entity, "Create", entity, new Dictionary { { "runOnExisting", runOnExisting } }).ConfigureAwait(false); + return await Client.PostAsync(entity, "Create", entity, new Dictionary { { "runOnExisting", runOnExisting } }, cancellationToken) + .ConfigureAwait(false); } - public async Task RemoveAsync(AppInstanceEntity entity) + public async Task RemoveAsync(AppInstanceEntity entity, CancellationToken cancellationToken = default) { if (entity == null) throw new ArgumentNullException(nameof(entity)); - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(AppInstanceEntity entity) + public async Task UpdateAsync(AppInstanceEntity entity, CancellationToken cancellationToken = default) { if (entity == null) throw new ArgumentNullException(nameof(entity)); - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task InvokeAsync(AppInstanceEntity entity, string eventId, IReadOnlyDictionary settingOverrides) + public async Task InvokeAsync(AppInstanceEntity entity, string eventId, IReadOnlyDictionary settingOverrides, CancellationToken cancellationToken = default) { if (entity == null) throw new ArgumentNullException(nameof(entity)); if (eventId == null) throw new ArgumentNullException(nameof(eventId)); var postedSettings = settingOverrides ?? new Dictionary(); - await Client.PostAsync(entity, "Invoke", postedSettings, new Dictionary{{"eventId", eventId}}); + await Client.PostAsync(entity, "Invoke", postedSettings, new Dictionary{{"eventId", eventId}}, cancellationToken); } } } diff --git a/src/Seq.Api/ResourceGroups/AppsResourceGroup.cs b/src/Seq.Api/ResourceGroups/AppsResourceGroup.cs index 6488b3b..751d2ea 100644 --- a/src/Seq.Api/ResourceGroups/AppsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/AppsResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Apps; @@ -12,44 +13,53 @@ internal AppsResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(AppEntity entity) + public async Task AddAsync(AppEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await Client.PostAsync(entity, "Create", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(AppEntity entity) + public async Task RemoveAsync(AppEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(AppEntity entity) + public async Task UpdateAsync(AppEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task InstallPackageAsync(string feedId, string packageId, string version = null) + public async Task InstallPackageAsync(string feedId, string packageId, string version = null, CancellationToken cancellationToken = default) { if (feedId == null) throw new ArgumentNullException(nameof(feedId)); if (packageId == null) throw new ArgumentNullException(nameof(packageId)); - var parameters = new Dictionary{{ "feedId", feedId}, {"packageId", packageId}}; + var parameters = new Dictionary { { "feedId", feedId }, { "packageId", packageId } }; if (version != null) parameters.Add("version", version); - return await GroupPostAsync("InstallPackage", new object(), parameters).ConfigureAwait(false); + return await GroupPostAsync("InstallPackage", new object(), parameters, cancellationToken).ConfigureAwait(false); + } + + public async Task UpdatePackageAsync(AppEntity entity, string version = null, bool force = false) + { + if (entity == null) throw new ArgumentNullException(nameof(entity)); + var parameters = new Dictionary(); + if (force) parameters.Add("force", true); + if (version != null) parameters.Add("version", version); + return await Client.PostAsync(entity, "UpdatePackage", new object(), parameters).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/BackupsResourceGroup.cs b/src/Seq.Api/ResourceGroups/BackupsResourceGroup.cs index 3740359..08398d4 100644 --- a/src/Seq.Api/ResourceGroups/BackupsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/BackupsResourceGroup.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Backups; @@ -12,15 +14,20 @@ internal BackupsResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); + } + + public async Task DownloadImmediateAsync(CancellationToken cancellationToken = default) + { + return await GroupPostReadBytesAsync("Immediate", new object(), cancellationToken: cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs b/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs index 39eee61..5b63e9b 100644 --- a/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Monitoring; @@ -12,36 +13,36 @@ internal DashboardsResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync(string ownerId = null, bool shared = false) + public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default) { var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } }; - return await GroupListAsync("Items", parameters).ConfigureAwait(false); + return await GroupListAsync("Items", parameters, cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(DashboardEntity entity) + public async Task AddAsync(DashboardEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(DashboardEntity entity) + public async Task RemoveAsync(DashboardEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(DashboardEntity entity) + public async Task UpdateAsync(DashboardEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs index 08ab59a..c06f518 100644 --- a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Data; using Seq.Api.Model.Signals; @@ -23,17 +24,19 @@ internal DataResourceGroup(ISeqConnection connection) /// A constructed signal that may not appear on the server, for example, a that has been /// created but not saved, a signal from another server, or the modified representation of an entity already persisted. /// The query timeout; if not specified, the query will run until completion. + /// Token through which the operation can be cancelled. /// A structured result set. public async Task QueryAsync( string query, - DateTime rangeStartUtc, + DateTime? rangeStartUtc = null, DateTime? rangeEndUtc = null, SignalExpressionPart signal = null, SignalEntity unsavedSignal = null, - TimeSpan? timeout = null) + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) { MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, out var body, out var parameters); - return await GroupPostAsync("Query", body, parameters).ConfigureAwait(false); + return await GroupPostAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); } /// @@ -46,23 +49,25 @@ public async Task QueryAsync( /// A constructed signal that may not appear on the server, for example, a that has been /// created but not saved, a signal from another server, or the modified representation of an entity already persisted. /// The query timeout; if not specified, the query will run until completion. + /// Token through which the operation can be cancelled. /// A CSV result set. public async Task QueryCsvAsync( string query, - DateTime rangeStartUtc, + DateTime? rangeStartUtc = null, DateTime? rangeEndUtc = null, SignalExpressionPart signal = null, SignalEntity unsavedSignal = null, - TimeSpan? timeout = null) + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) { MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, out var body, out var parameters); parameters.Add("format", "text/csv"); - return await GroupPostReadStringAsync("Query", body, parameters).ConfigureAwait(false); + return await GroupPostReadStringAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); } static void MakeParameters( string query, - DateTime rangeStartUtc, + DateTime? rangeStartUtc, DateTime? rangeEndUtc, SignalExpressionPart signal, SignalEntity unsavedSignal, @@ -72,22 +77,20 @@ static void MakeParameters( { parameters = new Dictionary { - {"q", query}, - {nameof(rangeStartUtc), rangeStartUtc} + {"q", query} }; + 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/DiagnosticsResourceGroup.cs b/src/Seq.Api/ResourceGroups/DiagnosticsResourceGroup.cs index a983bd3..4ae8e7c 100644 --- a/src/Seq.Api/ResourceGroups/DiagnosticsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DiagnosticsResourceGroup.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using Seq.Api.Model.Diagnostics; namespace Seq.Api.ResourceGroups @@ -10,19 +11,19 @@ internal DiagnosticsResourceGroup(ISeqConnection connection) { } - public async Task GetServerMetricsAsync() + public async Task GetServerMetricsAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("ServerMetrics").ConfigureAwait(false); + return await GroupGetAsync("ServerMetrics", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task GetServerStatusAsync() + public async Task GetServerStatusAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("ServerStatus").ConfigureAwait(false); + return await GroupGetAsync("ServerStatus", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task GetIngestionLogAsync() + public async Task GetIngestionLogAsync(CancellationToken cancellationToken = default) { - return await GroupGetStringAsync("IngestionLog").ConfigureAwait(false); + return await GroupGetStringAsync("IngestionLog", cancellationToken: cancellationToken).ConfigureAwait(false); } } } 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..cca68e7 100644 --- a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Events; using Seq.Api.Model.Shared; @@ -16,10 +17,25 @@ 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. + /// Token through which the operation can be cancelled. + /// The event. + public async Task FindAsync( + string id, + bool render = false, + string permalinkId = null, + CancellationToken cancellationToken = default) { 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, cancellationToken).ConfigureAwait(false); } /// @@ -38,17 +54,23 @@ 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. + /// Token through which the operation can be cancelled. /// The complete list of events, ordered from least to most recent. public async Task> ListAsync( SignalExpressionPart signal = null, - string filter = null, + string filter = null, int count = 30, string startAtId = null, string afterId = null, bool render = false, DateTime? fromDateUtc = null, DateTime? toDateUtc = null, - int? shortCircuitAfter = null) + int? shortCircuitAfter = null, + string permalinkId = null, + CancellationToken cancellationToken = default) { var parameters = new Dictionary { { "count", count } }; if (signal != null) { parameters.Add("signal", signal.ToString()); } @@ -59,13 +81,14 @@ 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; while (true) { - var resultSet = await GroupGetAsync("InSignal", parameters).ConfigureAwait(false); + var resultSet = await GroupGetAsync("InSignal", parameters, cancellationToken).ConfigureAwait(false); chunks.Add(resultSet.Events); remaining -= resultSet.Events.Count; @@ -104,18 +127,24 @@ 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. + /// Token through which the operation can be cancelled. /// The complete list of events, ordered from least to most recent. public async Task InSignalAsync( SignalEntity unsavedSignal = null, SignalExpressionPart signal = null, - string filter = null, + string filter = null, int count = 30, string startAtId = null, - string afterId = null, + string afterId = null, bool render = false, DateTime? fromDateUtc = null, DateTime? toDateUtc = null, - int? shortCircuitAfter = null) + int? shortCircuitAfter = null, + string permalinkId = null, + CancellationToken cancellationToken = default) { var parameters = new Dictionary{{ "count", count }}; if (signal != null) { parameters.Add("signal", signal.ToString()); } @@ -126,21 +155,45 @@ 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); + return await GroupPostAsync("InSignal", body, parameters, cancellationToken).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. + /// Token through which the operation can be cancelled. + /// The complete list of events, ordered from least to most recent. public async Task InSignalAsync( SignalExpressionPart signal, - string filter = null, + string filter = null, int count = 30, string startAtId = null, - string afterId = null, + string afterId = null, bool render = false, DateTime? fromDateUtc = null, DateTime? toDateUtc = null, - int? shortCircuitAfter = null) + int? shortCircuitAfter = null, + string permalinkId = null, + CancellationToken cancellationToken = default) { if (signal == null) throw new ArgumentNullException(nameof(signal)); @@ -156,8 +209,9 @@ 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); + return await GroupGetAsync("InSignal", parameters, cancellationToken).ConfigureAwait(false); } /// @@ -170,13 +224,15 @@ public async Task InSignalAsync( /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use connection.Expressions.ToStrictAsync(). /// Earliest (inclusive) date/time from which to delete. /// Latest (exclusive) date/time from which to delete. + /// Token through which the operation can be cancelled. /// A result carrying the count of events deleted. public async Task DeleteInSignalAsync( SignalEntity unsavedSignal = null, SignalExpressionPart signal = null, - string filter = null, + string filter = null, DateTime? fromDateUtc = null, - DateTime? toDateUtc = null) + DateTime? toDateUtc = null, + CancellationToken cancellationToken = default) { var parameters = new Dictionary(); if (signal != null) { parameters.Add("signal", signal.ToString()); } @@ -185,7 +241,7 @@ public async Task DeleteInSignalAsync( if (toDateUtc != null) { parameters.Add("toDateUtc", toDateUtc.Value); } var body = unsavedSignal ?? new SignalEntity(); - return await GroupDeleteAsync("DeleteInSignal", body, parameters).ConfigureAwait(false); + return await GroupDeleteAsync("DeleteInSignal", body, parameters, cancellationToken).ConfigureAwait(false); } /// @@ -195,18 +251,20 @@ public async Task DeleteInSignalAsync( /// 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(). + /// Token through which the operation can be cancelled. /// An observable that will stream events from the server to subscribers. Events will be buffered server-side until the first /// subscriber connects, ensure at least one subscription is made in order to avoid event loss. public async Task> StreamAsync( SignalExpressionPart signal = null, - string filter = null) + string filter = null, + CancellationToken cancellationToken = default) { var parameters = new Dictionary(); if (signal != null) { parameters.Add("signal", signal.ToString()); } if (filter != null) { parameters.Add("filter", filter); } - var group = await LoadGroupAsync().ConfigureAwait(false); - return await Client.StreamAsync(group, "Stream", parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.StreamAsync(group, "Stream", parameters, cancellationToken).ConfigureAwait(false); } /// @@ -216,18 +274,20 @@ public async Task> StreamAsync( /// 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(). + /// Token through which the operation can be cancelled. /// An observable that will stream events from the server to subscribers. Events will be buffered server-side until the first /// subscriber connects, ensure at least one subscription is made in order to avoid event loss. public async Task> StreamDocumentsAsync( SignalExpressionPart signal = null, - string filter = null) + string filter = null, + CancellationToken cancellationToken = default) { var parameters = new Dictionary(); if (signal != null) { parameters.Add("signal", signal.ToString()); } if (filter != null) { parameters.Add("filter", filter); } - var group = await LoadGroupAsync().ConfigureAwait(false); - return await Client.StreamTextAsync(group, "Stream", parameters).ConfigureAwait(false); + var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false); + return await Client.StreamTextAsync(group, "Stream", parameters, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs b/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs index c43d1c2..1088f8b 100644 --- a/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Expressions; @@ -11,20 +12,20 @@ internal ExpressionsResourceGroup(ISeqConnection connection) { } - public Task ToStrictAsync(string fuzzy) + public Task ToStrictAsync(string fuzzy, CancellationToken cancellationToken = default) { return GroupGetAsync("ToStrict", new Dictionary { {"fuzzy", fuzzy} - }); + }, cancellationToken); } - public Task ToSqlAsync(string fuzzy) + public Task ToSqlAsync(string fuzzy, CancellationToken cancellationToken = default) { return GroupGetAsync("ToSql", new Dictionary { {"fuzzy", fuzzy} - }); + }, cancellationToken); } } } \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/FeedsResourceGroup.cs b/src/Seq.Api/ResourceGroups/FeedsResourceGroup.cs index 2e0bdec..3e0e1bb 100644 --- a/src/Seq.Api/ResourceGroups/FeedsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/FeedsResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Feeds; @@ -12,35 +13,35 @@ internal FeedsResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(NuGetFeedEntity entity) + public async Task AddAsync(NuGetFeedEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await Client.PostAsync(entity, "Create", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(NuGetFeedEntity entity) + public async Task RemoveAsync(NuGetFeedEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(NuGetFeedEntity entity) + public async Task UpdateAsync(NuGetFeedEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/ISeqConnection.cs b/src/Seq.Api/ResourceGroups/ISeqConnection.cs index cbdcb0c..ca9d9e8 100644 --- a/src/Seq.Api/ResourceGroups/ISeqConnection.cs +++ b/src/Seq.Api/ResourceGroups/ISeqConnection.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using Seq.Api.Client; using Seq.Api.Model; @@ -6,7 +7,7 @@ namespace Seq.Api.ResourceGroups { interface ISeqConnection { - Task LoadResourceGroupAsync(string name); + Task LoadResourceGroupAsync(string name, CancellationToken cancellationToken = default); SeqApiClient Client { get; } } } diff --git a/src/Seq.Api/ResourceGroups/LicensesResourceGroup.cs b/src/Seq.Api/ResourceGroups/LicensesResourceGroup.cs index 3064d4f..af40301 100644 --- a/src/Seq.Api/ResourceGroups/LicensesResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/LicensesResourceGroup.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; -using Seq.Api.Model.Inputs; using Seq.Api.Model.License; namespace Seq.Api.ResourceGroups @@ -13,30 +13,30 @@ internal LicensesResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task FindCurrentAsync() + public async Task FindCurrentAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Current").ConfigureAwait(false); + return await GroupGetAsync("Current", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(LicenseEntity entity) + public async Task UpdateAsync(LicenseEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task DowngradeAsync() + public async Task DowngradeAsync(CancellationToken cancellationToken = default) { - await GroupPostAsync("Downgrade", new object()).ConfigureAwait(false); + await GroupPostAsync("Downgrade", new object(), cancellationToken: cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/PermalinksResourceGroup.cs b/src/Seq.Api/ResourceGroups/PermalinksResourceGroup.cs index a98c098..a899911 100644 --- a/src/Seq.Api/ResourceGroups/PermalinksResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/PermalinksResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Permalinks; @@ -14,54 +15,52 @@ internal PermalinksResourceGroup(ISeqConnection connection) public async Task FindAsync( string id, - bool includeEvent = false, - bool renderEvent = false, - bool includeUser = false) + bool includeEvent = false, + bool renderEvent = false, + CancellationToken cancellationToken = default) { 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); + return await GroupGetAsync("Item", parameters, cancellationToken).ConfigureAwait(false); } public async Task> ListAsync( - bool includeEvent = false, - bool renderEvent = false, - bool includeUser = false) + bool includeEvent = false, + bool renderEvent = false, + CancellationToken cancellationToken = default) { var parameters = new Dictionary { {"includeEvent", includeEvent}, - {"renderEvent", renderEvent}, - {"includeUser", includeUser} + {"renderEvent", renderEvent} }; - return await GroupListAsync("Items", parameters).ConfigureAwait(false); + return await GroupListAsync("Items", parameters, cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(PermalinkEntity entity) + public async Task AddAsync(PermalinkEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(PermalinkEntity entity) + public async Task RemoveAsync(PermalinkEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(PermalinkEntity entity) + public async Task UpdateAsync(PermalinkEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/Seq.Api/ResourceGroups/RetentionPoliciesResourceGroup.cs b/src/Seq.Api/ResourceGroups/RetentionPoliciesResourceGroup.cs index e750e41..1e3ac18 100644 --- a/src/Seq.Api/ResourceGroups/RetentionPoliciesResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/RetentionPoliciesResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Retention; @@ -12,35 +13,35 @@ internal RetentionPoliciesResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(RetentionPolicyEntity entity) + public async Task AddAsync(RetentionPolicyEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await Client.PostAsync(entity, "Create", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(RetentionPolicyEntity entity) + public async Task RemoveAsync(RetentionPolicyEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(RetentionPolicyEntity entity) + public async Task UpdateAsync(RetentionPolicyEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/SettingsResourceGroup.cs b/src/Seq.Api/ResourceGroups/SettingsResourceGroup.cs index 1ec5292..9968d1c 100644 --- a/src/Seq.Api/ResourceGroups/SettingsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/SettingsResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Settings; @@ -12,40 +13,50 @@ internal SettingsResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task FindNamedAsync(SettingName name) + public async Task FindNamedAsync(SettingName name, CancellationToken cancellationToken = default) { - return await GroupGetAsync(name.ToString()).ConfigureAwait(false); + return await GroupGetAsync(name.ToString(), cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(SettingEntity entity) + public async Task AddAsync(SettingEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await Client.PostAsync(entity, "Create", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(SettingEntity entity) + public async Task RemoveAsync(SettingEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(SettingEntity entity) + public async Task UpdateAsync(SettingEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + public async Task GetInternalErrorReportingAsync() + { + return await GroupGetAsync("InternalErrorReporting").ConfigureAwait(false); + } + + public async Task UpdateInternalErrorReportingAsync(InternalErrorReportingSettingsPart internalErrorReporting) + { + await GroupPutAsync("InternalErrorReporting", internalErrorReporting).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..594143c 100644 --- a/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs @@ -1,46 +1,48 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Signals; namespace Seq.Api.ResourceGroups { - public class SignalsResourceGroup : EntityResourceGroup + public class SignalsResourceGroup : ApiResourceGroup { internal SignalsResourceGroup(ISeqConnection connection) : base("Signals", connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } }; + return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(SignalEntity entity) + public async Task AddAsync(SignalEntity entity, CancellationToken cancellationToken = default) { - return await GroupCreateAsync(entity).ConfigureAwait(false); + return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(SignalEntity entity) + public async Task RemoveAsync(SignalEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(SignalEntity entity) + public async Task UpdateAsync(SignalEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs b/src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs index 3b84725..7d5aac3 100644 --- a/src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.SqlQueries; @@ -12,35 +13,36 @@ internal SqlQueriesResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } }; + return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(SqlQueryEntity entity) + public async Task AddAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(SqlQueryEntity entity) + public async Task RemoveAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(SqlQueryEntity entity) + public async Task UpdateAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Seq.Api/ResourceGroups/UpdatesResourceGroup.cs b/src/Seq.Api/ResourceGroups/UpdatesResourceGroup.cs index 157adb5..953d136 100644 --- a/src/Seq.Api/ResourceGroups/UpdatesResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/UpdatesResourceGroup.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Updates; @@ -11,9 +12,9 @@ internal UpdatesResourceGroup(ISeqConnection connection) { } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Seq.Api/ResourceGroups/UsersResourceGroup.cs b/src/Seq.Api/ResourceGroups/UsersResourceGroup.cs index 83baf25..2f94120 100644 --- a/src/Seq.Api/ResourceGroups/UsersResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/UsersResourceGroup.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Seq.Api.Model.Users; using System.Linq; +using System.Threading; using Seq.Api.Client; namespace Seq.Api.ResourceGroups @@ -14,69 +15,69 @@ internal UsersResourceGroup(ISeqConnection connection) { } - public async Task FindAsync(string id) + public async Task FindAsync(string id, CancellationToken cancellationToken = default) { if (id == null) throw new ArgumentNullException(nameof(id)); - return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false); } - public async Task FindCurrentAsync() + public async Task FindCurrentAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Current").ConfigureAwait(false); + return await GroupGetAsync("Current", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task> ListAsync() + public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await GroupListAsync("Items").ConfigureAwait(false); + return await GroupListAsync("Items", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task TemplateAsync() + public async Task TemplateAsync(CancellationToken cancellationToken = default) { - return await GroupGetAsync("Template").ConfigureAwait(false); + return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task AddAsync(UserEntity entity) + public async Task AddAsync(UserEntity entity, CancellationToken cancellationToken = default) { - return await Client.PostAsync(entity, "Create", entity).ConfigureAwait(false); + return await Client.PostAsync(entity, "Create", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task RemoveAsync(UserEntity entity) + public async Task RemoveAsync(UserEntity entity, CancellationToken cancellationToken = default) { - await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task UpdateAsync(UserEntity entity) + public async Task UpdateAsync(UserEntity entity, CancellationToken cancellationToken = default) { - await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task LoginAsync(string username, string password) + public async Task LoginAsync(string username, string password, CancellationToken cancellationToken = default) { if (username == null) throw new ArgumentNullException(nameof(username)); if (password == null) throw new ArgumentNullException(nameof(password)); var credentials = new CredentialsPart {Username = username, Password = password}; - return await GroupPostAsync("Login", credentials).ConfigureAwait(false); + return await GroupPostAsync("Login", credentials, cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task LogoutAsync() + public async Task LogoutAsync(CancellationToken cancellationToken = default) { - await GroupPostAsync("Logout", new object()).ConfigureAwait(false); + await GroupPostAsync("Logout", new object(), cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task GetSearchHistoryAsync(UserEntity entity) + public async Task GetSearchHistoryAsync(UserEntity entity, CancellationToken cancellationToken = default) { - return await Client.GetAsync(entity, "SearchHistory").ConfigureAwait(false); + return await Client.GetAsync(entity, "SearchHistory", cancellationToken: cancellationToken).ConfigureAwait(false); } - public async Task LoginWindowsIntegratedAsync() + public async Task LoginWindowsIntegratedAsync(CancellationToken cancellationToken = default) { - var providers = await GroupGetAsync("AuthenticationProviders").ConfigureAwait(false); + var providers = await GroupGetAsync("AuthenticationProviders", cancellationToken: cancellationToken).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."); - var response = await Client.HttpClient.GetAsync(provider.Url).ConfigureAwait(false); + throw new SeqApiException("The Integrated Windows Authentication provider is not available.", null); + var response = await Client.HttpClient.GetAsync(provider.Url, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); - return await FindCurrentAsync().ConfigureAwait(false); + return await FindCurrentAsync(cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Seq.Api/ResourceGroups/WorkspacesResourceGroup.cs b/src/Seq.Api/ResourceGroups/WorkspacesResourceGroup.cs new file mode 100644 index 0000000..2dd6109 --- /dev/null +++ b/src/Seq.Api/ResourceGroups/WorkspacesResourceGroup.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Seq.Api.Model.Workspaces; + +namespace Seq.Api.ResourceGroups +{ + public class WorkspacesResourceGroup : ApiResourceGroup + { + internal WorkspacesResourceGroup(ISeqConnection connection) + : base("Workspaces", connection) + { + } + + public async Task FindAsync(string id) + { + if (id == null) throw new ArgumentNullException(nameof(id)); + return await GroupGetAsync("Item", new Dictionary { { "id", id } }).ConfigureAwait(false); + } + + public async Task> ListAsync(string ownerId = null, bool shared = false) + { + var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } }; + return await GroupListAsync("Items", parameters).ConfigureAwait(false); + } + + public async Task TemplateAsync() + { + return await GroupGetAsync("Template").ConfigureAwait(false); + } + + public async Task AddAsync(WorkspaceEntity entity) + { + return await GroupCreateAsync(entity).ConfigureAwait(false); + } + + public async Task RemoveAsync(WorkspaceEntity entity) + { + await Client.DeleteAsync(entity, "Self", entity).ConfigureAwait(false); + } + + public async Task UpdateAsync(WorkspaceEntity entity) + { + await Client.PutAsync(entity, "Self", entity).ConfigureAwait(false); + } + } +} diff --git a/src/Seq.Api/Seq.Api.csproj b/src/Seq.Api/Seq.Api.csproj index 0b4a849..e8b24b0 100644 --- a/src/Seq.Api/Seq.Api.csproj +++ b/src/Seq.Api/Seq.Api.csproj @@ -1,37 +1,27 @@ - + Client library for the Seq HTTP API. - 4.2.2 + 5.0.0 Datalust;Contributors - netstandard1.3;net452 - $(NoWarn);CS1591 + netstandard1.3;net46;netstandard2.0 + $(NoWarn);CS1591;CS1573 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 + 7.1 - + + - - - - - - - - - - + \ No newline at end of file diff --git a/src/Seq.Api/SeqConnection.cs b/src/Seq.Api/SeqConnection.cs index 5d611b2..6d3e722 100644 --- a/src/Seq.Api/SeqConnection.cs +++ b/src/Seq.Api/SeqConnection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Threading; using System.Threading.Tasks; using Seq.Api.Client; using Seq.Api.Model; @@ -10,16 +11,15 @@ namespace Seq.Api { public class SeqConnection : ISeqConnection { - readonly SeqApiClient _client; 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); - - _root = new Lazy>(() => _client.GetRootAsync()); + Client = new SeqApiClient(serverUrl, apiKey, useDefaultCredentials); + + _root = new Lazy>(() => Client.GetRootAsync()); } public ApiKeysResourceGroup ApiKeys => new ApiKeysResourceGroup(this); @@ -52,20 +52,24 @@ public SeqConnection(string serverUrl, string apiKey = null) public SignalsResourceGroup Signals => new SignalsResourceGroup(this); + public SqlQueriesResourceGroup SqlQueries => new SqlQueriesResourceGroup(this); + public UpdatesResourceGroup Updates => new UpdatesResourceGroup(this); public UsersResourceGroup Users => new UsersResourceGroup(this); - public async Task LoadResourceGroupAsync(string name) + public WorkspacesResourceGroup Workspaces => new WorkspacesResourceGroup(this); + + public async Task LoadResourceGroupAsync(string name, CancellationToken cancellationToken = default) { - return await _resourceGroups.GetOrAdd(name, ResourceGroupFactory).ConfigureAwait(false); + return await _resourceGroups.GetOrAdd(name, s => ResourceGroupFactory(s, cancellationToken)).ConfigureAwait(false); } - async Task ResourceGroupFactory(string name) + async Task ResourceGroupFactory(string name, CancellationToken cancellationToken = default) { - return await _client.GetAsync(await _root.Value, name + "Resources").ConfigureAwait(false); + return await Client.GetAsync(await _root.Value, name + "Resources", cancellationToken: cancellationToken).ConfigureAwait(false); } - public SeqApiClient Client => _client; + public SeqApiClient Client { get; } } } diff --git a/src/Seq.Api/Streams/ObservableStream.cs b/src/Seq.Api/Streams/ObservableStream.cs index ac96d4d..0ddc419 100644 --- a/src/Seq.Api/Streams/ObservableStream.cs +++ b/src/Seq.Api/Streams/ObservableStream.cs @@ -38,11 +38,8 @@ public sealed partial class ObservableStream : IObservable, IDisposable internal ObservableStream(ClientWebSocket socket, Func deserialize) { - if (socket == null) throw new ArgumentNullException(nameof(socket)); - if (deserialize == null) throw new ArgumentNullException(nameof(deserialize)); - - _deserialize = deserialize; - _socket = socket; + _deserialize = deserialize ?? throw new ArgumentNullException(nameof(deserialize)); + _socket = socket ?? throw new ArgumentNullException(nameof(socket)); } public IDisposable Subscribe(IObserver observer) @@ -179,7 +176,7 @@ void End() OnError(exceptions); } - void OnError(IList exceptions) + static void OnError(IEnumerable exceptions) { // This will hit TaskScheduler.UnobservedTaskException throw new AggregateException("At least one observer failed to accept the event", exceptions); @@ -205,10 +202,21 @@ public void Dispose() } } } - catch { } + catch + { + // Don't mask exceptions by throwing here during unwinding + } if (_run != null) - _run.ConfigureAwait(false).GetAwaiter().GetResult(); + { + try + { + _run.ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (TaskCanceledException) + { + } + } _socket.Dispose(); } diff --git a/test/Seq.Api.Tests/Properties/AssemblyInfo.cs b/test/Seq.Api.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 083fd7a..0000000 --- a/test/Seq.Api.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Seq.Api.Tests")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/test/Seq.Api.Tests/Seq.Api.Tests.csproj b/test/Seq.Api.Tests/Seq.Api.Tests.csproj index 397fd34..b573d26 100644 --- a/test/Seq.Api.Tests/Seq.Api.Tests.csproj +++ b/test/Seq.Api.Tests/Seq.Api.Tests.csproj @@ -1,15 +1,8 @@  - netcoreapp1.0;net46 - Seq.Api.Tests - Seq.Api.Tests + net46;netcoreapp2.0 true - $(PackageTargetFallback);dnxcore50;portable-net45+win8 - 1.0.4 - false - false - false @@ -27,4 +20,4 @@ - + \ No newline at end of file