Add Sentry error reporting
This commit is contained in:
@@ -9,12 +9,13 @@ Dedykowana usługa Windows zastępująca proces Node na stanowiskach z enova365.
|
|||||||
- Operacje Enovy są wykonywane pojedynczo i w osobnych sesjach. Zapytania SQL korzystają z puli połączeń i mogą działać równolegle.
|
- Operacje Enovy są wykonywane pojedynczo i w osobnych sesjach. Zapytania SQL korzystają z puli połączeń i mogą działać równolegle.
|
||||||
- ZO jest zapisywane transakcyjnie, domyślnie do bufora. Powtarzający się numer obcy nie jest blokowany.
|
- ZO jest zapisywane transakcyjnie, domyślnie do bufora. Powtarzający się numer obcy nie jest blokowany.
|
||||||
- Pierwsza wersja obsługuje PLN, `price_netto` i rabat procentowy `0-100`.
|
- Pierwsza wersja obsługuje PLN, `price_netto` i rabat procentowy `0-100`.
|
||||||
|
- Sentry raportuje błędy krytyczne procesu oraz nieoczekiwane błędy procedur Enovy i SQL, oznaczone portalem i nazwą procedury WAMP.
|
||||||
|
|
||||||
## Konfiguracja
|
## Konfiguracja
|
||||||
|
|
||||||
Plik `config/enova.json` zawiera portal WAMP, ścieżkę instalacji, alias bazy, operatora wraz z hasłem, connection string SQL, definicję dokumentu, magazyn domyślny i tryb zapisu. Repozytorium oraz paczka zawierają tylko `config/enova.json.sample`. Skrypt instalacyjny kopiuje wzorzec do `config/enova.json` wyłącznie wtedy, gdy lokalna konfiguracja jeszcze nie istnieje.
|
Plik `config/enova.json` zawiera portal WAMP, ścieżkę instalacji, alias bazy, operatora wraz z hasłem, connection string SQL, definicję dokumentu, magazyn domyślny, tryb zapisu oraz DSN Sentry. Repozytorium oraz paczka zawierają tylko `config/enova.json.sample`. Skrypt instalacyjny kopiuje wzorzec do `config/enova.json` wyłącznie wtedy, gdy lokalna konfiguracja jeszcze nie istnieje.
|
||||||
|
|
||||||
Uzupełnij pola `enova.operator`, `enova.password` i `sql.connectionString` bezpośrednio w lokalnym pliku `config/enova.json`. Jeżeli operator lub hasło Enovy nie są podane, usługa nie loguje się do bazy Enovy i nie rejestruje endpointu `eu.smartb2b.place_order`; endpoint SQL oraz diagnostyka pozostają dostępne. Po zmianie konfiguracji trzeba zrestartować usługę. Connection string powinien używać konta SQL o możliwie najmniejszych uprawnieniach; `sql_raw` celowo dopuszcza również polecenia modyfikujące dane dla zgodności z obecną usługą.
|
Uzupełnij pola `enova.operator`, `enova.password` i `sql.connectionString` bezpośrednio w lokalnym pliku `config/enova.json`. Parametr `sentry.dsn` wskazuje projekt Sentry; pusty lub pominięty DSN wyłącza wysyłanie błędów. Jeżeli operator lub hasło Enovy nie są podane, usługa nie loguje się do bazy Enovy i nie rejestruje endpointu `eu.smartb2b.place_order`; endpoint SQL oraz diagnostyka pozostają dostępne. Po zmianie konfiguracji trzeba zrestartować usługę. Connection string powinien używać konta SQL o możliwie najmniejszych uprawnieniach; `sql_raw` celowo dopuszcza również polecenia modyfikujące dane dla zgodności z obecną usługą.
|
||||||
|
|
||||||
## Budowanie i testy
|
## Budowanie i testy
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Newtonsoft.Json.dll" to="%BASE%\..\Newtonsoft.Json.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Newtonsoft.Json.dll" to="%BASE%\..\Newtonsoft.Json.dll" failOnError="false" />
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/NewtonsoftMessagePack.dll" to="%BASE%\..\NewtonsoftMessagePack.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/NewtonsoftMessagePack.dll" to="%BASE%\..\NewtonsoftMessagePack.dll" failOnError="false" />
|
||||||
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Sentry.dll" to="%BASE%\..\Sentry.dll" failOnError="false" />
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Reactive.dll" to="%BASE%\..\System.Reactive.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Reactive.dll" to="%BASE%\..\System.Reactive.dll" failOnError="false" />
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Runtime.InteropServices.WindowsRuntime.dll" to="%BASE%\..\System.Runtime.InteropServices.WindowsRuntime.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Runtime.InteropServices.WindowsRuntime.dll" to="%BASE%\..\System.Runtime.InteropServices.WindowsRuntime.dll" failOnError="false" />
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/WampSharp.dll" to="%BASE%\..\WampSharp.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/WampSharp.dll" to="%BASE%\..\WampSharp.dll" failOnError="false" />
|
||||||
|
|||||||
BIN
release/Sentry.dll
Normal file
BIN
release/Sentry.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,6 +9,7 @@
|
|||||||
"SmartB2B.Enova.Service/1.0.0": {
|
"SmartB2B.Enova.Service/1.0.0": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
|
"Sentry": "6.9.0",
|
||||||
"SmartB2B.Enova.Contracts": "1.0.0",
|
"SmartB2B.Enova.Contracts": "1.0.0",
|
||||||
"WampSharp.Default.Client": "23.8.1"
|
"WampSharp.Default.Client": "23.8.1"
|
||||||
},
|
},
|
||||||
@@ -57,6 +58,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Sentry/6.9.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Sentry.dll": {
|
||||||
|
"assemblyVersion": "6.9.0.0",
|
||||||
|
"fileVersion": "6.9.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"System.Collections/4.3.0": {
|
"System.Collections/4.3.0": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||||
@@ -396,6 +405,13 @@
|
|||||||
"path": "newtonsoftmessagepack/0.1.11",
|
"path": "newtonsoftmessagepack/0.1.11",
|
||||||
"hashPath": "newtonsoftmessagepack.0.1.11.nupkg.sha512"
|
"hashPath": "newtonsoftmessagepack.0.1.11.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"Sentry/6.9.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-qQIvEwuvjAB6fDLVLLcDj/5f8n5jOyPyHjj3a/GQ1ogTLLQqsSxgQj1fEEquNT9HQuj4ZTyCg3c1DCBMUIvJGQ==",
|
||||||
|
"path": "sentry/6.9.0",
|
||||||
|
"hashPath": "sentry.6.9.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"System.Collections/4.3.0": {
|
"System.Collections/4.3.0": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -17,6 +17,9 @@
|
|||||||
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
|
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
|
||||||
"commandTimeoutSeconds": 30
|
"commandTimeoutSeconds": 30
|
||||||
},
|
},
|
||||||
|
"sentry": {
|
||||||
|
"dsn": "https://9ae7cbe5e29f4a85b2a748a78e55a868@glitchtip.glebocka.synology.me/10"
|
||||||
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"logDirectory": "daemon",
|
"logDirectory": "daemon",
|
||||||
"logPrefix": "smartb2bsync-enova"
|
"logPrefix": "smartb2bsync-enova"
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Newtonsoft.Json.dll" to="%BASE%\..\Newtonsoft.Json.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Newtonsoft.Json.dll" to="%BASE%\..\Newtonsoft.Json.dll" failOnError="false" />
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/NewtonsoftMessagePack.dll" to="%BASE%\..\NewtonsoftMessagePack.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/NewtonsoftMessagePack.dll" to="%BASE%\..\NewtonsoftMessagePack.dll" failOnError="false" />
|
||||||
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Sentry.dll" to="%BASE%\..\Sentry.dll" failOnError="false" />
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Reactive.dll" to="%BASE%\..\System.Reactive.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Reactive.dll" to="%BASE%\..\System.Reactive.dll" failOnError="false" />
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Runtime.InteropServices.WindowsRuntime.dll" to="%BASE%\..\System.Runtime.InteropServices.WindowsRuntime.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Runtime.InteropServices.WindowsRuntime.dll" to="%BASE%\..\System.Runtime.InteropServices.WindowsRuntime.dll" failOnError="false" />
|
||||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/WampSharp.dll" to="%BASE%\..\WampSharp.dll" failOnError="false" />
|
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/WampSharp.dll" to="%BASE%\..\WampSharp.dll" failOnError="false" />
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ public sealed class ServiceSettings
|
|||||||
|
|
||||||
public SqlSettings Sql { get; init; } = new();
|
public SqlSettings Sql { get; init; } = new();
|
||||||
|
|
||||||
|
public SentrySettings Sentry { get; init; } = new();
|
||||||
|
|
||||||
public DiagnosticsSettings Diagnostics { get; init; } = new();
|
public DiagnosticsSettings Diagnostics { get; init; } = new();
|
||||||
|
|
||||||
public static ServiceSettings Load(string path)
|
public static ServiceSettings Load(string path)
|
||||||
@@ -137,6 +139,11 @@ public sealed class SqlSettings
|
|||||||
public int CommandTimeoutSeconds { get; init; } = 30;
|
public int CommandTimeoutSeconds { get; init; } = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class SentrySettings
|
||||||
|
{
|
||||||
|
public string? Dsn { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class DiagnosticsSettings
|
public sealed class DiagnosticsSettings
|
||||||
{
|
{
|
||||||
public string LogDirectory { get; init; } = "daemon";
|
public string LogDirectory { get; init; } = "daemon";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Sentry;
|
||||||
using SmartB2B.Enova.Contracts;
|
using SmartB2B.Enova.Contracts;
|
||||||
using SmartB2B.Enova.Service.Rpc;
|
using SmartB2B.Enova.Service.Rpc;
|
||||||
|
|
||||||
@@ -73,6 +74,11 @@ public sealed class PlaceOrderHandler
|
|||||||
}
|
}
|
||||||
catch (EnovaOperationException exception)
|
catch (EnovaOperationException exception)
|
||||||
{
|
{
|
||||||
|
SentrySdk.CaptureException(exception, scope =>
|
||||||
|
{
|
||||||
|
scope.SetTag("rpc.procedure", "eu.smartb2b.place_order");
|
||||||
|
scope.SetTag("wamp.error_uri", exception.ErrorUri);
|
||||||
|
});
|
||||||
throw WampErrorFactory.FromEnova(exception);
|
throw WampErrorFactory.FromEnova(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using SmartB2B.Enova.Service.Orders;
|
|||||||
using SmartB2B.Enova.Service.Rpc;
|
using SmartB2B.Enova.Service.Rpc;
|
||||||
using SmartB2B.Enova.Service.Runtime;
|
using SmartB2B.Enova.Service.Runtime;
|
||||||
using SmartB2B.Enova.Service.Sql;
|
using SmartB2B.Enova.Service.Sql;
|
||||||
|
using Sentry;
|
||||||
using WampSharp.V2.Rpc;
|
using WampSharp.V2.Rpc;
|
||||||
|
|
||||||
namespace SmartB2B.Enova.Service;
|
namespace SmartB2B.Enova.Service;
|
||||||
@@ -13,6 +14,7 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
public static async Task<int> Main(string[] args)
|
public static async Task<int> Main(string[] args)
|
||||||
{
|
{
|
||||||
|
IDisposable? sentry = null;
|
||||||
var cancellation = new CancellationTokenSource();
|
var cancellation = new CancellationTokenSource();
|
||||||
Console.CancelKeyPress += (_, eventArgs) =>
|
Console.CancelKeyPress += (_, eventArgs) =>
|
||||||
{
|
{
|
||||||
@@ -25,6 +27,7 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
var commandLine = CommandLineOptions.Parse(args);
|
var commandLine = CommandLineOptions.Parse(args);
|
||||||
var settings = ServiceSettings.Load(commandLine.ConfigurationPath);
|
var settings = ServiceSettings.Load(commandLine.ConfigurationPath);
|
||||||
|
sentry = InitializeSentry(settings);
|
||||||
var runtime = settings.ResolveRuntimeSettings();
|
var runtime = settings.ResolveRuntimeSettings();
|
||||||
|
|
||||||
using var adapterLoader = new EnovaAdapterLoader(runtime.EnovaInstallationPath);
|
using var adapterLoader = new EnovaAdapterLoader(runtime.EnovaInstallationPath);
|
||||||
@@ -98,8 +101,34 @@ internal static class Program
|
|||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"Błąd krytyczny: {exception}");
|
Console.Error.WriteLine($"Błąd krytyczny: {exception}");
|
||||||
|
SentrySdk.CaptureException(exception);
|
||||||
|
await SentrySdk.FlushAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
sentry?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IDisposable? InitializeSentry(ServiceSettings settings)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(settings.Sentry.Dsn))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Sentry wyłączone (brak parametru sentry.dsn).");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sentry = SentrySdk.Init(options =>
|
||||||
|
{
|
||||||
|
options.Dsn = settings.Sentry.Dsn;
|
||||||
|
options.IsGlobalModeEnabled = true;
|
||||||
|
options.Release = typeof(Program).Assembly.GetName().Version?.ToString();
|
||||||
|
});
|
||||||
|
|
||||||
|
SentrySdk.ConfigureScope(scope => scope.SetTag("portal", settings.Portal));
|
||||||
|
Console.WriteLine("Raportowanie błędów do Sentry włączone.");
|
||||||
|
return sentry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using WampSharp.Core.Serialization;
|
using WampSharp.Core.Serialization;
|
||||||
using WampSharp.V2.Core.Contracts;
|
using WampSharp.V2.Core.Contracts;
|
||||||
using WampSharp.V2.Rpc;
|
using WampSharp.V2.Rpc;
|
||||||
|
using Sentry;
|
||||||
|
|
||||||
namespace SmartB2B.Enova.Service.Rpc;
|
namespace SmartB2B.Enova.Service.Rpc;
|
||||||
|
|
||||||
@@ -99,6 +100,9 @@ public sealed class DelegateRpcOperation : IWampRpcOperation
|
|||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"Nieobsłużony błąd procedury {Procedure}: {exception}");
|
Console.Error.WriteLine($"Nieobsłużony błąd procedury {Procedure}: {exception}");
|
||||||
|
SentrySdk.CaptureException(
|
||||||
|
exception,
|
||||||
|
scope => scope.SetTag("rpc.procedure", Procedure));
|
||||||
var details = formatter.Serialize(new Dictionary<string, object>());
|
var details = formatter.Serialize(new Dictionary<string, object>());
|
||||||
var keywordArguments = formatter.Serialize(new Dictionary<string, object?>
|
var keywordArguments = formatter.Serialize(new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../SmartB2B.Enova.Contracts/SmartB2B.Enova.Contracts.csproj" />
|
<ProjectReference Include="../SmartB2B.Enova.Contracts/SmartB2B.Enova.Contracts.csproj" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Sentry" Version="6.9.0" />
|
||||||
<PackageReference Include="WampSharp.Default.Client" Version="23.8.1" />
|
<PackageReference Include="WampSharp.Default.Client" Version="23.8.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Sentry;
|
||||||
using SmartB2B.Enova.Service.Rpc;
|
using SmartB2B.Enova.Service.Rpc;
|
||||||
|
|
||||||
namespace SmartB2B.Enova.Service.Sql;
|
namespace SmartB2B.Enova.Service.Sql;
|
||||||
@@ -98,6 +99,11 @@ public sealed class SqlRawHandler
|
|||||||
var code = exception.GetType().GetProperty("Number")?.GetValue(exception)
|
var code = exception.GetType().GetProperty("Number")?.GetValue(exception)
|
||||||
?? exception.ErrorCode;
|
?? exception.ErrorCode;
|
||||||
Console.Error.WriteLine($"Błąd SQL {code}: {exception.Message}");
|
Console.Error.WriteLine($"Błąd SQL {code}: {exception.Message}");
|
||||||
|
SentrySdk.CaptureException(exception, scope =>
|
||||||
|
{
|
||||||
|
scope.SetTag("rpc.procedure", "eu.smartb2b.sql_raw");
|
||||||
|
scope.SetExtra("sql.error_code", code);
|
||||||
|
});
|
||||||
throw WampErrorFactory.Create(
|
throw WampErrorFactory.Create(
|
||||||
"eu.smartb2b.sql_error",
|
"eu.smartb2b.sql_error",
|
||||||
exception.Message,
|
exception.Message,
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
|
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
|
||||||
"commandTimeoutSeconds": 30
|
"commandTimeoutSeconds": 30
|
||||||
},
|
},
|
||||||
|
"sentry": {
|
||||||
|
"dsn": "https://9ae7cbe5e29f4a85b2a748a78e55a868@glitchtip.glebocka.synology.me/10"
|
||||||
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"logDirectory": "daemon",
|
"logDirectory": "daemon",
|
||||||
"logPrefix": "smartb2bsync-enova"
|
"logPrefix": "smartb2bsync-enova"
|
||||||
|
|||||||
@@ -207,6 +207,9 @@ static Task SecretsComeFromConfigurationFile()
|
|||||||
"connectionString": "Server=db;Database=enova;User ID=user;Password=sql-z-pliku",
|
"connectionString": "Server=db;Database=enova;User ID=user;Password=sql-z-pliku",
|
||||||
"commandTimeoutSeconds": 30
|
"commandTimeoutSeconds": 30
|
||||||
},
|
},
|
||||||
|
"sentry": {
|
||||||
|
"dsn": "https://public@example.com/1"
|
||||||
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"logDirectory": "C:/logs",
|
"logDirectory": "C:/logs",
|
||||||
"logPrefix": "test"
|
"logPrefix": "test"
|
||||||
@@ -214,8 +217,10 @@ static Task SecretsComeFromConfigurationFile()
|
|||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
|
|
||||||
var runtime = ServiceSettings.Load(path).ResolveRuntimeSettings();
|
var settings = ServiceSettings.Load(path);
|
||||||
|
var runtime = settings.ResolveRuntimeSettings();
|
||||||
|
|
||||||
|
AssertEqual("https://public@example.com/1", settings.Sentry.Dsn, "DSN Sentry");
|
||||||
AssertEqual(
|
AssertEqual(
|
||||||
"haslo-z-pliku",
|
"haslo-z-pliku",
|
||||||
runtime.EnovaConfiguration?.Connection.Password,
|
runtime.EnovaConfiguration?.Connection.Password,
|
||||||
|
|||||||
Reference in New Issue
Block a user