diff --git a/Build.ps1 b/Build.ps1 index ff877e2..40b95c7 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -14,7 +14,7 @@ if($LASTEXITCODE -ne 0) { exit 1 } $branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; -$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"] +$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"] echo "build: Version suffix is $suffix" diff --git a/README.md b/README.md index 71ee848..5ad2fc5 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ 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.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). +For a more complete example, see the [seq-tail app included in the source](https://github.com/datalust/seq-api/blob/main/example/SeqTail/Program.cs). #### Creating entities @@ -43,7 +43,7 @@ signal.Title = "Signal 123"; await connection.Signals.AddAsync(signal); ``` -See the [signal-copy app](https://github.com/datalust/seq-api/blob/master/example/SignalCopy/Program.cs) for an example of this pattern in action. +See the [signal-copy app](https://github.com/datalust/seq-api/blob/main/example/SignalCopy/Program.cs) for an example of this pattern in action. ### Reading events diff --git a/appveyor.yml b/appveyor.yml index 4743d67..73bec11 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,11 +12,11 @@ deploy: secure: sMicBLl7Z83H/mhX10DL7Yqwa80ZHUbb9fRHKmxd5m2MN2DWAE1kbYH/GPQPFajZ skip_symbols: true on: - branch: /^(master|dev)$/ + branch: /^(main|dev)$/ - provider: GitHub auth_token: secure: hX+cZmW+9BCXy7vyH8myWsYdtQHyzzil9K5yvjJv7dK9XmyrGYYDj/DPzMqsXSjo artifact: /Seq.Api.*\.nupkg/ tag: v$(appveyor_build_version) on: - branch: master + branch: main diff --git a/example/SeqEnableAAD/Program.cs b/example/SeqEnableAAD/Program.cs new file mode 100644 index 0000000..259a6e1 --- /dev/null +++ b/example/SeqEnableAAD/Program.cs @@ -0,0 +1,91 @@ +using System; +using System.Threading.Tasks; +using DocoptNet; +using Seq.Api; +using Seq.Api.Model.Settings; + +namespace SeqEnableAAD +{ + class Program + { + const string Usage = @"seq-enable-aad: enable authentication on your Seq server (for initial setup of a new Seq server only). + +Usage: + seq-enable-aad.exe --uname= --tenantid= --clientid= --clientkey= [--authority=] + seq-enable-aad.exe (-h | --help) + +Options: + -h --help Show this screen. + --uname= Username. Azure Active Directory usernames must take the form of an email address. + --tenantid= Tenant ID. + --clientid= Client ID. + --clientkey= Client key. + --authority= Authority (optional, defaults to 'login.windows.net'). + "; + static void Main(string[] args) + { + Task.Run(async () => + { + try + { + var arguments = new Docopt().Apply(Usage, args, version: "Seq Enable AAD 0.1", exit: true); + + var server = arguments[""].ToString(); + var username = Normalize(arguments["--uname"]); + var tenantId = Normalize(arguments["--tenantid"]); + var clientId = Normalize(arguments["--clientid"]); + var clientKey = Normalize(arguments["--clientkey"]); + var authority = Normalize(arguments["--authority"]); + + await Run(server, username, tenantId, clientId, clientKey, authority); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.White; + Console.BackgroundColor = ConsoleColor.Red; + Console.WriteLine("seq-enable-aad: {0}", ex); + Console.ResetColor(); + Environment.Exit(-1); + } + }).Wait(); + } + + static string Normalize(ValueObject v) + { + if (v == null) return null; + var s = v.ToString(); + return string.IsNullOrWhiteSpace(s) ? null : s; + } + + static async Task Run(string server, string username, string tenantId, string clientId, string clientKey, string authority="login.windows.net") + { + var connection = new SeqConnection(server); + + var user = await connection.Users.FindCurrentAsync(); + var provider = await connection.Settings.FindNamedAsync(SettingName.AuthenticationProvider); + var cid = await connection.Settings.FindNamedAsync(SettingName.AzureADClientId); + var ckey = await connection.Settings.FindNamedAsync(SettingName.AzureADClientKey); + var aut = await connection.Settings.FindNamedAsync(SettingName.AzureADAuthority); + var tid = await connection.Settings.FindNamedAsync(SettingName.AzureADTenantId); + + user.Username = username; + provider.Value = "Azure Active Directory"; + cid.Value = clientId; + ckey.Value = clientKey; + tid.Value = tenantId; + aut.Value = authority; + + await connection.Users.UpdateAsync(user); + await connection.Settings.UpdateAsync(cid); + await connection.Settings.UpdateAsync(ckey); + await connection.Settings.UpdateAsync(tid); + await connection.Settings.UpdateAsync(aut); + + await connection.Settings.UpdateAsync(provider); // needs to go before IsAuthenticationEnabled but after the other settings + + var iae = await connection.Settings.FindNamedAsync(SettingName.IsAuthenticationEnabled); + iae.Value = true; + await connection.Settings.UpdateAsync(iae); // this update needs to happen last, as enabling auth will lock this connection out + } + } +} diff --git a/example/SeqEnableAAD/README.md b/example/SeqEnableAAD/README.md new file mode 100644 index 0000000..14c67af --- /dev/null +++ b/example/SeqEnableAAD/README.md @@ -0,0 +1,34 @@ +# Seq Enable AAD (Azure Active Directory) + +Be sure to read the [Seq Azure Active Directory documentation](https://docs.datalust.co/docs/azure-active-directory) to find the manual AAD setup instructions. + +## Example usage: + +``` +seq-enable-aad.exe https://seq.example.com --uname=example@microsoft.com --clientid=xxxxxx --tenantid=xxxxxx --clientkey=xxxxxx --authority=login.windows.net +``` + +### **Important note:** + +#### Windows + +Don't forget to set the "canonical URI" which Seq uses as a reply address for AAD. + +``` +seq config -k api.canonicalUri -v https://seq.example.com +seq service restart +``` + +#### Linux / Docker + +Don't forget to include the BASE_URI which Seq uses as a reply address for AAD. + +``` +docker run -d \ + --restart unless-stopped \ + --name seq \ + -p 5341:80 \ + -e ACCEPT_EULA=Y \ + -e BASE_URI=https://seq.example.com \ + datalust/seq:latest +``` \ No newline at end of file diff --git a/example/SeqEnableAAD/SeqEnableAAD.csproj b/example/SeqEnableAAD/SeqEnableAAD.csproj new file mode 100644 index 0000000..499ae03 --- /dev/null +++ b/example/SeqEnableAAD/SeqEnableAAD.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp3.1 + SeqEnableAuth + + + + + + + + + + + diff --git a/seq-api.sln b/seq-api.sln index 0c8feca..8fda661 100644 --- a/seq-api.sln +++ b/seq-api.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SeqTail", "example\SeqTail\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SignalCopy", "example\SignalCopy\SignalCopy.csproj", "{E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeqEnableAAD", "example\SeqEnableAAD\SeqEnableAAD.csproj", "{035B62FC-CAD7-4DF9-A2A3-FAAA64DF3B55}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,6 +55,10 @@ Global {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}.Debug|Any CPU.Build.0 = Debug|Any CPU {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}.Release|Any CPU.ActiveCfg = Release|Any CPU {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}.Release|Any CPU.Build.0 = Release|Any CPU + {035B62FC-CAD7-4DF9-A2A3-FAAA64DF3B55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {035B62FC-CAD7-4DF9-A2A3-FAAA64DF3B55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {035B62FC-CAD7-4DF9-A2A3-FAAA64DF3B55}.Release|Any CPU.ActiveCfg = Release|Any CPU + {035B62FC-CAD7-4DF9-A2A3-FAAA64DF3B55}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -63,6 +69,7 @@ Global {34BBD428-8297-484E-B771-0B72C172C264} = {1C66E116-DC21-4C8F-833E-A4C2DDF58487} {42CEBFBA-208F-40F1-AC95-13F05F6D5412} = {1C66E116-DC21-4C8F-833E-A4C2DDF58487} {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752} = {1C66E116-DC21-4C8F-833E-A4C2DDF58487} + {035B62FC-CAD7-4DF9-A2A3-FAAA64DF3B55} = {1C66E116-DC21-4C8F-833E-A4C2DDF58487} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {20BAB483-FB94-4373-8E4C-0F846B6DBFFC} diff --git a/src/Seq.Api/Model/Diagnostics/ServerMetricsEntity.cs b/src/Seq.Api/Model/Diagnostics/ServerMetricsEntity.cs index bca4e1c..6bb1079 100644 --- a/src/Seq.Api/Model/Diagnostics/ServerMetricsEntity.cs +++ b/src/Seq.Api/Model/Diagnostics/ServerMetricsEntity.cs @@ -28,6 +28,16 @@ public class ServerMetricsEntity : Entity public ServerMetricsEntity() { } + + /// + /// The start time in UTC of the events in the memory cache. + /// + public DateTime? EventStoreCacheStart { get; set; } + + /// + /// The end time in UTC of the events in the memory cache. + /// + public DateTime? EventStoreCacheEnd { get; set; } /// /// The number of days of events able to fit in the memory cache. diff --git a/src/Seq.Api/Model/Diagnostics/Storage/ColumnDescriptionPart.cs b/src/Seq.Api/Model/Diagnostics/Storage/ColumnDescriptionPart.cs new file mode 100644 index 0000000..9393642 --- /dev/null +++ b/src/Seq.Api/Model/Diagnostics/Storage/ColumnDescriptionPart.cs @@ -0,0 +1,33 @@ +using System; + +namespace Seq.Api.Model.Diagnostics.Storage +{ + /// + /// A description of a column in a rowset. + /// + public readonly struct ColumnDescriptionPart + { + /// + /// A label for the column. + /// + public string Label { get; } + + /// + /// Additional metadata describing the role of the column; this is separate from, + /// but related to, the runtime type of the column values. + /// + public ColumnType Type { get; } + + /// + /// Construct a . + /// + /// A label for the column. + /// Additional metadata describing the role of the column; this is separate from, + /// but related to, the runtime type of the column values. + public ColumnDescriptionPart(string label, ColumnType type) + { + Label = label ?? throw new ArgumentNullException(nameof(label)); + Type = type; + } + } +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Diagnostics/Storage/ColumnType.cs b/src/Seq.Api/Model/Diagnostics/Storage/ColumnType.cs new file mode 100644 index 0000000..7de3e51 --- /dev/null +++ b/src/Seq.Api/Model/Diagnostics/Storage/ColumnType.cs @@ -0,0 +1,19 @@ +namespace Seq.Api.Model.Diagnostics.Storage +{ + /// + /// Additional metadata describing the role of a column; this is separate from, + /// but related to, the runtime type of the column values. + /// + public enum ColumnType + { + /// + /// The column contains general data. + /// + General, + + /// + /// The column contains timestamps that may be used to create a timeseries. + /// + Timestamp, + } +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Diagnostics/Storage/RowsetPart.cs b/src/Seq.Api/Model/Diagnostics/Storage/RowsetPart.cs new file mode 100644 index 0000000..7632648 --- /dev/null +++ b/src/Seq.Api/Model/Diagnostics/Storage/RowsetPart.cs @@ -0,0 +1,19 @@ +namespace Seq.Api.Model.Diagnostics.Storage +{ + /// + /// Values in rows and columns. + /// + public class RowsetPart + { + /// + /// The columns of the rowset. + /// + public ColumnDescriptionPart[] Columns { get; set; } + + /// + /// An array of rows, where each row is an array of values + /// corresponding to the columns of the rowset. + /// + public object[][] Rows { get; set; } + } +} \ No newline at end of file diff --git a/src/Seq.Api/Model/Diagnostics/Storage/StorageConsumptionPart.cs b/src/Seq.Api/Model/Diagnostics/Storage/StorageConsumptionPart.cs new file mode 100644 index 0000000..4cbed6b --- /dev/null +++ b/src/Seq.Api/Model/Diagnostics/Storage/StorageConsumptionPart.cs @@ -0,0 +1,41 @@ +// 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 Seq.Api.Model.Shared; + +namespace Seq.Api.Model.Diagnostics.Storage +{ + /// + /// Describes storage space consumed by the event store, for a range + /// of event timestamps. + /// + public class StorageConsumptionPart + { + /// + /// The range of timestamps covered by the result. + /// + public DateTimeRange Range { get; set; } + + /// + /// The duration of the timestamp interval covered by each result. + /// + public uint IntervalMinutes { get; set; } + + /// + /// A potentially-sparse rowset describing the storage space consumed + /// for a range of timestamp intervals. + /// + public RowsetPart Results { get; set; } + } +} diff --git a/src/Seq.Api/Model/License/LicenseEntity.cs b/src/Seq.Api/Model/License/LicenseEntity.cs index ea7aa61..0bdb65e 100644 --- a/src/Seq.Api/Model/License/LicenseEntity.cs +++ b/src/Seq.Api/Model/License/LicenseEntity.cs @@ -36,6 +36,11 @@ public class LicenseEntity : Entity /// public bool IsSingleUser { get; set; } + /// + /// If the license is a subscription, the subscription id. + /// + public string SubscriptionId { get; set; } + /// /// Information about the status of the license. /// @@ -56,5 +61,11 @@ public class LicenseEntity : Entity /// the license has no user limit. /// public int? LicensedUsers { get; set; } + + /// + /// If the license is for a subscription, automatically check datalust.co and + /// update the license when the subscription is renewed or tier changed. + /// + public bool AutomaticallyRefresh { get; set; } } } diff --git a/src/Seq.Api/Model/Shared/DateTimeRange.cs b/src/Seq.Api/Model/Shared/DateTimeRange.cs new file mode 100644 index 0000000..8790bdc --- /dev/null +++ b/src/Seq.Api/Model/Shared/DateTimeRange.cs @@ -0,0 +1,31 @@ +using System; + +namespace Seq.Api.Model.Shared +{ + /// + /// A range represented by a start and end . + /// + public readonly struct DateTimeRange + { + /// + /// The (inclusive) start of the range. + /// + public DateTime Start { get; } + + /// + /// The (exclusive) end of the range. + /// + public DateTime End { get; } + + /// + /// Construct a . + /// + /// The (inclusive) start of the range. + /// The (exclusive) end of the range. + public DateTimeRange(DateTime start, DateTime end) + { + Start = start; + End = end; + } + } +} \ No newline at end of file diff --git a/src/Seq.Api/ResourceGroups/DiagnosticsResourceGroup.cs b/src/Seq.Api/ResourceGroups/DiagnosticsResourceGroup.cs index a386bc0..644242c 100644 --- a/src/Seq.Api/ResourceGroups/DiagnosticsResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/DiagnosticsResourceGroup.cs @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Seq.Api.Model.Diagnostics; +using Seq.Api.Model.Diagnostics.Storage; using Seq.Api.Model.Inputs; +// ReSharper disable UnusedMember.Global + namespace Seq.Api.ResourceGroups { /// @@ -65,11 +69,38 @@ public async Task GetIngestionLogAsync(CancellationToken cancellationTok /// /// A allowing the operation to be canceled. /// The measurement to get. - /// + /// A timeseries showing the measurement over time. public async Task GetMeasurementTimeseriesAsync(string measurement, CancellationToken cancellationToken = default) { var parameters = new Dictionary{ ["measurement"] = measurement }; return await GroupGetAsync("Metric", parameters, cancellationToken); } + + /// + /// Report on storage space consumed by the event store across a range of timestamps. The returned range may be + /// extended to account for the resolution of the underlying data. + /// + /// The (inclusive) start of the range to report on. If omitted, the results will report from the + /// earliest stored data. The range start must land on a five-minute boundary. + /// The (exclusive) end of the range to report on. If omitted, the results will report from the + /// earliest stored data. The range must be a multiple of the interval size, or a whole number of days if + /// no interval is specified. + /// The bucket size to use. Must be a multiple of 5 minutes. Defaults to 1440 (one day). + /// A allowing the operation to be canceled. + /// Storage consumption information. + public async Task GetStorageConsumptionAsync( + DateTime? rangeStart, + DateTime? rangeEnd, + int? intervalMinutes, + CancellationToken cancellationToken = default) + { + var parameters = new Dictionary + { + ["rangeStart"] = rangeStart, + ["rangeEnd"] = rangeEnd, + ["intervalMinutes"] = intervalMinutes + }; + return await GroupGetAsync("Storage", parameters, cancellationToken); + } } } diff --git a/src/Seq.Api/Seq.Api.csproj b/src/Seq.Api/Seq.Api.csproj index 0138235..b20e2a9 100644 --- a/src/Seq.Api/Seq.Api.csproj +++ b/src/Seq.Api/Seq.Api.csproj @@ -1,20 +1,21 @@ Client library for the Seq HTTP API. - 2020.1.0 + 2020.4.0 Datalust;Contributors netstandard2.0 true true seq Copyright © Datalust Pty Ltd and Contributors - https://datalust.co/images/seq-nuget.png + seq-api-icon.png https://github.com/datalust/seq-api Apache-2.0 8 - + + diff --git a/src/Seq.Api/seq-api-icon.png b/src/Seq.Api/seq-api-icon.png new file mode 100644 index 0000000..ed37092 Binary files /dev/null and b/src/Seq.Api/seq-api-icon.png differ