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