Made certain config options editable in the app config.
This commit is contained in:
+22
-2
@@ -145,6 +145,25 @@ File: `src\HostAvailabilityMonitor\appsettings.json`
|
|||||||
- `HipVirtualMachines`: optional static VM checks
|
- `HipVirtualMachines`: optional static VM checks
|
||||||
- `Targets`: endpoints to probe
|
- `Targets`: endpoints to probe
|
||||||
|
|
||||||
|
### Runtime section
|
||||||
|
|
||||||
|
Important fields in `Runtime`:
|
||||||
|
|
||||||
|
- `MaxConcurrency`: maximum number of parallel endpoint checks
|
||||||
|
- `DefaultValidateUncPathAccess`: default for UNC targets without their own `ValidateUncPathAccess` value
|
||||||
|
- `NonZeroExitCodeOnAnyFailure`: exit with code `2` when endpoint failures are found
|
||||||
|
- `NonZeroExitCodeOnExecutionError`: exit with code `1` on technical execution errors
|
||||||
|
- `StateDirectory`: directory for the state file
|
||||||
|
- `StateFileName`: state file name, default `monitor-state.json`
|
||||||
|
|
||||||
|
### Logging section
|
||||||
|
|
||||||
|
Important fields in `Logging`:
|
||||||
|
|
||||||
|
- `LogDirectory`: directory for rolling log files
|
||||||
|
- `FilePrefix`: prefix for daily log files
|
||||||
|
- `RetentionDays`: number of daily log files to retain, default `30`; `0` disables automatic cleanup
|
||||||
|
|
||||||
### Email section
|
### Email section
|
||||||
|
|
||||||
Important fields in `Email`:
|
Important fields in `Email`:
|
||||||
@@ -330,12 +349,13 @@ It contains:
|
|||||||
- `Logging`
|
- `Logging`
|
||||||
- `EventLog`
|
- `EventLog`
|
||||||
- `Email`
|
- `Email`
|
||||||
|
- `HipVirtualMachines`
|
||||||
|
|
||||||
`Targets` is rebuilt by the generator at runtime.
|
`Targets` is rebuilt by the generator at runtime.
|
||||||
|
|
||||||
Important:
|
Important:
|
||||||
|
|
||||||
When the generator writes the monitor JSON, the daily summary options and static HIP VM settings are also copied into the generated target file. That means `GeneratedMonitorConfig.Email.SendDailySummary`, `DailySummaryHourLocal`, `DailySummaryMinuteLocal`, and `GeneratedMonitorConfig.HipVirtualMachines` flow directly into the output monitor configuration.
|
When the generator writes the monitor JSON, runtime, logging, daily summary, and static HIP VM settings are also copied into the generated target file. That means values such as `GeneratedMonitorConfig.Runtime.StateFileName`, `GeneratedMonitorConfig.Logging.RetentionDays`, `GeneratedMonitorConfig.Email.SendDailySummary`, `DailySummaryHourLocal`, `DailySummaryMinuteLocal`, and `GeneratedMonitorConfig.HipVirtualMachines` flow directly into the output monitor configuration.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -472,7 +492,7 @@ The generator is designed defensively:
|
|||||||
The monitor is also defensive:
|
The monitor is also defensive:
|
||||||
|
|
||||||
- daily rolling logs
|
- daily rolling logs
|
||||||
- default retention of 5 days
|
- default retention of 30 days, configurable through `Logging.RetentionDays`
|
||||||
- event log writes are best effort only
|
- event log writes are best effort only
|
||||||
- state file is written atomically
|
- state file is written atomically
|
||||||
- failure/recovery emails follow explicit notification rules
|
- failure/recovery emails follow explicit notification rules
|
||||||
|
|||||||
+22
-2
@@ -145,6 +145,25 @@ Datei: `src\HostAvailabilityMonitor\appsettings.json`
|
|||||||
- `HipVirtualMachines`: optionale statische VM-Prüfungen
|
- `HipVirtualMachines`: optionale statische VM-Prüfungen
|
||||||
- `Targets`: die zu prüfenden Endpunkte
|
- `Targets`: die zu prüfenden Endpunkte
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
Wichtige Felder im Abschnitt `Runtime`:
|
||||||
|
|
||||||
|
- `MaxConcurrency`: maximale Anzahl paralleler Endpoint-Checks
|
||||||
|
- `DefaultValidateUncPathAccess`: Standardwert fuer UNC-Targets ohne eigenen `ValidateUncPathAccess`-Wert
|
||||||
|
- `NonZeroExitCodeOnAnyFailure`: bei fachlichen DOWN-Ergebnissen mit Exitcode `2` beenden
|
||||||
|
- `NonZeroExitCodeOnExecutionError`: bei technischen Ausfuehrungsfehlern mit Exitcode `1` beenden
|
||||||
|
- `StateDirectory`: Verzeichnis fuer die Statusdatei
|
||||||
|
- `StateFileName`: Dateiname der Statusdatei, standardmaessig `monitor-state.json`
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
Wichtige Felder im Abschnitt `Logging`:
|
||||||
|
|
||||||
|
- `LogDirectory`: Verzeichnis fuer die Rolling-Logdateien
|
||||||
|
- `FilePrefix`: Prefix der taeglichen Logdateien
|
||||||
|
- `RetentionDays`: Anzahl der aufzubewahrenden taeglichen Logdateien, standardmaessig `30`; `0` deaktiviert die automatische Bereinigung
|
||||||
|
|
||||||
### Email
|
### Email
|
||||||
|
|
||||||
Wichtige Felder im Abschnitt `Email`:
|
Wichtige Felder im Abschnitt `Email`:
|
||||||
@@ -330,12 +349,13 @@ Hier werden gepflegt:
|
|||||||
- `Logging`
|
- `Logging`
|
||||||
- `EventLog`
|
- `EventLog`
|
||||||
- `Email`
|
- `Email`
|
||||||
|
- `HipVirtualMachines`
|
||||||
|
|
||||||
`Targets` wird vom Generator zur Laufzeit neu erzeugt.
|
`Targets` wird vom Generator zur Laufzeit neu erzeugt.
|
||||||
|
|
||||||
Wichtig:
|
Wichtig:
|
||||||
|
|
||||||
Wenn der Generator die Monitor-JSON erzeugt, werden auch die Felder für die Tagesstatus-Mail und die statische HIP-VM-Prüfung mit erzeugt. Das heißt: `GeneratedMonitorConfig.Email.SendDailySummary`, `DailySummaryHourLocal`, `DailySummaryMinuteLocal` und `GeneratedMonitorConfig.HipVirtualMachines` werden direkt in die Zielkonfiguration übernommen.
|
Wenn der Generator die Monitor-JSON erzeugt, werden auch Runtime-, Logging-, Tagesstatus-Mail- und statische HIP-VM-Felder mit erzeugt. Das heißt: Werte wie `GeneratedMonitorConfig.Runtime.StateFileName`, `GeneratedMonitorConfig.Logging.RetentionDays`, `GeneratedMonitorConfig.Email.SendDailySummary`, `DailySummaryHourLocal`, `DailySummaryMinuteLocal` und `GeneratedMonitorConfig.HipVirtualMachines` werden direkt in die Zielkonfiguration übernommen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -472,7 +492,7 @@ Der Generator ist defensiv gebaut:
|
|||||||
Der Monitor ist ebenfalls defensiv gebaut:
|
Der Monitor ist ebenfalls defensiv gebaut:
|
||||||
|
|
||||||
- Rolling-Dateilog mit täglicher Rotation
|
- Rolling-Dateilog mit täglicher Rotation
|
||||||
- Retention standardmäßig 5 Tage
|
- Retention standardmäßig 30 Tage, konfigurierbar über `Logging.RetentionDays`
|
||||||
- Eventlog-Schreiben nur best effort
|
- Eventlog-Schreiben nur best effort
|
||||||
- Statusdatei mit atomischem Schreiben
|
- Statusdatei mit atomischem Schreiben
|
||||||
- Failure-/Recovery-Mails nur bei definierten Bedingungen
|
- Failure-/Recovery-Mails nur bei definierten Bedingungen
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ Diese Struktur ist bewusst einfach gehalten, damit das Repository sauber in **Gi
|
|||||||
|
|
||||||
- Task-Scheduler-tauglich
|
- Task-Scheduler-tauglich
|
||||||
- tägliche Logrotation
|
- tägliche Logrotation
|
||||||
- Log-Retention standardmäßig 5 Tage
|
- Log-Retention standardmäßig 30 Tage
|
||||||
- Windows Application Event Log
|
- Windows Application Event Log
|
||||||
- SMTP-Mails bei DOWN, Recovery (`DOWN -> UP`) und täglichem Status
|
- SMTP-Mails bei DOWN, Recovery (`DOWN -> UP`) und täglichem Status
|
||||||
- SMTP-Sendports bzw. reine E-Mail-Empfaengerlisten werden vom Generator uebersprungen und vom Monitor defensiv nur als `Skipped` protokolliert, nicht alarmiert
|
- SMTP-Sendports bzw. reine E-Mail-Empfaengerlisten werden vom Generator uebersprungen und vom Monitor defensiv nur als `Skipped` protokolliert, nicht alarmiert
|
||||||
@@ -341,6 +341,8 @@ Wichtige Felder:
|
|||||||
- `BizTalk.ApplicationMappings`: Mapping von BizTalk-Anwendungsnamen auf fachliche App-Namen
|
- `BizTalk.ApplicationMappings`: Mapping von BizTalk-Anwendungsnamen auf fachliche App-Namen
|
||||||
- `Output.Path`: Pfad der zu erzeugenden Monitor-JSON
|
- `Output.Path`: Pfad der zu erzeugenden Monitor-JSON
|
||||||
- `GeneratedMonitorConfig`: Vorlage für Logging, Runtime, Event Log und Mail des Monitors
|
- `GeneratedMonitorConfig`: Vorlage für Logging, Runtime, Event Log und Mail des Monitors
|
||||||
|
- `GeneratedMonitorConfig.Logging.RetentionDays`: Aufbewahrung der taeglichen Monitor-Logdateien, standardmaessig `30`
|
||||||
|
- `GeneratedMonitorConfig.Runtime.StateFileName`: Dateiname der Statusdatei, falls mehrere Monitor-Konfigurationen getrennten Zustand brauchen
|
||||||
- `GeneratedMonitorConfig.HipVirtualMachines`: statische HIP-VM-Prüfung in der generierten Monitor-JSON einschalten
|
- `GeneratedMonitorConfig.HipVirtualMachines`: statische HIP-VM-Prüfung in der generierten Monitor-JSON einschalten
|
||||||
- `GeneratedMonitorConfig.Email.SendDailySummary`: tägliche Status-Mail in der generierten Monitor-JSON einschalten
|
- `GeneratedMonitorConfig.Email.SendDailySummary`: tägliche Status-Mail in der generierten Monitor-JSON einschalten
|
||||||
- `GeneratedMonitorConfig.Email.DailySummaryHourLocal`: lokale Uhrzeit der Tageszusammenfassung
|
- `GeneratedMonitorConfig.Email.DailySummaryHourLocal`: lokale Uhrzeit der Tageszusammenfassung
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ namespace BizTalkMonitorConfigGenerator.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the number of daily generator log files to retain.
|
/// Gets or sets the number of daily generator log files to retain.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int RetentionDays { get; set; } = 5;
|
public int RetentionDays { get; set; } = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -424,6 +424,23 @@ namespace BizTalkMonitorConfigGenerator.Configuration
|
|||||||
configuration.Runtime.MaxConcurrency = 1;
|
configuration.Runtime.MaxConcurrency = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(configuration.Runtime.StateDirectory))
|
||||||
|
{
|
||||||
|
configuration.Runtime.StateDirectory = "state";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(configuration.Runtime.StateFileName))
|
||||||
|
{
|
||||||
|
configuration.Runtime.StateFileName = "monitor-state.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(configuration.Runtime.StateFileName)
|
||||||
|
|| configuration.Runtime.StateFileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0
|
||||||
|
|| configuration.Runtime.StateFileName.IndexOfAny(new[] { '\\', '/' }) >= 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("GeneratedMonitorConfig.Runtime.StateFileName muss ein Dateiname ohne Pfadanteile sein.");
|
||||||
|
}
|
||||||
|
|
||||||
if (configuration.Logging.RetentionDays < 0)
|
if (configuration.Logging.RetentionDays < 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("GeneratedMonitorConfig.Logging.RetentionDays darf nicht negativ sein.");
|
throw new InvalidOperationException("GeneratedMonitorConfig.Logging.RetentionDays darf nicht negativ sein.");
|
||||||
@@ -493,7 +510,8 @@ namespace BizTalkMonitorConfigGenerator.Configuration
|
|||||||
DefaultValidateUncPathAccess = configuration.Runtime.DefaultValidateUncPathAccess,
|
DefaultValidateUncPathAccess = configuration.Runtime.DefaultValidateUncPathAccess,
|
||||||
NonZeroExitCodeOnAnyFailure = configuration.Runtime.NonZeroExitCodeOnAnyFailure,
|
NonZeroExitCodeOnAnyFailure = configuration.Runtime.NonZeroExitCodeOnAnyFailure,
|
||||||
NonZeroExitCodeOnExecutionError = configuration.Runtime.NonZeroExitCodeOnExecutionError,
|
NonZeroExitCodeOnExecutionError = configuration.Runtime.NonZeroExitCodeOnExecutionError,
|
||||||
StateDirectory = configuration.Runtime.StateDirectory
|
StateDirectory = configuration.Runtime.StateDirectory,
|
||||||
|
StateFileName = configuration.Runtime.StateFileName
|
||||||
},
|
},
|
||||||
Logging = new LoggingOptions
|
Logging = new LoggingOptions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ namespace BizTalkMonitorConfigGenerator
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var directory = ResolvePath(baseDirectory ?? AppDomain.CurrentDomain.BaseDirectory, "logs");
|
var directory = ResolvePath(baseDirectory ?? AppDomain.CurrentDomain.BaseDirectory, "logs");
|
||||||
var fallbackLogger = new GeneratorFileLogger(directory, "biztalk-monitor-config-generator-startup", 5, "BizTalkMonitorConfigGenerator", "Startup");
|
var fallbackLogger = new GeneratorFileLogger(directory, "biztalk-monitor-config-generator-startup", 30, "BizTalkMonitorConfigGenerator", "Startup");
|
||||||
fallbackLogger.Error("Generator konnte nicht gestartet werden. Konfiguration=" + (configPath ?? "(nicht aufgeloest)") + ". Fehler=" + exception.GetType().Name + ": " + exception.Message);
|
fallbackLogger.Error("Generator konnte nicht gestartet werden. Konfiguration=" + (configPath ?? "(nicht aufgeloest)") + ". Fehler=" + exception.GetType().Name + ": " + exception.Message);
|
||||||
fallbackLogger.Error("Details: " + exception);
|
fallbackLogger.Error("Details: " + exception);
|
||||||
WriteConsoleInfo("Fallback-Log geschrieben: " + BuildLogFilePreview(directory, "biztalk-monitor-config-generator-startup"));
|
WriteConsoleInfo("Fallback-Log geschrieben: " + BuildLogFilePreview(directory, "biztalk-monitor-config-generator-startup"));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"Logging": {
|
"Logging": {
|
||||||
"LogDirectory": "logs",
|
"LogDirectory": "logs",
|
||||||
"FilePrefix": "biztalk-monitor-config-generator",
|
"FilePrefix": "biztalk-monitor-config-generator",
|
||||||
"RetentionDays": 5
|
"RetentionDays": 30
|
||||||
},
|
},
|
||||||
"EventLog": {
|
"EventLog": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
@@ -52,12 +52,13 @@
|
|||||||
"DefaultValidateUncPathAccess": false,
|
"DefaultValidateUncPathAccess": false,
|
||||||
"NonZeroExitCodeOnAnyFailure": false,
|
"NonZeroExitCodeOnAnyFailure": false,
|
||||||
"NonZeroExitCodeOnExecutionError": true,
|
"NonZeroExitCodeOnExecutionError": true,
|
||||||
"StateDirectory": "state"
|
"StateDirectory": "state",
|
||||||
|
"StateFileName": "monitor-state.json"
|
||||||
},
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogDirectory": "logs",
|
"LogDirectory": "logs",
|
||||||
"FilePrefix": "host-availability-monitor",
|
"FilePrefix": "host-availability-monitor",
|
||||||
"RetentionDays": 5
|
"RetentionDays": 30
|
||||||
},
|
},
|
||||||
"EventLog": {
|
"EventLog": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
|
|||||||
@@ -158,6 +158,23 @@ namespace HostAvailabilityMonitor.Configuration
|
|||||||
Runtime.MaxConcurrency = 1;
|
Runtime.MaxConcurrency = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(Runtime.StateDirectory))
|
||||||
|
{
|
||||||
|
Runtime.StateDirectory = "state";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(Runtime.StateFileName))
|
||||||
|
{
|
||||||
|
Runtime.StateFileName = "monitor-state.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(Runtime.StateFileName)
|
||||||
|
|| Runtime.StateFileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0
|
||||||
|
|| Runtime.StateFileName.IndexOfAny(new[] { '\\', '/' }) >= 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Runtime.StateFileName muss ein Dateiname ohne Pfadanteile sein.");
|
||||||
|
}
|
||||||
|
|
||||||
if (HipVirtualMachines == null)
|
if (HipVirtualMachines == null)
|
||||||
{
|
{
|
||||||
HipVirtualMachines = new HipVirtualMachineOptions();
|
HipVirtualMachines = new HipVirtualMachineOptions();
|
||||||
@@ -286,6 +303,11 @@ namespace HostAvailabilityMonitor.Configuration
|
|||||||
/// Gets or sets the state directory path.
|
/// Gets or sets the state directory path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string StateDirectory { get; set; } = "state";
|
public string StateDirectory { get; set; } = "state";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the state file name inside the configured state directory.
|
||||||
|
/// </summary>
|
||||||
|
public string StateFileName { get; set; } = "monitor-state.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -306,7 +328,7 @@ namespace HostAvailabilityMonitor.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the number of daily log files to retain.
|
/// Gets or sets the number of daily log files to retain.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int RetentionDays { get; set; } = 5;
|
public int RetentionDays { get; set; } = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace HostAvailabilityMonitor
|
|||||||
|
|
||||||
var logDirectory = ResolvePath(baseDirectory, config.Logging.LogDirectory);
|
var logDirectory = ResolvePath(baseDirectory, config.Logging.LogDirectory);
|
||||||
var stateDirectory = ResolvePath(baseDirectory, config.Runtime.StateDirectory);
|
var stateDirectory = ResolvePath(baseDirectory, config.Runtime.StateDirectory);
|
||||||
var stateFilePath = Path.Combine(stateDirectory, "monitor-state.json");
|
var stateFilePath = Path.Combine(stateDirectory, config.Runtime.StateFileName);
|
||||||
|
|
||||||
WriteConsoleInfo("Initialisiere Dateilog: " + BuildLogFilePreview(logDirectory, config.Logging.FilePrefix));
|
WriteConsoleInfo("Initialisiere Dateilog: " + BuildLogFilePreview(logDirectory, config.Logging.FilePrefix));
|
||||||
logger = new FileLogger(logDirectory, config.Logging.FilePrefix, config.Logging.RetentionDays, config.ApplicationName, config.EnvironmentName);
|
logger = new FileLogger(logDirectory, config.Logging.FilePrefix, config.Logging.RetentionDays, config.ApplicationName, config.EnvironmentName);
|
||||||
@@ -699,7 +699,7 @@ namespace HostAvailabilityMonitor
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var directory = ResolvePath(baseDirectory ?? AppDomain.CurrentDomain.BaseDirectory, "logs");
|
var directory = ResolvePath(baseDirectory ?? AppDomain.CurrentDomain.BaseDirectory, "logs");
|
||||||
var fallbackLogger = new FileLogger(directory, "host-availability-monitor-startup", 5, "HostAvailabilityMonitor", "Startup");
|
var fallbackLogger = new FileLogger(directory, "host-availability-monitor-startup", 30, "HostAvailabilityMonitor", "Startup");
|
||||||
fallbackLogger.Error("Monitor konnte nicht gestartet werden. Konfiguration=" + (configPath ?? "(nicht aufgeloest)") + ". Fehler=" + exception.GetType().Name + ": " + exception.Message);
|
fallbackLogger.Error("Monitor konnte nicht gestartet werden. Konfiguration=" + (configPath ?? "(nicht aufgeloest)") + ". Fehler=" + exception.GetType().Name + ": " + exception.Message);
|
||||||
fallbackLogger.Error("Details: " + exception);
|
fallbackLogger.Error("Details: " + exception);
|
||||||
WriteConsoleInfo("Fallback-Log geschrieben: " + BuildLogFilePreview(directory, "host-availability-monitor-startup"));
|
WriteConsoleInfo("Fallback-Log geschrieben: " + BuildLogFilePreview(directory, "host-availability-monitor-startup"));
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
"DefaultValidateUncPathAccess": false,
|
"DefaultValidateUncPathAccess": false,
|
||||||
"NonZeroExitCodeOnAnyFailure": false,
|
"NonZeroExitCodeOnAnyFailure": false,
|
||||||
"NonZeroExitCodeOnExecutionError": true,
|
"NonZeroExitCodeOnExecutionError": true,
|
||||||
"StateDirectory": "state"
|
"StateDirectory": "state",
|
||||||
|
"StateFileName": "monitor-state.json"
|
||||||
},
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogDirectory": "logs",
|
"LogDirectory": "logs",
|
||||||
"FilePrefix": "host-availability-monitor",
|
"FilePrefix": "host-availability-monitor",
|
||||||
"RetentionDays": 5
|
"RetentionDays": 30
|
||||||
},
|
},
|
||||||
"EventLog": {
|
"EventLog": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user