diff --git a/devolutions-gateway/openapi/doc/index.adoc b/devolutions-gateway/openapi/doc/index.adoc index 04e0934d0f..d63c02b8e2 100644 --- a/devolutions-gateway/openapi/doc/index.adoc +++ b/devolutions-gateway/openapi/doc/index.adoc @@ -1426,6 +1426,102 @@ ifdef::internal-generation[] endif::internal-generation[] +[.searchRecordings] +==== searchRecordings + +`POST /jet/jrec/search` + +Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + +===== Description + +The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + + +// markup not found, no include::{specDir}jet/jrec/search/POST/spec.adoc[opts=optional] + + + +===== Security + +[cols="2,1,1"] +|=== +| Name | Type | Scheme + +| `scope_token` +| http +| bearer +|=== + +===== Parameters + + +====== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| RecordingSearchRequest +| JSON-encoded search request <> +| X +| +| + +|=== + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Matching log lines +| <> + + +| 400 +| Bad request +| <<>> + + +| 401 +| Invalid or missing authorization token +| <<>> + + +| 403 +| Insufficient permissions +| <<>> + +|=== + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}jet/jrec/search/POST/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + [.Jrl] === Jrl @@ -3371,6 +3467,7 @@ endif::internal-generation[] | gateway.net.monitor.drain | gateway.agent.delete | gateway.agent.read +| gateway.recordings.search |=== @@ -5203,6 +5300,161 @@ Current auto-update schedule for Devolutions Agent. |=== +[#RecordingSearchHit] +=== _RecordingSearchHit_ + +A line that matches the search text. + + +[.fields-RecordingSearchHit] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| fileName +| X +| +| String +| Name of the `.slog` artifact holding the line, as listed in `recording.json`. +| + +| line +| X +| +| String +| The line's text without its line break; bytes that are not UTF-8 read as U+FFFD. +| + +| lineNumber +| X +| +| Long +| Number of the line in the file, counting from 1. +| int64 + +| offset +| X +| +| Long +| Byte offset of the line in the file. +| int64 + +| recordingId +| X +| +| UUID +| The recording whose artifact holds the line. +| uuid + +| truncated +| X +| +| Boolean +| The line is longer than 256 KiB, so `line` holds only its start, and the file holds the rest. +| + +|=== + + + +[#RecordingSearchRequest] +=== _RecordingSearchRequest_ + +What to search for, and in which recordings. + +Unknown fields are rejected, so that an option added later is never silently ignored by a Gateway that predates it. + + +[.fields-RecordingSearchRequest] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| cursor +| +| X +| String +| Where the page starts: the `nextCursor` of the previous page, sent unchanged with the same `query`, `recordings` and `startTime`. +| + +| maxHits +| +| X +| Integer +| Most hits in the page, from 1 to 1000; 1000 when missing. +| int32 + +| query +| X +| +| String +| Text to search for, ignoring case; leading and trailing spaces are part of it, and an empty text matches every line. +| + +| recordings +| X +| +| <> +| +| + +| startTime +| +| X +| <> +| +| + +|=== + + + +[#RecordingSearchResponse] +=== _RecordingSearchResponse_ + +The lines that match the search text. + + +[.fields-RecordingSearchResponse] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| hits +| X +| +| List of <> +| Matching lines, newest recording first when searching all recordings, otherwise in the requested order. +| + +| nextCursor +| +| X +| String +| Where the next page starts; absent when every log has been read. The cursor is opaque, and its format may change between versions. A page with fewer hits than asked for may still be followed by more pages. +| + +|=== + + + +[#RecordingSelection] +=== _RecordingSelection_ + +`"all"`, or a list of recording IDs. + +A repeated ID is searched once, and an empty list searches nothing. + + +[.fields-RecordingSelection] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +|=== + + + [#ScanOriginDto] === _ScanOriginDto_ @@ -5470,6 +5722,37 @@ Desired auto-update schedule to apply to Devolutions Agent. +[#StartTimeRange] +=== _StartTimeRange_ + +A range of recording start times, as written in `recording.json`, that includes `from` and excludes `to`. + +A missing end leaves that side of the range open. + + +[.fields-StartTimeRange] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| from +| +| X +| Date +| Earliest start time searched. +| date-time + +| to +| +| X +| Date +| Recordings that start at this time or later are not searched. +| date-time + +|=== + + + [#SubProvisionerKey] === _SubProvisionerKey_ diff --git a/devolutions-gateway/openapi/dotnet-client/.openapi-generator/FILES b/devolutions-gateway/openapi/dotnet-client/.openapi-generator/FILES index b49a091545..b2982cd9e2 100644 --- a/devolutions-gateway/openapi/dotnet-client/.openapi-generator/FILES +++ b/devolutions-gateway/openapi/dotnet-client/.openapi-generator/FILES @@ -59,6 +59,10 @@ docs/PreflightOperationKind.md docs/PreflightOutput.md docs/PreflightOutputKind.md docs/PubKeyFormat.md +docs/RecordingSearchHit.md +docs/RecordingSearchRequest.md +docs/RecordingSearchResponse.md +docs/RecordingSelection.md docs/ScanOriginDto.md docs/ScanResultSourceDto.md docs/SessionInfo.md @@ -67,6 +71,7 @@ docs/SessionTokenSignRequest.md docs/SessionsApi.md docs/SetConfigResponse.md docs/SetUpdateScheduleRequest.md +docs/StartTimeRange.md docs/SubProvisionerKey.md docs/Subscriber.md docs/TargetConnectionOptions.md @@ -158,6 +163,10 @@ src/Devolutions.Gateway.Client/Model/PreflightOperationKind.cs src/Devolutions.Gateway.Client/Model/PreflightOutput.cs src/Devolutions.Gateway.Client/Model/PreflightOutputKind.cs src/Devolutions.Gateway.Client/Model/PubKeyFormat.cs +src/Devolutions.Gateway.Client/Model/RecordingSearchHit.cs +src/Devolutions.Gateway.Client/Model/RecordingSearchRequest.cs +src/Devolutions.Gateway.Client/Model/RecordingSearchResponse.cs +src/Devolutions.Gateway.Client/Model/RecordingSelection.cs src/Devolutions.Gateway.Client/Model/ScanOriginDto.cs src/Devolutions.Gateway.Client/Model/ScanResultSourceDto.cs src/Devolutions.Gateway.Client/Model/SessionInfo.cs @@ -165,6 +174,7 @@ src/Devolutions.Gateway.Client/Model/SessionTokenContentType.cs src/Devolutions.Gateway.Client/Model/SessionTokenSignRequest.cs src/Devolutions.Gateway.Client/Model/SetConfigResponse.cs src/Devolutions.Gateway.Client/Model/SetUpdateScheduleRequest.cs +src/Devolutions.Gateway.Client/Model/StartTimeRange.cs src/Devolutions.Gateway.Client/Model/SubProvisionerKey.cs src/Devolutions.Gateway.Client/Model/Subscriber.cs src/Devolutions.Gateway.Client/Model/TargetConnectionOptions.cs diff --git a/devolutions-gateway/openapi/dotnet-client/README.md b/devolutions-gateway/openapi/dotnet-client/README.md index c60cd47e28..f08c67eb32 100644 --- a/devolutions-gateway/openapi/dotnet-client/README.md +++ b/devolutions-gateway/openapi/dotnet-client/README.md @@ -156,6 +156,7 @@ Class | Method | HTTP request | Description *JrecApi* | [**ListRecordings**](docs/JrecApi.md#listrecordings) | **GET** /jet/jrec/list | Lists all recordings stored on this instance *JrecApi* | [**PullRecordingFile**](docs/JrecApi.md#pullrecordingfile) | **GET** /jet/jrec/pull/{id}/{filename} | Retrieves a recording file for a given session *JrecApi* | [**PullRecordingSession**](docs/JrecApi.md#pullrecordingsession) | **GET** /jet/jrec/pull/{id} | Downloads an entire recorded session as a ZIP archive +*JrecApi* | [**SearchRecordings**](docs/JrecApi.md#searchrecordings) | **POST** /jet/jrec/search | Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance *JrlApi* | [**GetJrlInfo**](docs/JrlApi.md#getjrlinfo) | **GET** /jet/jrl/info | Retrieves current JRL (Json Revocation List) info *JrlApi* | [**UpdateJrl**](docs/JrlApi.md#updatejrl) | **POST** /jet/jrl | Updates JRL (Json Revocation List) using a JRL token *NetApi* | [**GetNetConfig**](docs/NetApi.md#getnetconfig) | **GET** /jet/net/config | Lists network interfaces @@ -227,6 +228,10 @@ Class | Method | HTTP request | Description - [Model.PreflightOutput](docs/PreflightOutput.md) - [Model.PreflightOutputKind](docs/PreflightOutputKind.md) - [Model.PubKeyFormat](docs/PubKeyFormat.md) + - [Model.RecordingSearchHit](docs/RecordingSearchHit.md) + - [Model.RecordingSearchRequest](docs/RecordingSearchRequest.md) + - [Model.RecordingSearchResponse](docs/RecordingSearchResponse.md) + - [Model.RecordingSelection](docs/RecordingSelection.md) - [Model.ScanOriginDto](docs/ScanOriginDto.md) - [Model.ScanResultSourceDto](docs/ScanResultSourceDto.md) - [Model.SessionInfo](docs/SessionInfo.md) @@ -234,6 +239,7 @@ Class | Method | HTTP request | Description - [Model.SessionTokenSignRequest](docs/SessionTokenSignRequest.md) - [Model.SetConfigResponse](docs/SetConfigResponse.md) - [Model.SetUpdateScheduleRequest](docs/SetUpdateScheduleRequest.md) + - [Model.StartTimeRange](docs/StartTimeRange.md) - [Model.SubProvisionerKey](docs/SubProvisionerKey.md) - [Model.Subscriber](docs/Subscriber.md) - [Model.TargetConnectionOptions](docs/TargetConnectionOptions.md) diff --git a/devolutions-gateway/openapi/dotnet-client/docs/JrecApi.md b/devolutions-gateway/openapi/dotnet-client/docs/JrecApi.md index 5777700acd..a376db1eb0 100644 --- a/devolutions-gateway/openapi/dotnet-client/docs/JrecApi.md +++ b/devolutions-gateway/openapi/dotnet-client/docs/JrecApi.md @@ -9,6 +9,7 @@ All URIs are relative to *http://localhost* | [**ListRecordings**](JrecApi.md#listrecordings) | **GET** /jet/jrec/list | Lists all recordings stored on this instance | | [**PullRecordingFile**](JrecApi.md#pullrecordingfile) | **GET** /jet/jrec/pull/{id}/{filename} | Retrieves a recording file for a given session | | [**PullRecordingSession**](JrecApi.md#pullrecordingsession) | **GET** /jet/jrec/pull/{id} | Downloads an entire recorded session as a ZIP archive | +| [**SearchRecordings**](JrecApi.md#searchrecordings) | **POST** /jet/jrec/search | Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance | # **DeleteManyRecordings** @@ -513,3 +514,104 @@ catch (ApiException e) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **SearchRecordings** +> RecordingSearchResponse SearchRecordings (RecordingSearchRequest recordingSearchRequest) + +Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + +The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + +### Example +```csharp +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using Devolutions.Gateway.Client.Api; +using Devolutions.Gateway.Client.Client; +using Devolutions.Gateway.Client.Model; + +namespace Example +{ + public class SearchRecordingsExample + { + public static void Main() + { + Configuration config = new Configuration(); + config.BasePath = "http://localhost"; + // Configure Bearer token for authorization: scope_token + config.AccessToken = "YOUR_BEARER_TOKEN"; + + // create instances of HttpClient, HttpClientHandler to be reused later with different Api classes + HttpClient httpClient = new HttpClient(); + HttpClientHandler httpClientHandler = new HttpClientHandler(); + var apiInstance = new JrecApi(httpClient, config, httpClientHandler); + var recordingSearchRequest = new RecordingSearchRequest(); // RecordingSearchRequest | JSON-encoded search request + + try + { + // Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + RecordingSearchResponse result = apiInstance.SearchRecordings(recordingSearchRequest); + Debug.WriteLine(result); + } + catch (ApiException e) + { + Debug.Print("Exception when calling JrecApi.SearchRecordings: " + e.Message); + Debug.Print("Status Code: " + e.ErrorCode); + Debug.Print(e.StackTrace); + } + } + } +} +``` + +#### Using the SearchRecordingsWithHttpInfo variant +This returns an ApiResponse object which contains the response data, status code and headers. + +```csharp +try +{ + // Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + ApiResponse response = apiInstance.SearchRecordingsWithHttpInfo(recordingSearchRequest); + Debug.Write("Status Code: " + response.StatusCode); + Debug.Write("Response Headers: " + response.Headers); + Debug.Write("Response Body: " + response.Data); +} +catch (ApiException e) +{ + Debug.Print("Exception when calling JrecApi.SearchRecordingsWithHttpInfo: " + e.Message); + Debug.Print("Status Code: " + e.ErrorCode); + Debug.Print(e.StackTrace); +} +``` + +### Parameters + +| Name | Type | Description | Notes | +|------|------|-------------|-------| +| **recordingSearchRequest** | [**RecordingSearchRequest**](RecordingSearchRequest.md) | JSON-encoded search request | | + +### Return type + +[**RecordingSearchResponse**](RecordingSearchResponse.md) + +### Authorization + +[scope_token](../README.md#scope_token) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | Matching log lines | - | +| **400** | Bad request | - | +| **401** | Invalid or missing authorization token | - | +| **403** | Insufficient permissions | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchHit.md b/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchHit.md new file mode 100644 index 0000000000..bfab56ff4a --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchHit.md @@ -0,0 +1,16 @@ +# Devolutions.Gateway.Client.Model.RecordingSearchHit +A line that matches the search text. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**FileName** | **string** | Name of the `.slog` artifact holding the line, as listed in `recording.json`. | +**Line** | **string** | The line's text without its line break; bytes that are not UTF-8 read as U+FFFD. | +**LineNumber** | **long** | Number of the line in the file, counting from 1. | +**Offset** | **long** | Byte offset of the line in the file. | +**RecordingId** | **Guid** | The recording whose artifact holds the line. | +**Truncated** | **bool** | The line is longer than 256 KiB, so `line` holds only its start, and the file holds the rest. | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchRequest.md b/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchRequest.md new file mode 100644 index 0000000000..81621f924b --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchRequest.md @@ -0,0 +1,15 @@ +# Devolutions.Gateway.Client.Model.RecordingSearchRequest +What to search for, and in which recordings. Unknown fields are rejected, so that an option added later is never silently ignored by a Gateway that predates it. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Cursor** | **string** | Where the page starts: the `nextCursor` of the previous page, sent unchanged with the same `query`, `recordings` and `startTime`. | [optional] +**MaxHits** | **int?** | Most hits in the page, from 1 to 1000; 1000 when missing. | [optional] +**Query** | **string** | Text to search for, ignoring case; leading and trailing spaces are part of it, and an empty text matches every line. | +**Recordings** | [**RecordingSelection**](RecordingSelection.md) | | +**StartTime** | [**StartTimeRange**](StartTimeRange.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchResponse.md b/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchResponse.md new file mode 100644 index 0000000000..ecdca759e5 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/RecordingSearchResponse.md @@ -0,0 +1,12 @@ +# Devolutions.Gateway.Client.Model.RecordingSearchResponse +The lines that match the search text. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Hits** | [**List<RecordingSearchHit>**](RecordingSearchHit.md) | Matching lines, newest recording first when searching all recordings, otherwise in the requested order. | +**NextCursor** | **string** | Where the next page starts; absent when every log has been read. The cursor is opaque, and its format may change between versions. A page with fewer hits than asked for may still be followed by more pages. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/RecordingSelection.md b/devolutions-gateway/openapi/dotnet-client/docs/RecordingSelection.md new file mode 100644 index 0000000000..167f6167ef --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/RecordingSelection.md @@ -0,0 +1,10 @@ +# Devolutions.Gateway.Client.Model.RecordingSelection +`\"all\"`, or a list of recording IDs. A repeated ID is searched once, and an empty list searches nothing. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/StartTimeRange.md b/devolutions-gateway/openapi/dotnet-client/docs/StartTimeRange.md new file mode 100644 index 0000000000..961921e2ef --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/StartTimeRange.md @@ -0,0 +1,12 @@ +# Devolutions.Gateway.Client.Model.StartTimeRange +A range of recording start times, as written in `recording.json`, that includes `from` and excludes `to`. A missing end leaves that side of the range open. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**From** | **DateTime?** | Earliest start time searched. | [optional] +**To** | **DateTime?** | Recordings that start at this time or later are not searched. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Api/JrecApi.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Api/JrecApi.cs index 17a031fcd6..59869f69ad 100644 --- a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Api/JrecApi.cs +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Api/JrecApi.cs @@ -126,6 +126,27 @@ public interface IJrecApiSync : IApiAccessor /// Recorded session ID /// ApiResponse of FileParameter ApiResponse PullRecordingSessionWithHttpInfo(Guid id); + /// + /// Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + /// + /// + /// The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + /// + /// Thrown when fails to make API call + /// JSON-encoded search request + /// RecordingSearchResponse + RecordingSearchResponse SearchRecordings(RecordingSearchRequest recordingSearchRequest); + + /// + /// Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + /// + /// + /// The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + /// + /// Thrown when fails to make API call + /// JSON-encoded search request + /// ApiResponse of RecordingSearchResponse + ApiResponse SearchRecordingsWithHttpInfo(RecordingSearchRequest recordingSearchRequest); #endregion Synchronous Operations } @@ -252,6 +273,29 @@ public interface IJrecApiAsync : IApiAccessor /// Cancellation Token to cancel the request. /// Task of ApiResponse (FileParameter) System.Threading.Tasks.Task> PullRecordingSessionWithHttpInfoAsync(Guid id, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); + /// + /// Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + /// + /// + /// The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + /// + /// Thrown when fails to make API call + /// JSON-encoded search request + /// Cancellation Token to cancel the request. + /// Task of RecordingSearchResponse + System.Threading.Tasks.Task SearchRecordingsAsync(RecordingSearchRequest recordingSearchRequest, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); + + /// + /// Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + /// + /// + /// The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + /// + /// Thrown when fails to make API call + /// JSON-encoded search request + /// Cancellation Token to cancel the request. + /// Task of ApiResponse (RecordingSearchResponse) + System.Threading.Tasks.Task> SearchRecordingsWithHttpInfoAsync(RecordingSearchRequest recordingSearchRequest, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); #endregion Asynchronous Operations } @@ -1080,5 +1124,134 @@ public Devolutions.Gateway.Client.Client.ApiResponse PullRecordin return localVarResponse; } + /// + /// Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + /// + /// Thrown when fails to make API call + /// JSON-encoded search request + /// RecordingSearchResponse + public RecordingSearchResponse SearchRecordings(RecordingSearchRequest recordingSearchRequest) + { + Devolutions.Gateway.Client.Client.ApiResponse localVarResponse = SearchRecordingsWithHttpInfo(recordingSearchRequest); + return localVarResponse.Data; + } + + /// + /// Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + /// + /// Thrown when fails to make API call + /// JSON-encoded search request + /// ApiResponse of RecordingSearchResponse + public Devolutions.Gateway.Client.Client.ApiResponse SearchRecordingsWithHttpInfo(RecordingSearchRequest recordingSearchRequest) + { + // verify the required parameter 'recordingSearchRequest' is set + if (recordingSearchRequest == null) + throw new Devolutions.Gateway.Client.Client.ApiException(400, "Missing required parameter 'recordingSearchRequest' when calling JrecApi->SearchRecordings"); + + Devolutions.Gateway.Client.Client.RequestOptions localVarRequestOptions = new Devolutions.Gateway.Client.Client.RequestOptions(); + + string[] _contentTypes = new string[] { + "application/json" + }; + + // to determine the Accept header + string[] _accepts = new string[] { + "application/json" + }; + + var localVarContentType = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderContentType(_contentTypes); + if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); + + var localVarAccept = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderAccept(_accepts); + if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept); + + localVarRequestOptions.Data = recordingSearchRequest; + + // authentication (scope_token) required + // bearer authentication required + if (!string.IsNullOrEmpty(this.Configuration.AccessToken) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization")) + { + localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken); + } + + // make the HTTP request + var localVarResponse = this.Client.Post("/jet/jrec/search", localVarRequestOptions, this.Configuration); + + if (this.ExceptionFactory != null) + { + Exception _exception = this.ExceptionFactory("SearchRecordings", localVarResponse); + if (_exception != null) throw _exception; + } + + return localVarResponse; + } + + /// + /// Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + /// + /// Thrown when fails to make API call + /// JSON-encoded search request + /// Cancellation Token to cancel the request. + /// Task of RecordingSearchResponse + public async System.Threading.Tasks.Task SearchRecordingsAsync(RecordingSearchRequest recordingSearchRequest, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + Devolutions.Gateway.Client.Client.ApiResponse localVarResponse = await SearchRecordingsWithHttpInfoAsync(recordingSearchRequest, cancellationToken).ConfigureAwait(false); + return localVarResponse.Data; + } + + /// + /// Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + /// + /// Thrown when fails to make API call + /// JSON-encoded search request + /// Cancellation Token to cancel the request. + /// Task of ApiResponse (RecordingSearchResponse) + public async System.Threading.Tasks.Task> SearchRecordingsWithHttpInfoAsync(RecordingSearchRequest recordingSearchRequest, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + // verify the required parameter 'recordingSearchRequest' is set + if (recordingSearchRequest == null) + throw new Devolutions.Gateway.Client.Client.ApiException(400, "Missing required parameter 'recordingSearchRequest' when calling JrecApi->SearchRecordings"); + + + Devolutions.Gateway.Client.Client.RequestOptions localVarRequestOptions = new Devolutions.Gateway.Client.Client.RequestOptions(); + + string[] _contentTypes = new string[] { + "application/json" + }; + + // to determine the Accept header + string[] _accepts = new string[] { + "application/json" + }; + + + var localVarContentType = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderContentType(_contentTypes); + if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); + + var localVarAccept = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderAccept(_accepts); + if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept); + + localVarRequestOptions.Data = recordingSearchRequest; + + // authentication (scope_token) required + // bearer authentication required + if (!string.IsNullOrEmpty(this.Configuration.AccessToken) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization")) + { + localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken); + } + + // make the HTTP request + + var localVarResponse = await this.AsynchronousClient.PostAsync("/jet/jrec/search", localVarRequestOptions, this.Configuration, cancellationToken).ConfigureAwait(false); + + if (this.ExceptionFactory != null) + { + Exception _exception = this.ExceptionFactory("SearchRecordings", localVarResponse); + if (_exception != null) throw _exception; + } + + return localVarResponse; + } + } } diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AccessScope.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AccessScope.cs index 98c7ac937d..03cf404273 100644 --- a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AccessScope.cs +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AccessScope.cs @@ -145,7 +145,13 @@ public enum AccessScope /// Enum GatewayAgentRead for value: gateway.agent.read /// [EnumMember(Value = "gateway.agent.read")] - GatewayAgentRead = 19 + GatewayAgentRead = 19, + + /// + /// Enum GatewayRecordingsSearch for value: gateway.recordings.search + /// + [EnumMember(Value = "gateway.recordings.search")] + GatewayRecordingsSearch = 20 } public static class AccessScopeExtensions @@ -195,6 +201,8 @@ public static string ToValue(this AccessScope variant) return "gateway.agent.delete"; case AccessScope.GatewayAgentRead: return "gateway.agent.read"; + case AccessScope.GatewayRecordingsSearch: + return "gateway.recordings.search"; default: throw new ArgumentOutOfRangeException(nameof(variant), $"Unexpected variant: {variant}"); } diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchHit.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchHit.cs new file mode 100644 index 0000000000..4191f310b0 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchHit.cs @@ -0,0 +1,162 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// A line that matches the search text. + /// + [DataContract(Name = "RecordingSearchHit")] + public partial class RecordingSearchHit : IValidatableObject + { + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected RecordingSearchHit() { } + /// + /// Initializes a new instance of the class. + /// + /// Name of the `.slog` artifact holding the line, as listed in `recording.json`. (required). + /// The line's text without its line break; bytes that are not UTF-8 read as U+FFFD. (required). + /// Number of the line in the file, counting from 1. (required). + /// Byte offset of the line in the file. (required). + /// The recording whose artifact holds the line. (required). + /// The line is longer than 256 KiB, so `line` holds only its start, and the file holds the rest. (required). + public RecordingSearchHit(string fileName = default(string), string line = default(string), long lineNumber = default(long), long offset = default(long), Guid recordingId = default(Guid), bool truncated = default(bool)) + { + // to ensure "fileName" is required (not null) + if (fileName == null) + { + throw new ArgumentNullException("fileName is a required property for RecordingSearchHit and cannot be null"); + } + this.FileName = fileName; + // to ensure "line" is required (not null) + if (line == null) + { + throw new ArgumentNullException("line is a required property for RecordingSearchHit and cannot be null"); + } + this.Line = line; + this.LineNumber = lineNumber; + this.Offset = offset; + this.RecordingId = recordingId; + this.Truncated = truncated; + } + + /// + /// Name of the `.slog` artifact holding the line, as listed in `recording.json`. + /// + /// Name of the `.slog` artifact holding the line, as listed in `recording.json`. + [DataMember(Name = "fileName", IsRequired = true, EmitDefaultValue = true)] + public string FileName { get; set; } + + /// + /// The line's text without its line break; bytes that are not UTF-8 read as U+FFFD. + /// + /// The line's text without its line break; bytes that are not UTF-8 read as U+FFFD. + [DataMember(Name = "line", IsRequired = true, EmitDefaultValue = true)] + public string Line { get; set; } + + /// + /// Number of the line in the file, counting from 1. + /// + /// Number of the line in the file, counting from 1. + [DataMember(Name = "lineNumber", IsRequired = true, EmitDefaultValue = true)] + public long LineNumber { get; set; } + + /// + /// Byte offset of the line in the file. + /// + /// Byte offset of the line in the file. + [DataMember(Name = "offset", IsRequired = true, EmitDefaultValue = true)] + public long Offset { get; set; } + + /// + /// The recording whose artifact holds the line. + /// + /// The recording whose artifact holds the line. + [DataMember(Name = "recordingId", IsRequired = true, EmitDefaultValue = true)] + public Guid RecordingId { get; set; } + + /// + /// The line is longer than 256 KiB, so `line` holds only its start, and the file holds the rest. + /// + /// The line is longer than 256 KiB, so `line` holds only its start, and the file holds the rest. + [DataMember(Name = "truncated", IsRequired = true, EmitDefaultValue = true)] + public bool Truncated { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class RecordingSearchHit {\n"); + sb.Append(" FileName: ").Append(FileName).Append("\n"); + sb.Append(" Line: ").Append(Line).Append("\n"); + sb.Append(" LineNumber: ").Append(LineNumber).Append("\n"); + sb.Append(" Offset: ").Append(Offset).Append("\n"); + sb.Append(" RecordingId: ").Append(RecordingId).Append("\n"); + sb.Append(" Truncated: ").Append(Truncated).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + // LineNumber (long) minimum + if (this.LineNumber < (long)1) + { + yield return new ValidationResult("Invalid value for LineNumber, must be a value greater than or equal to 1.", new [] { "LineNumber" }); + } + + // Offset (long) minimum + if (this.Offset < (long)0) + { + yield return new ValidationResult("Invalid value for Offset, must be a value greater than or equal to 0.", new [] { "Offset" }); + } + + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchRequest.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchRequest.cs new file mode 100644 index 0000000000..b9c98407af --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchRequest.cs @@ -0,0 +1,150 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// What to search for, and in which recordings. Unknown fields are rejected, so that an option added later is never silently ignored by a Gateway that predates it. + /// + [DataContract(Name = "RecordingSearchRequest")] + public partial class RecordingSearchRequest : IValidatableObject + { + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected RecordingSearchRequest() { } + /// + /// Initializes a new instance of the class. + /// + /// Where the page starts: the `nextCursor` of the previous page, sent unchanged with the same `query`, `recordings` and `startTime`.. + /// Most hits in the page, from 1 to 1000; 1000 when missing.. + /// Text to search for, ignoring case; leading and trailing spaces are part of it, and an empty text matches every line. (required). + /// recordings (required). + /// startTime. + public RecordingSearchRequest(string cursor = default(string), int? maxHits = default(int?), string query = default(string), RecordingSelection recordings = default(RecordingSelection), StartTimeRange startTime = default(StartTimeRange)) + { + // to ensure "query" is required (not null) + if (query == null) + { + throw new ArgumentNullException("query is a required property for RecordingSearchRequest and cannot be null"); + } + this.Query = query; + // to ensure "recordings" is required (not null) + if (recordings == null) + { + throw new ArgumentNullException("recordings is a required property for RecordingSearchRequest and cannot be null"); + } + this.Recordings = recordings; + this.Cursor = cursor; + this.MaxHits = maxHits; + this.StartTime = startTime; + } + + /// + /// Where the page starts: the `nextCursor` of the previous page, sent unchanged with the same `query`, `recordings` and `startTime`. + /// + /// Where the page starts: the `nextCursor` of the previous page, sent unchanged with the same `query`, `recordings` and `startTime`. + [DataMember(Name = "cursor", EmitDefaultValue = true)] + public string Cursor { get; set; } + + /// + /// Most hits in the page, from 1 to 1000; 1000 when missing. + /// + /// Most hits in the page, from 1 to 1000; 1000 when missing. + [DataMember(Name = "maxHits", EmitDefaultValue = true)] + public int? MaxHits { get; set; } + + /// + /// Text to search for, ignoring case; leading and trailing spaces are part of it, and an empty text matches every line. + /// + /// Text to search for, ignoring case; leading and trailing spaces are part of it, and an empty text matches every line. + [DataMember(Name = "query", IsRequired = true, EmitDefaultValue = true)] + public string Query { get; set; } + + /// + /// Gets or Sets Recordings + /// + [DataMember(Name = "recordings", IsRequired = true, EmitDefaultValue = true)] + public RecordingSelection Recordings { get; set; } + + /// + /// Gets or Sets StartTime + /// + [DataMember(Name = "startTime", EmitDefaultValue = true)] + public StartTimeRange StartTime { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class RecordingSearchRequest {\n"); + sb.Append(" Cursor: ").Append(Cursor).Append("\n"); + sb.Append(" MaxHits: ").Append(MaxHits).Append("\n"); + sb.Append(" Query: ").Append(Query).Append("\n"); + sb.Append(" Recordings: ").Append(Recordings).Append("\n"); + sb.Append(" StartTime: ").Append(StartTime).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + // MaxHits (int?) maximum + if (this.MaxHits > (int?)1000) + { + yield return new ValidationResult("Invalid value for MaxHits, must be a value less than or equal to 1000.", new [] { "MaxHits" }); + } + + // MaxHits (int?) minimum + if (this.MaxHits < (int?)1) + { + yield return new ValidationResult("Invalid value for MaxHits, must be a value greater than or equal to 1.", new [] { "MaxHits" }); + } + + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchResponse.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchResponse.cs new file mode 100644 index 0000000000..9119308fb5 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSearchResponse.cs @@ -0,0 +1,105 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// The lines that match the search text. + /// + [DataContract(Name = "RecordingSearchResponse")] + public partial class RecordingSearchResponse : IValidatableObject + { + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected RecordingSearchResponse() { } + /// + /// Initializes a new instance of the class. + /// + /// Matching lines, newest recording first when searching all recordings, otherwise in the requested order. (required). + /// Where the next page starts; absent when every log has been read. The cursor is opaque, and its format may change between versions. A page with fewer hits than asked for may still be followed by more pages.. + public RecordingSearchResponse(List hits = default(List), string nextCursor = default(string)) + { + // to ensure "hits" is required (not null) + if (hits == null) + { + throw new ArgumentNullException("hits is a required property for RecordingSearchResponse and cannot be null"); + } + this.Hits = hits; + this.NextCursor = nextCursor; + } + + /// + /// Matching lines, newest recording first when searching all recordings, otherwise in the requested order. + /// + /// Matching lines, newest recording first when searching all recordings, otherwise in the requested order. + [DataMember(Name = "hits", IsRequired = true, EmitDefaultValue = true)] + public List Hits { get; set; } + + /// + /// Where the next page starts; absent when every log has been read. The cursor is opaque, and its format may change between versions. A page with fewer hits than asked for may still be followed by more pages. + /// + /// Where the next page starts; absent when every log has been read. The cursor is opaque, and its format may change between versions. A page with fewer hits than asked for may still be followed by more pages. + [DataMember(Name = "nextCursor", EmitDefaultValue = true)] + public string NextCursor { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class RecordingSearchResponse {\n"); + sb.Append(" Hits: ").Append(Hits).Append("\n"); + sb.Append(" NextCursor: ").Append(NextCursor).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSelection.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSelection.cs new file mode 100644 index 0000000000..c5953fc73e --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/RecordingSelection.cs @@ -0,0 +1,264 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; +using System.Reflection; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// `\"all\"`, or a list of recording IDs. A repeated ID is searched once, and an empty list searches nothing. + /// + [JsonConverter(typeof(RecordingSelectionJsonConverter))] + [DataContract(Name = "RecordingSelection")] + public partial class RecordingSelection : AbstractOpenAPISchema, IValidatableObject + { + /// + /// Initializes a new instance of the class + /// with the class + /// + /// An instance of string. + public RecordingSelection(string actualInstance) + { + this.IsNullable = false; + this.SchemaType= "oneOf"; + this.ActualInstance = actualInstance ?? throw new ArgumentException("Invalid instance found. Must not be null."); + } + + /// + /// Initializes a new instance of the class + /// with the class + /// + /// An instance of List<Guid>. + public RecordingSelection(List actualInstance) + { + this.IsNullable = false; + this.SchemaType= "oneOf"; + this.ActualInstance = actualInstance ?? throw new ArgumentException("Invalid instance found. Must not be null."); + } + + + private Object _actualInstance; + + /// + /// Gets or Sets ActualInstance + /// + public override Object ActualInstance + { + get + { + return _actualInstance; + } + set + { + if (value.GetType() == typeof(List) || value is List) + { + this._actualInstance = value; + } + else if (value.GetType() == typeof(string) || value is string) + { + this._actualInstance = value; + } + else + { + throw new ArgumentException("Invalid instance found. Must be the following types: List, string"); + } + } + } + + /// + /// Get the actual instance of `string`. If the actual instance is not `string`, + /// the InvalidClassException will be thrown + /// + /// An instance of string + public string GetString() + { + return (string)this.ActualInstance; + } + + /// + /// Get the actual instance of `List<Guid>`. If the actual instance is not `List<Guid>`, + /// the InvalidClassException will be thrown + /// + /// An instance of List<Guid> + public List GetListUuid() + { + return (List)this.ActualInstance; + } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class RecordingSelection {\n"); + sb.Append(" ActualInstance: ").Append(this.ActualInstance).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public override string ToJson() + { + return JsonConvert.SerializeObject(this.ActualInstance, RecordingSelection.SerializerSettings); + } + + /// + /// Converts the JSON string into an instance of RecordingSelection + /// + /// JSON string + /// An instance of RecordingSelection + public static RecordingSelection FromJson(string jsonString) + { + RecordingSelection newRecordingSelection = null; + + if (string.IsNullOrEmpty(jsonString)) + { + return newRecordingSelection; + } + int match = 0; + List matchedTypes = new List(); + + try + { + // if it does not contains "AdditionalProperties", use SerializerSettings to deserialize + if (typeof(List).GetProperty("AdditionalProperties") == null) + { + newRecordingSelection = new RecordingSelection(JsonConvert.DeserializeObject>(jsonString, RecordingSelection.SerializerSettings)); + } + else + { + newRecordingSelection = new RecordingSelection(JsonConvert.DeserializeObject>(jsonString, RecordingSelection.AdditionalPropertiesSerializerSettings)); + } + matchedTypes.Add("List"); + match++; + } + catch (Exception exception) + { + // deserialization failed, try the next one + System.Diagnostics.Debug.WriteLine(string.Format("Failed to deserialize `{0}` into List: {1}", jsonString, exception.ToString())); + } + + try + { + // if it does not contains "AdditionalProperties", use SerializerSettings to deserialize + if (typeof(string).GetProperty("AdditionalProperties") == null) + { + newRecordingSelection = new RecordingSelection(JsonConvert.DeserializeObject(jsonString, RecordingSelection.SerializerSettings)); + } + else + { + newRecordingSelection = new RecordingSelection(JsonConvert.DeserializeObject(jsonString, RecordingSelection.AdditionalPropertiesSerializerSettings)); + } + matchedTypes.Add("string"); + match++; + } + catch (Exception exception) + { + // deserialization failed, try the next one + System.Diagnostics.Debug.WriteLine(string.Format("Failed to deserialize `{0}` into string: {1}", jsonString, exception.ToString())); + } + + if (match == 0) + { + throw new InvalidDataException("The JSON string `" + jsonString + "` cannot be deserialized into any schema defined."); + } + else if (match > 1) + { + throw new InvalidDataException("The JSON string `" + jsonString + "` incorrectly matches more than one schema (should be exactly one match): " + String.Join(",", matchedTypes)); + } + + // deserialization is considered successful at this point if no exception has been thrown. + return newRecordingSelection; + } + + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + yield break; + } + } + + /// + /// Custom JSON converter for RecordingSelection + /// + public class RecordingSelectionJsonConverter : JsonConverter + { + /// + /// To write the JSON string + /// + /// JSON writer + /// Object to be converted into a JSON string + /// JSON Serializer + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteRawValue((string)(typeof(RecordingSelection).GetMethod("ToJson").Invoke(value, null))); + } + + /// + /// To convert a JSON string into an object + /// + /// JSON reader + /// Object type + /// Existing value + /// JSON Serializer + /// The object converted from the JSON string + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + switch(reader.TokenType) + { + case JsonToken.String: + return new RecordingSelection(Convert.ToString(reader.Value)); + case JsonToken.StartObject: + return RecordingSelection.FromJson(JObject.Load(reader).ToString(Formatting.None)); + case JsonToken.StartArray: + return RecordingSelection.FromJson(JArray.Load(reader).ToString(Formatting.None)); + default: + return null; + } + } + + /// + /// Check if the object can be converted + /// + /// Object type + /// True if the object can be converted + public override bool CanConvert(Type objectType) + { + return false; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/StartTimeRange.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/StartTimeRange.cs new file mode 100644 index 0000000000..1bc5d4e9b6 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/StartTimeRange.cs @@ -0,0 +1,95 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// A range of recording start times, as written in `recording.json`, that includes `from` and excludes `to`. A missing end leaves that side of the range open. + /// + [DataContract(Name = "StartTimeRange")] + public partial class StartTimeRange : IValidatableObject + { + /// + /// Initializes a new instance of the class. + /// + /// Earliest start time searched.. + /// Recordings that start at this time or later are not searched.. + public StartTimeRange(DateTime? from = default(DateTime?), DateTime? to = default(DateTime?)) + { + this.From = from; + this.To = to; + } + + /// + /// Earliest start time searched. + /// + /// Earliest start time searched. + [DataMember(Name = "from", EmitDefaultValue = true)] + public DateTime? From { get; set; } + + /// + /// Recordings that start at this time or later are not searched. + /// + /// Recordings that start at this time or later are not searched. + [DataMember(Name = "to", EmitDefaultValue = true)] + public DateTime? To { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class StartTimeRange {\n"); + sb.Append(" From: ").Append(From).Append("\n"); + sb.Append(" To: ").Append(To).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/gateway-api.yaml b/devolutions-gateway/openapi/gateway-api.yaml index a2c1b1b2b5..9ec75de7f3 100644 --- a/devolutions-gateway/openapi/gateway-api.yaml +++ b/devolutions-gateway/openapi/gateway-api.yaml @@ -346,6 +346,50 @@ paths: security: - jrec_token: - pull + /jet/jrec/search: + post: + tags: + - Jrec + summary: Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + description: |- + The query is a case-insensitive substring, and an empty query matches every line. + In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read + once unescaped, so field names, numbers, booleans and null never match. + Any other line, such as one still being written, is matched as raw text. + A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually + matched as raw text. + Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. + Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. + + Results come in pages. + To get the next page, send the same `query`, `recordings` and `startTime` again, + with the `nextCursor` of the previous page as `cursor`. + Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not + searched. + operationId: SearchRecordings + requestBody: + description: JSON-encoded search request + content: + application/json: + schema: + $ref: '#/components/schemas/RecordingSearchRequest' + required: true + responses: + '200': + description: Matching log lines + content: + application/json: + schema: + $ref: '#/components/schemas/RecordingSearchResponse' + '400': + description: Bad request + '401': + description: Invalid or missing authorization token + '403': + description: Insufficient permissions + security: + - scope_token: + - gateway.recordings.search /jet/jrl: post: tags: @@ -1326,6 +1370,7 @@ components: - gateway.net.monitor.drain - gateway.agent.delete - gateway.agent.read + - gateway.recordings.search AckRequest: type: object required: @@ -2196,6 +2241,107 @@ components: enum: - Spki - Pkcs1 + RecordingSearchHit: + type: object + description: A line that matches the search text. + required: + - recordingId + - fileName + - lineNumber + - offset + - line + - truncated + properties: + fileName: + type: string + description: Name of the `.slog` artifact holding the line, as listed in `recording.json`. + line: + type: string + description: The line's text without its line break; bytes that are not UTF-8 read as U+FFFD. + lineNumber: + type: integer + format: int64 + description: Number of the line in the file, counting from 1. + minimum: 1 + offset: + type: integer + format: int64 + description: Byte offset of the line in the file. + minimum: 0 + recordingId: + type: string + format: uuid + description: The recording whose artifact holds the line. + truncated: + type: boolean + description: The line is longer than 256 KiB, so `line` holds only its start, and the file holds the rest. + RecordingSearchRequest: + type: object + description: |- + What to search for, and in which recordings. + + Unknown fields are rejected, so that an option added later is never silently ignored by a Gateway that predates it. + required: + - query + - recordings + properties: + cursor: + type: string + description: |- + Where the page starts: the `nextCursor` of the previous page, sent unchanged with the same `query`, + `recordings` and `startTime`. + nullable: true + maxHits: + type: integer + format: int32 + description: Most hits in the page, from 1 to 1000; 1000 when missing. + nullable: true + maximum: 1000 + minimum: 1 + query: + type: string + description: |- + Text to search for, ignoring case; leading and trailing spaces are part of it, and an empty text matches every + line. + recordings: + $ref: '#/components/schemas/RecordingSelection' + startTime: + allOf: + - $ref: '#/components/schemas/StartTimeRange' + nullable: true + additionalProperties: false + RecordingSearchResponse: + type: object + description: The lines that match the search text. + required: + - hits + properties: + hits: + type: array + items: + $ref: '#/components/schemas/RecordingSearchHit' + description: Matching lines, newest recording first when searching all recordings, otherwise in the requested order. + nextCursor: + type: string + description: |- + Where the next page starts; absent when every log has been read. + + The cursor is opaque, and its format may change between versions. + A page with fewer hits than asked for may still be followed by more pages. + nullable: true + RecordingSelection: + oneOf: + - type: string + enum: + - all + - type: array + items: + type: string + format: uuid + description: |- + `"all"`, or a list of recording IDs. + + A repeated ID is searched once, and an empty list searches nothing. ScanOriginDto: type: string enum: @@ -2349,6 +2495,24 @@ components: minimum: 0 SetUpdateScheduleResponse: type: object + StartTimeRange: + type: object + description: |- + A range of recording start times, as written in `recording.json`, that includes `from` and excludes `to`. + + A missing end leaves that side of the range open. + properties: + from: + type: string + format: date-time + description: Earliest start time searched. + nullable: true + to: + type: string + format: date-time + description: Recordings that start at this time or later are not searched. + nullable: true + additionalProperties: false SubProvisionerKey: type: object required: diff --git a/devolutions-gateway/openapi/ts-angular-client/.openapi-generator/FILES b/devolutions-gateway/openapi/ts-angular-client/.openapi-generator/FILES index 84eba61edd..a7c07e7165 100644 --- a/devolutions-gateway/openapi/ts-angular-client/.openapi-generator/FILES +++ b/devolutions-gateway/openapi/ts-angular-client/.openapi-generator/FILES @@ -68,6 +68,10 @@ model/preflightOperationKind.ts model/preflightOutput.ts model/preflightOutputKind.ts model/pubKeyFormat.ts +model/recordingSearchHit.ts +model/recordingSearchRequest.ts +model/recordingSearchResponse.ts +model/recordingSelection.ts model/scanOriginDto.ts model/scanResultSourceDto.ts model/sessionInfo.ts @@ -75,6 +79,7 @@ model/sessionTokenContentType.ts model/sessionTokenSignRequest.ts model/setConfigResponse.ts model/setUpdateScheduleRequest.ts +model/startTimeRange.ts model/subProvisionerKey.ts model/subscriber.ts model/targetConnectionOptions.ts diff --git a/devolutions-gateway/openapi/ts-angular-client/api/jrec.service.ts b/devolutions-gateway/openapi/ts-angular-client/api/jrec.service.ts index b1e8e0a18a..130ff953ac 100644 --- a/devolutions-gateway/openapi/ts-angular-client/api/jrec.service.ts +++ b/devolutions-gateway/openapi/ts-angular-client/api/jrec.service.ts @@ -18,6 +18,10 @@ import { Observable } from 'rxjs'; // @ts-ignore import { DeleteManyResult } from '../model/deleteManyResult'; +// @ts-ignore +import { RecordingSearchRequest } from '../model/recordingSearchRequest'; +// @ts-ignore +import { RecordingSearchResponse } from '../model/recordingSearchResponse'; // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; @@ -445,4 +449,86 @@ export class JrecService { ); } + /** + * Searches the `.slog` artifacts, such as AI analysis logs, of recordings stored on this instance + * The query is a case-insensitive substring, and an empty query matches every line. In a line holding a JSON object, it is matched against the string values of the object at any depth, as they read once unescaped, so field names, numbers, booleans and null never match. Any other line, such as one still being written, is matched as raw text. A line longer than 256 KiB is matched on its first 256 KiB only, by the same rule, so a cut JSON object is usually matched as raw text. Only the files listed under `artifacts` in `recording.json` with the `slog` type are searched. Recordings without a readable `recording.json` and IDs with no recording on this instance give no hits. Results come in pages. To get the next page, send the same `query`, `recordings` and `startTime` again, with the `nextCursor` of the previous page as `cursor`. Pages never go back, so a recording that starts, or a log that grows, behind the cursor while paging is not searched. + * @param recordingSearchRequest JSON-encoded search request + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public searchRecordings(recordingSearchRequest: RecordingSearchRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public searchRecordings(recordingSearchRequest: RecordingSearchRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public searchRecordings(recordingSearchRequest: RecordingSearchRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public searchRecordings(recordingSearchRequest: RecordingSearchRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (recordingSearchRequest === null || recordingSearchRequest === undefined) { + throw new Error('Required parameter recordingSearchRequest was null or undefined when calling searchRecordings.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (scope_token) required + localVarCredential = this.configuration.lookupCredential('scope_token'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/jet/jrec/search`; + return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: recordingSearchRequest, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + } diff --git a/devolutions-gateway/openapi/ts-angular-client/model/accessScope.ts b/devolutions-gateway/openapi/ts-angular-client/model/accessScope.ts index 0827c35b77..07dc408fcb 100644 --- a/devolutions-gateway/openapi/ts-angular-client/model/accessScope.ts +++ b/devolutions-gateway/openapi/ts-angular-client/model/accessScope.ts @@ -9,7 +9,7 @@ */ -export type AccessScope = '*' | 'gateway.sessions.read' | 'gateway.session.terminate' | 'gateway.associations.read' | 'gateway.diagnostics.read' | 'gateway.jrl.read' | 'gateway.config.write' | 'gateway.heartbeat.read' | 'gateway.recording.delete' | 'gateway.recordings.read' | 'gateway.update' | 'gateway.update.read' | 'gateway.preflight' | 'gateway.traffic.claim' | 'gateway.traffic.ack' | 'gateway.net.monitor.config' | 'gateway.net.monitor.drain' | 'gateway.agent.delete' | 'gateway.agent.read'; +export type AccessScope = '*' | 'gateway.sessions.read' | 'gateway.session.terminate' | 'gateway.associations.read' | 'gateway.diagnostics.read' | 'gateway.jrl.read' | 'gateway.config.write' | 'gateway.heartbeat.read' | 'gateway.recording.delete' | 'gateway.recordings.read' | 'gateway.update' | 'gateway.update.read' | 'gateway.preflight' | 'gateway.traffic.claim' | 'gateway.traffic.ack' | 'gateway.net.monitor.config' | 'gateway.net.monitor.drain' | 'gateway.agent.delete' | 'gateway.agent.read' | 'gateway.recordings.search'; export const AccessScope = { Star: '*' as AccessScope, @@ -30,6 +30,7 @@ export const AccessScope = { GatewayNetMonitorConfig: 'gateway.net.monitor.config' as AccessScope, GatewayNetMonitorDrain: 'gateway.net.monitor.drain' as AccessScope, GatewayAgentDelete: 'gateway.agent.delete' as AccessScope, - GatewayAgentRead: 'gateway.agent.read' as AccessScope + GatewayAgentRead: 'gateway.agent.read' as AccessScope, + GatewayRecordingsSearch: 'gateway.recordings.search' as AccessScope }; diff --git a/devolutions-gateway/openapi/ts-angular-client/model/models.ts b/devolutions-gateway/openapi/ts-angular-client/model/models.ts index 7d7eb4f0fe..0c26c4654b 100644 --- a/devolutions-gateway/openapi/ts-angular-client/model/models.ts +++ b/devolutions-gateway/openapi/ts-angular-client/model/models.ts @@ -46,6 +46,10 @@ export * from './preflightOperationKind'; export * from './preflightOutput'; export * from './preflightOutputKind'; export * from './pubKeyFormat'; +export * from './recordingSearchHit'; +export * from './recordingSearchRequest'; +export * from './recordingSearchResponse'; +export * from './recordingSelection'; export * from './scanOriginDto'; export * from './scanResultSourceDto'; export * from './sessionInfo'; @@ -53,6 +57,7 @@ export * from './sessionTokenContentType'; export * from './sessionTokenSignRequest'; export * from './setConfigResponse'; export * from './setUpdateScheduleRequest'; +export * from './startTimeRange'; export * from './subProvisionerKey'; export * from './subscriber'; export * from './targetConnectionOptions'; diff --git a/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchHit.ts b/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchHit.ts new file mode 100644 index 0000000000..5c66fe0b67 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchHit.ts @@ -0,0 +1,41 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * A line that matches the search text. + */ +export interface RecordingSearchHit { + /** + * Name of the `.slog` artifact holding the line, as listed in `recording.json`. + */ + fileName: string; + /** + * The line\'s text without its line break; bytes that are not UTF-8 read as U+FFFD. + */ + line: string; + /** + * Number of the line in the file, counting from 1. + */ + lineNumber: number; + /** + * Byte offset of the line in the file. + */ + offset: number; + /** + * The recording whose artifact holds the line. + */ + recordingId: string; + /** + * The line is longer than 256 KiB, so `line` holds only its start, and the file holds the rest. + */ + truncated: boolean; +} + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchRequest.ts b/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchRequest.ts new file mode 100644 index 0000000000..9e7873c419 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchRequest.ts @@ -0,0 +1,33 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { StartTimeRange } from './startTimeRange'; +import { RecordingSelection } from './recordingSelection'; + + +/** + * What to search for, and in which recordings. Unknown fields are rejected, so that an option added later is never silently ignored by a Gateway that predates it. + */ +export interface RecordingSearchRequest { + /** + * Where the page starts: the `nextCursor` of the previous page, sent unchanged with the same `query`, `recordings` and `startTime`. + */ + cursor?: string | null; + /** + * Most hits in the page, from 1 to 1000; 1000 when missing. + */ + maxHits?: number | null; + /** + * Text to search for, ignoring case; leading and trailing spaces are part of it, and an empty text matches every line. + */ + query: string; + recordings: RecordingSelection; + startTime?: StartTimeRange | null; +} + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchResponse.ts b/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchResponse.ts new file mode 100644 index 0000000000..85f6946f8f --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/recordingSearchResponse.ts @@ -0,0 +1,26 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { RecordingSearchHit } from './recordingSearchHit'; + + +/** + * The lines that match the search text. + */ +export interface RecordingSearchResponse { + /** + * Matching lines, newest recording first when searching all recordings, otherwise in the requested order. + */ + hits: Array; + /** + * Where the next page starts; absent when every log has been read. The cursor is opaque, and its format may change between versions. A page with fewer hits than asked for may still be followed by more pages. + */ + nextCursor?: string | null; +} + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/recordingSelection.ts b/devolutions-gateway/openapi/ts-angular-client/model/recordingSelection.ts new file mode 100644 index 0000000000..985ada4cc4 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/recordingSelection.ts @@ -0,0 +1,21 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * `\"all\"`, or a list of recording IDs. A repeated ID is searched once, and an empty list searches nothing. + */ +/** + * @type RecordingSelection + * `\"all\"`, or a list of recording IDs. A repeated ID is searched once, and an empty list searches nothing. + * @export + */ +export type RecordingSelection = Array | string; + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/startTimeRange.ts b/devolutions-gateway/openapi/ts-angular-client/model/startTimeRange.ts new file mode 100644 index 0000000000..c07bd4deda --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/startTimeRange.ts @@ -0,0 +1,25 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * A range of recording start times, as written in `recording.json`, that includes `from` and excludes `to`. A missing end leaves that side of the range open. + */ +export interface StartTimeRange { + /** + * Earliest start time searched. + */ + from?: string | null; + /** + * Recordings that start at this time or later are not searched. + */ + to?: string | null; +} +