From 703287ce7ae5833a9c6848df997ec9d86e99145c Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Thu, 24 Feb 2022 15:01:21 +1100 Subject: [PATCH 1/9] variables and permissions --- src/Seq.Api/Client/SeqApiClient.cs | 18 +-- src/Seq.Api/Model/Alerting/AlertEntity.cs | 2 +- .../Model/AppInstances/AppInstanceEntity.cs | 10 ++ src/Seq.Api/Model/Apps/AppSettingPart.cs | 11 ++ .../Model/Dashboarding/DashboardEntity.cs | 2 +- src/Seq.Api/Model/Data/QueryResultPart.cs | 7 + src/Seq.Api/Model/Link.cs | 2 +- src/Seq.Api/Model/Security/Permission.cs | 19 ++- .../Model/Shared/EvaluationContextPart.cs | 39 ++++++ src/Seq.Api/Model/Signals/SignalEntity.cs | 2 +- .../Model/SqlQueries/SqlQueryEntity.cs | 2 +- .../Model/Workspaces/WorkspaceEntity.cs | 2 +- .../ResourceGroups/DataResourceGroup.cs | 16 ++- .../ResourceGroups/EventsResourceGroup.cs | 129 +++++++++--------- src/Seq.Api/Seq.Api.csproj | 11 +- 15 files changed, 183 insertions(+), 89 deletions(-) create mode 100644 src/Seq.Api/Model/Shared/EvaluationContextPart.cs diff --git a/src/Seq.Api/Client/SeqApiClient.cs b/src/Seq.Api/Client/SeqApiClient.cs index 12d8f88..d3565ec 100644 --- a/src/Seq.Api/Client/SeqApiClient.cs +++ b/src/Seq.Api/Client/SeqApiClient.cs @@ -35,16 +35,16 @@ namespace Seq.Api.Client /// /// A low-level client that provides navigation over the linked resource structure of the Seq HTTP API. /// - public class SeqApiClient : IDisposable + public sealed class SeqApiClient : IDisposable { readonly string _apiKey; - // Future versions of Seq may not completely support v1 features, however + // Future versions of Seq may not completely support vN-1 features, however // providing this as an Accept header will ensure what compatibility is available // can be utilized. - const string SeqApiV8MediaType = "application/vnd.datalust.seq.v8+json"; + const string SeqApiV9MediaType = "application/vnd.datalust.seq.v9+json"; - readonly CookieContainer _cookies = new CookieContainer(); + readonly CookieContainer _cookies = new(); readonly JsonSerializer _serializer = JsonSerializer.Create( new JsonSerializerSettings { @@ -89,7 +89,7 @@ public SeqApiClient(string serverUrl, string apiKey = null, Action(ILinked entity, string link, TEntity conten var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); - using (var reader = new StreamReader(stream)) - reader.ReadToEnd(); + using var reader = new StreamReader(stream); + await reader.ReadToEndAsync(); } /// @@ -213,8 +213,8 @@ public async Task PostReadStringAsync(ILinked entity, string li var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) }; var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); - using (var reader = new StreamReader(stream)) - return await reader.ReadToEndAsync(); + using var reader = new StreamReader(stream); + return await reader.ReadToEndAsync(); } /// diff --git a/src/Seq.Api/Model/Alerting/AlertEntity.cs b/src/Seq.Api/Model/Alerting/AlertEntity.cs index 5dc1745..ad8bab1 100644 --- a/src/Seq.Api/Model/Alerting/AlertEntity.cs +++ b/src/Seq.Api/Model/Alerting/AlertEntity.cs @@ -42,7 +42,7 @@ public class AlertEntity : Entity public string OwnerId { get; set; } /// - /// If true, the alert can only be modified by users with the permission. + /// If true, the alert can only be modified by users with the permission. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs index b1e0739..3d7014a 100644 --- a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs +++ b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs @@ -160,5 +160,15 @@ public AppInstanceEntity() [Obsolete("Use !AcceptDirectInvocation instead.")] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool? DisallowManualInput { get; set; } + + /// + /// The name of the application. + /// + public string AppName { get; set; } + + /// + /// Is the application an input application? + /// + public bool IsInput { get; set; } } } diff --git a/src/Seq.Api/Model/Apps/AppSettingPart.cs b/src/Seq.Api/Model/Apps/AppSettingPart.cs index 47dd5f7..8cc3343 100644 --- a/src/Seq.Api/Model/Apps/AppSettingPart.cs +++ b/src/Seq.Api/Model/Apps/AppSettingPart.cs @@ -54,5 +54,16 @@ public class AppSettingPart /// for the setting. /// public List AllowedValues { get; set; } = new List(); + + /// + /// If the setting value contains code in a programming or markup language, the + /// language name. + /// + /// Valid names are a subset of the names and aliases recognized by + /// GitHub + /// Linguist. The generic value code will be specified if the language is non-specific but + /// the value should be displayed in fixed-width font. Seq also recognizes the special Seq-specific + /// template and expression syntaxes. + public string Syntax { get; set; } } } diff --git a/src/Seq.Api/Model/Dashboarding/DashboardEntity.cs b/src/Seq.Api/Model/Dashboarding/DashboardEntity.cs index edac684..c9da631 100644 --- a/src/Seq.Api/Model/Dashboarding/DashboardEntity.cs +++ b/src/Seq.Api/Model/Dashboarding/DashboardEntity.cs @@ -34,7 +34,7 @@ public class DashboardEntity : Entity public string Title { get; set; } /// - /// If true, only users with can modify the dashboard. + /// If true, only users with can modify the dashboard. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/Model/Data/QueryResultPart.cs b/src/Seq.Api/Model/Data/QueryResultPart.cs index dcbefd6..2007a80 100644 --- a/src/Seq.Api/Model/Data/QueryResultPart.cs +++ b/src/Seq.Api/Model/Data/QueryResultPart.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Collections.Generic; using Newtonsoft.Json; namespace Seq.Api.Model.Data @@ -50,6 +51,12 @@ public class QueryResultPart /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public TimeseriesPart[] Series { get; set; } + + /// + /// Result variables. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary Variables { get; set; } /// /// On error only, a description of the error. diff --git a/src/Seq.Api/Model/Link.cs b/src/Seq.Api/Model/Link.cs index 6d48fc8..4e95246 100644 --- a/src/Seq.Api/Model/Link.cs +++ b/src/Seq.Api/Model/Link.cs @@ -26,7 +26,7 @@ namespace Seq.Api.Model /// parameterized in order to produce a complete URI (if the template contains no /// parameters then it may also be a literal URI). /// - public struct Link + public readonly struct Link { /// /// An empty link. diff --git a/src/Seq.Api/Model/Security/Permission.cs b/src/Seq.Api/Model/Security/Permission.cs index 8c8b015..ed61b3f 100644 --- a/src/Seq.Api/Model/Security/Permission.cs +++ b/src/Seq.Api/Model/Security/Permission.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; + namespace Seq.Api.Model.Security { /// @@ -46,10 +48,23 @@ public enum Permission /// Write-access to signals, alerts, preferences etc. /// Write, - + /// /// Access to administrative features of Seq, management of other users, app installation, backups. /// - Setup, + [Obsolete("The `Setup` permission has been replaced by `Project` and `System`.")] + Setup = 5, + + /// + /// Access to settings required for day-to-day operation of Seq, such as users, retention policies, API keys. + /// + Project, + + /// + /// Access to settings and features that interact with, or provide access to, the underlying host server, + /// such as app (plug-in) installation, backup settings, cluster configuration, diagnostics, and features + /// relying on outbound network access like package feeds and update checks. + /// + System } } diff --git a/src/Seq.Api/Model/Shared/EvaluationContextPart.cs b/src/Seq.Api/Model/Shared/EvaluationContextPart.cs new file mode 100644 index 0000000..7538029 --- /dev/null +++ b/src/Seq.Api/Model/Shared/EvaluationContextPart.cs @@ -0,0 +1,39 @@ +// Copyright © Datalust and contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; +using Seq.Api.Model.Signals; + +#nullable enable + +namespace Seq.Api.Model.Shared +{ + /// + /// Specifies the context that queries and searches execute within. + /// + public class EvaluationContextPart + { + /// + /// An unsaved or modified signal. + /// + public SignalEntity? Signal { get; set; } + + /// + /// Values for free variables that appear in the query or search condition. + /// + /// Variables will only be visible in the query or search being executed: any free variables + /// in signal filters will remain undefined. + public Dictionary? Variables { get; set; } + } +} diff --git a/src/Seq.Api/Model/Signals/SignalEntity.cs b/src/Seq.Api/Model/Signals/SignalEntity.cs index 38f8e62..9c90ba2 100644 --- a/src/Seq.Api/Model/Signals/SignalEntity.cs +++ b/src/Seq.Api/Model/Signals/SignalEntity.cs @@ -64,7 +64,7 @@ public SignalEntity() public bool? IsRestricted { get; set; } /// - /// If true, the signal can only be modified by users with the permission. + /// If true, the signal can only be modified by users with the permission. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs b/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs index 528c865..49b3b15 100644 --- a/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs +++ b/src/Seq.Api/Model/SqlQueries/SqlQueryEntity.cs @@ -46,7 +46,7 @@ public SqlQueryEntity() public string Sql { get; set; } /// - /// If true, only users with permission can edit the signal. + /// If true, only users with permission can edit the SQL query. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs b/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs index 23bd0cf..a674084 100644 --- a/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs +++ b/src/Seq.Api/Model/Workspaces/WorkspaceEntity.cs @@ -39,7 +39,7 @@ public class WorkspaceEntity : Entity public string OwnerId { get; set; } /// - /// If true, only users with the permission can modify the workspace. + /// If true, only users with the permission can modify the workspace. /// public bool IsProtected { get; set; } diff --git a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs index d8cc53a..a2a7a70 100644 --- a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs @@ -17,6 +17,7 @@ using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Data; +using Seq.Api.Model.Shared; using Seq.Api.Model.Signals; namespace Seq.Api.ResourceGroups @@ -41,6 +42,7 @@ internal DataResourceGroup(ILoadResourceGroup 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. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. /// A structured result set. public async Task QueryAsync( @@ -50,10 +52,11 @@ public async Task QueryAsync( SignalExpressionPart signal = null, SignalEntity unsavedSignal = null, TimeSpan? timeout = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { - MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, out var body, out var parameters); - return await GroupPostAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); + MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, variables, out var body, out var parameters); + return await GroupPostAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); } /// @@ -66,6 +69,7 @@ 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. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. /// A CSV result set. public async Task QueryCsvAsync( @@ -75,9 +79,10 @@ public async Task QueryCsvAsync( SignalExpressionPart signal = null, SignalEntity unsavedSignal = null, TimeSpan? timeout = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { - MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, out var body, out var parameters); + MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, variables, out var body, out var parameters); parameters.Add("format", "text/csv"); return await GroupPostReadStringAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false); } @@ -89,7 +94,8 @@ static void MakeParameters( SignalExpressionPart signal, SignalEntity unsavedSignal, TimeSpan? timeout, - out SignalEntity body, + Dictionary variables, + out EvaluationContextPart body, out Dictionary parameters) { parameters = new Dictionary @@ -109,7 +115,7 @@ static void MakeParameters( if (timeout != null) parameters.Add("timeoutMS", timeout.Value.TotalMilliseconds.ToString("0")); - body = unsavedSignal ?? new SignalEntity(); + body = new EvaluationContextPart { Signal = unsavedSignal, Variables = variables }; } } } diff --git a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs index 96ee76f..bcb0194 100644 --- a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs @@ -14,7 +14,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Events; @@ -58,9 +58,10 @@ public async Task FindAsync( } /// - /// 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. + /// Efficiently retrieve all events that match a set of conditions. /// + /// 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. /// 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(). @@ -77,8 +78,10 @@ public async Task FindAsync( /// 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. + /// Values for any free variables that appear in . /// The complete list of events, ordered from least to most recent. - public async Task> ListAsync( + public async IAsyncEnumerable EnumerateAsync( + SignalEntity unsavedSignal = null, SignalExpressionPart signal = null, string filter = null, int count = 30, @@ -89,48 +92,48 @@ public async Task> ListAsync( DateTime? toDateUtc = null, int? shortCircuitAfter = null, string permalinkId = null, + Dictionary variables = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) { - var parameters = new Dictionary { { "count", count } }; - if (signal != null) { parameters.Add("signal", signal.ToString()); } - if (filter != null) { parameters.Add("filter", filter); } - if (startAtId != null) { parameters.Add("startAtId", startAtId); } - if (afterId != null) { parameters.Add("afterId", afterId); } - if (render) { parameters.Add("render", true); } - 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>(); + // Limit server resource consumption. Assuming events are between 1000 and 10,000 bytes each, + // responses will be between 5 and 50 MB. + const int pageSize = 5000; + + var nextAfterId = afterId; var remaining = count; + var nextCount = Math.Min(remaining, pageSize); while (true) { - var resultSet = await GroupGetAsync("InSignal", parameters, cancellationToken).ConfigureAwait(false); - chunks.Add(resultSet.Events); - remaining -= resultSet.Events.Count; - - if (remaining <= 0) - break; + var resultSet = await PageAsync(unsavedSignal, signal, filter, nextCount, startAtId, nextAfterId, render, + fromDateUtc, toDateUtc, shortCircuitAfter, permalinkId, variables, cancellationToken).ConfigureAwait(false); - if (resultSet.Statistics.Status != ResultSetStatus.Partial) - break; + foreach (var evt in resultSet.Events) + { + yield return evt; + remaining -= 1; - parameters["afterId"] = resultSet.Statistics.LastReadEventId; - parameters["count"] = remaining; - } + if (remaining <= 0) + yield break; + } - var result = new List(chunks.Sum(c => c.Count)); - foreach (var evt in chunks.SelectMany(c => c)) - result.Add(evt); + if (remaining <= 0) + yield break; - return result; + if (resultSet.Statistics.Status == ResultSetStatus.Complete) + yield break; + + nextAfterId = resultSet.Statistics.LastReadEventId; + nextCount = Math.Min(remaining, pageSize); + } } + /// - /// 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. + /// Retrieve all events that match a set of conditions. The complete result is buffered into memory, + /// so if a large result set is expected, use EnumerateAsync(), or PageAsync() with + /// and to page the results. /// /// 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. @@ -149,9 +152,10 @@ public async Task> ListAsync( /// 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. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. - /// The complete list of events, ordered from least to most recent. - public async Task InSignalAsync( + /// The result set with a page of events. + public async Task> ListAsync( SignalEntity unsavedSignal = null, SignalExpressionPart signal = null, string filter = null, @@ -163,27 +167,23 @@ public async Task InSignalAsync( DateTime? toDateUtc = null, int? shortCircuitAfter = null, string permalinkId = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { - var parameters = new Dictionary{{ "count", count }}; - if (signal != null) { parameters.Add("signal", signal.ToString()); } - if (filter != null) { parameters.Add("filter", filter); } - if (startAtId != null) { parameters.Add("startAtId", startAtId); } - if (afterId != null) { parameters.Add("afterId", afterId); } - if (render) { parameters.Add("render", true); } - 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, cancellationToken).ConfigureAwait(false); + var results = new List(); + await foreach (var item in EnumerateAsync(unsavedSignal, signal, filter, count, startAtId, afterId, render, + fromDateUtc, toDateUtc, shortCircuitAfter, permalinkId, variables, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + results.Add(item); + return results; } /// - /// 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. + /// Retrieve a page of events that match a set of conditions. /// + /// 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. /// 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(). @@ -199,10 +199,12 @@ public async Task InSignalAsync( /// 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. + /// Values for any free variables that appear in . /// 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, + /// The result set with a page of events. + public async Task PageAsync( + SignalEntity unsavedSignal = null, + SignalExpressionPart signal = null, string filter = null, int count = 30, string startAtId = null, @@ -212,15 +214,11 @@ public async Task InSignalAsync( DateTime? toDateUtc = null, int? shortCircuitAfter = null, string permalinkId = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { - if (signal == null) throw new ArgumentNullException(nameof(signal)); - - var parameters = new Dictionary - { - { "signal", signal.ToString() }, - { "count", count } - }; + var parameters = new Dictionary{{ "count", count }}; + if (signal != null) { parameters.Add("signal", signal.ToString()); } if (filter != null) { parameters.Add("filter", filter); } if (startAtId != null) { parameters.Add("startAtId", startAtId); } if (afterId != null) { parameters.Add("afterId", afterId); } @@ -230,7 +228,8 @@ public async Task InSignalAsync( if (shortCircuitAfter != null) { parameters.Add("shortCircuitAfter", shortCircuitAfter.Value); } if (permalinkId != null) { parameters.Add("permalinkId", permalinkId); } - return await GroupGetAsync("InSignal", parameters, cancellationToken).ConfigureAwait(false); + var body = new EvaluationContextPart { Signal = unsavedSignal, Variables = variables }; + return await GroupPostAsync("InSignal", body, parameters, cancellationToken).ConfigureAwait(false); } /// @@ -243,14 +242,16 @@ 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. + /// Values for any free variables that appear in . /// Token through which the operation can be cancelled. /// A result carrying the count of events deleted. - public async Task DeleteInSignalAsync( + public async Task DeleteAsync( SignalEntity unsavedSignal = null, SignalExpressionPart signal = null, string filter = null, DateTime? fromDateUtc = null, DateTime? toDateUtc = null, + Dictionary variables = null, CancellationToken cancellationToken = default) { var parameters = new Dictionary(); @@ -259,8 +260,8 @@ public async Task DeleteInSignalAsync( if (fromDateUtc != null) { parameters.Add("fromDateUtc", fromDateUtc.Value); } if (toDateUtc != null) { parameters.Add("toDateUtc", toDateUtc.Value); } - var body = unsavedSignal ?? new SignalEntity(); - return await GroupDeleteAsync("DeleteInSignal", body, parameters, cancellationToken).ConfigureAwait(false); + var body = new EvaluationContextPart { Signal = unsavedSignal, Variables = variables }; + return await GroupDeleteAsync("DeleteInSignal", body, parameters, cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Seq.Api/Seq.Api.csproj b/src/Seq.Api/Seq.Api.csproj index 140e72b..caa9edf 100644 --- a/src/Seq.Api/Seq.Api.csproj +++ b/src/Seq.Api/Seq.Api.csproj @@ -1,9 +1,9 @@ Client library for the Seq HTTP API. - 2021.4.0 + 2022.1.0 Datalust;Contributors - netstandard2.0 + netstandard2.0;net6.0 true true seq @@ -11,7 +11,7 @@ seq-api-icon.png https://github.com/datalust/seq-api Apache-2.0 - 8 + 9 @@ -19,6 +19,11 @@ + + + + + From 310edccd16d940bdda01a8a297f71f82c8df9933 Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Thu, 24 Feb 2022 15:15:04 +1100 Subject: [PATCH 2/9] Added net6.0 target to test project --- test/Seq.Api.Tests/Seq.Api.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Seq.Api.Tests/Seq.Api.Tests.csproj b/test/Seq.Api.Tests/Seq.Api.Tests.csproj index 6545b6f..ce38adc 100644 --- a/test/Seq.Api.Tests/Seq.Api.Tests.csproj +++ b/test/Seq.Api.Tests/Seq.Api.Tests.csproj @@ -1,7 +1,7 @@  - net461;netcoreapp2.2 + net461;netcoreapp2.2;net6.0 true From 47faf0dc9a6940628c2e2890bc457fa85db8db3d Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Thu, 24 Feb 2022 15:33:36 +1100 Subject: [PATCH 3/9] use appveyor image that supports net6.0 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 7821de9..d07e56f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ version: '{build}' skip_tags: true -image: Visual Studio 2019 +image: Visual Studio 2022 build_script: - ps: ./Build.ps1 test: off From 9ccfc0af2c0c9e536ec75279b69efa64749f1617 Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Thu, 24 Feb 2022 15:59:34 +1100 Subject: [PATCH 4/9] Made AppInstanceEntity.cs properties nullable. --- .../Model/AppInstances/AppInstanceEntity.cs | 18 +++++++++++------- src/Seq.Api/Model/Security/Permission.cs | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs index 3d7014a..5297189 100644 --- a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs +++ b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs @@ -20,6 +20,8 @@ using Seq.Api.Model.Signals; using Seq.Api.ResourceGroups; +#nullable enable + namespace Seq.Api.Model.AppInstances { /// @@ -49,12 +51,12 @@ public AppInstanceEntity() /// /// The id of the that this is an instance of. /// - public string AppId { get; set; } + public string? AppId { get; set; } /// /// The user-friendly title of the app instance. /// - public string Title { get; set; } + public string? Title { get; set; } /// /// Values for the settings exposed by the app. @@ -95,7 +97,7 @@ public AppInstanceEntity() /// The signal expression describing which events will be sent to the app; if null, /// all events will reach the app. /// - public SignalExpressionPart StreamedSignalExpression { get; set; } + public SignalExpressionPart? StreamedSignalExpression { get; set; } /// /// If a value is specified, events will be buffered to disk and sorted by timestamp-order @@ -162,13 +164,15 @@ public AppInstanceEntity() public bool? DisallowManualInput { get; set; } /// - /// The name of the application. + /// The name of the app. /// - public string AppName { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string? AppName { get; set; } /// - /// Is the application an input application? + /// If true, then the app is able to write events to the log. /// - public bool IsInput { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool? IsInput { get; set; } } } diff --git a/src/Seq.Api/Model/Security/Permission.cs b/src/Seq.Api/Model/Security/Permission.cs index ed61b3f..8da1c61 100644 --- a/src/Seq.Api/Model/Security/Permission.cs +++ b/src/Seq.Api/Model/Security/Permission.cs @@ -53,7 +53,7 @@ public enum Permission /// Access to administrative features of Seq, management of other users, app installation, backups. /// [Obsolete("The `Setup` permission has been replaced by `Project` and `System`.")] - Setup = 5, + Setup, /// /// Access to settings required for day-to-day operation of Seq, such as users, retention policies, API keys. From cb4f90d3517ec48049eeacce1e57cdf3d1cf0a24 Mon Sep 17 00:00:00 2001 From: Liam McLennan Date: Thu, 24 Feb 2022 16:02:23 +1100 Subject: [PATCH 5/9] Made AppInstanceEntity.cs more nullable --- .../Model/AppInstances/AppInstanceEntity.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs index 5297189..e563308 100644 --- a/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs +++ b/src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs @@ -61,7 +61,7 @@ public AppInstanceEntity() /// /// Values for the settings exposed by the app. /// - public Dictionary Settings { get; set; } + public Dictionary? Settings { get; set; } /// /// If true, administrative users may invoke the app manually or through alerts. @@ -79,13 +79,13 @@ public AppInstanceEntity() /// The settings that can be overridden at invocation time (when an event is sent to /// the instance). /// - public List InvocationOverridableSettings { get; set; } + public List? InvocationOverridableSettings { get; set; } /// /// Metadata describing the overridable settings. This field is provided by the server /// and cannot be modified. /// - public List InvocationOverridableSettingDefinitions { get; set; } + public List? InvocationOverridableSettingDefinitions { get; set; } /// /// If true, events will be streamed to the app. Otherwise, events will be @@ -123,31 +123,31 @@ public AppInstanceEntity() /// Settings that control how events are ingested through the app. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public InputSettingsPart InputSettings { get; set; } + public InputSettingsPart? InputSettings { get; set; } /// /// Metrics describing the state and activity of the app process. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public AppInstanceProcessMetricsPart ProcessMetrics { get; set; } + public AppInstanceProcessMetricsPart? ProcessMetrics { get; set; } /// /// Information about ingestion activity through this app. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public InputMetricsPart InputMetrics { get; set; } + public InputMetricsPart? InputMetrics { get; set; } /// /// Information about the app's diagnostic input. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public InputMetricsPart DiagnosticInputMetrics { get; set; } + public InputMetricsPart? DiagnosticInputMetrics { get; set; } /// /// Information about events output through the app. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public AppInstanceOutputMetricsPart OutputMetrics { get; set; } + public AppInstanceOutputMetricsPart? OutputMetrics { get; set; } /// /// Obsolete. From 9cf400a22ecc5d9e50e4d5befa7ffb198ad0b3d9 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 15 Mar 2022 10:00:42 +1000 Subject: [PATCH 6/9] Don't convert date-looking strings to `DateTime` when deserializing untyped JSON; use `decimal` as the base numeric type for untyped JSON numbers, better matching Seq's internal representation. --- src/Seq.Api/Client/SeqApiClient.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Seq.Api/Client/SeqApiClient.cs b/src/Seq.Api/Client/SeqApiClient.cs index d3565ec..f0b690b 100644 --- a/src/Seq.Api/Client/SeqApiClient.cs +++ b/src/Seq.Api/Client/SeqApiClient.cs @@ -48,7 +48,9 @@ public sealed class SeqApiClient : IDisposable readonly JsonSerializer _serializer = JsonSerializer.Create( new JsonSerializerSettings { - Converters = { new StringEnumConverter(), new LinkCollectionConverter() } + Converters = { new StringEnumConverter(), new LinkCollectionConverter() }, + DateParseHandling = DateParseHandling.None, + FloatParseHandling = FloatParseHandling.Decimal }); /// From 59d24f14455a65d533acb78eb40a26e8f0d078db Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 15 Mar 2022 10:01:10 +1000 Subject: [PATCH 7/9] Add `Permission.Organization`. --- src/Seq.Api/Model/Security/Permission.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Seq.Api/Model/Security/Permission.cs b/src/Seq.Api/Model/Security/Permission.cs index 8da1c61..9ed273e 100644 --- a/src/Seq.Api/Model/Security/Permission.cs +++ b/src/Seq.Api/Model/Security/Permission.cs @@ -56,15 +56,21 @@ public enum Permission Setup, /// - /// Access to settings required for day-to-day operation of Seq, such as users, retention policies, API keys. + /// Access to settings that control data ingestion, storage, dashboarding and alerting. /// Project, /// /// Access to settings and features that interact with, or provide access to, the underlying host server, /// such as app (plug-in) installation, backup settings, cluster configuration, diagnostics, and features - /// relying on outbound network access like package feeds and update checks. + /// relying on outbound network access like package feeds and update checks. This permission is required for + /// configuration of the authentication provider and related settings. /// - System + System, + + /// + /// Create, edit, and delete user accounts, reset local user passwords. + /// + Organization } } From 85ecc6f7360d2649873355b6621de0f5c334542c Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 15 Mar 2022 10:01:28 +1000 Subject: [PATCH 8/9] Expose a text description of user roles. --- src/Seq.Api/Model/Security/RoleEntity.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Seq.Api/Model/Security/RoleEntity.cs b/src/Seq.Api/Model/Security/RoleEntity.cs index 57ad0e8..e8a66e5 100644 --- a/src/Seq.Api/Model/Security/RoleEntity.cs +++ b/src/Seq.Api/Model/Security/RoleEntity.cs @@ -29,6 +29,11 @@ public class RoleEntity : Entity /// /// Permissions granted to users in the role. /// - public HashSet Permissions { get; set; } = new HashSet(); + public HashSet Permissions { get; set; } = new(); + + /// + /// Optionally, an extended description of the role. + /// + public string Description { get; set; } } } From 48bc3764a34567fb614a0c377c76c4fe34da23f4 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 15 Mar 2022 10:03:55 +1000 Subject: [PATCH 9/9] Update API for search history tracking. --- ...rchHistoryItemStatus.cs => SearchHistoryItemAction.cs} | 8 ++++---- src/Seq.Api/Model/Users/SearchHistoryItemPart.cs | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) rename src/Seq.Api/Model/Users/{SearchHistoryItemStatus.cs => SearchHistoryItemAction.cs} (90%) diff --git a/src/Seq.Api/Model/Users/SearchHistoryItemStatus.cs b/src/Seq.Api/Model/Users/SearchHistoryItemAction.cs similarity index 90% rename from src/Seq.Api/Model/Users/SearchHistoryItemStatus.cs rename to src/Seq.Api/Model/Users/SearchHistoryItemAction.cs index 7944494..b374beb 100644 --- a/src/Seq.Api/Model/Users/SearchHistoryItemStatus.cs +++ b/src/Seq.Api/Model/Users/SearchHistoryItemAction.cs @@ -17,7 +17,7 @@ namespace Seq.Api.Model.Users /// /// An operation applied to a search history item. /// - public enum SearchHistoryItemStatus + public enum SearchHistoryItemAction { /// /// The item was used (make it more recent). @@ -28,10 +28,10 @@ public enum SearchHistoryItemStatus /// The item has been pinned. /// Pinned, - + /// - /// The item has been un-pinned. + /// The item has been unpinned. /// - Forgotten + Unpinned } } \ No newline at end of file diff --git a/src/Seq.Api/Model/Users/SearchHistoryItemPart.cs b/src/Seq.Api/Model/Users/SearchHistoryItemPart.cs index 1014a7a..6465910 100644 --- a/src/Seq.Api/Model/Users/SearchHistoryItemPart.cs +++ b/src/Seq.Api/Model/Users/SearchHistoryItemPart.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ReSharper disable ClassNeverInstantiated.Global + namespace Seq.Api.Model.Users { /// @@ -20,13 +22,13 @@ namespace Seq.Api.Model.Users public class SearchHistoryItemPart { /// - /// The filter entered by the user into the filter bar. + /// The search or query entered by the user into the search bar. /// public string Search { get; set; } /// /// Status to apply to the search history item. /// - public SearchHistoryItemStatus Status { get; set; } + public SearchHistoryItemAction Action { get; set; } } }