mirror of
https://github.com/Icinga/icinga-powershell-framework.git
synced 2026-02-03 04:09:29 -05:00
Merge pull request #841 from Icinga:feature/adds_new_type_info_for_checks_and_packages
Feature: Adds new [INFO] state for notice and un-checked monitoring objects Adds new [INFO] state for `New-IcingaCheck` and `New-IcingaCheckPackage`, to allow the printing of simple informational objects as well as telling the user, which objects are currently actively checked by the plugin engine. ```powershell icinga> Invoke-IcingaCheckCPU -Verbosity 3; [INFO] CPU Load (All must be [OK]) \_ [INFO] Overall Load: 8.714580% \_ [INFO] Socket #0 (All must be [OK]) \_ [INFO] Core 0: 22.63846% \_ [INFO] Core 1: 11.04723% \_ [INFO] Core 2: 0.672020% \_ [INFO] Core 3: 0.500612% \_ [INFO] Core Total: 8.714580% | totalload::ifw_cpu::load=8.714580%;;;0;100 0_0::ifw_cpu::load=22.63846%;;;0;100 0_1::ifw_cpu::load=11.04723%;;;0;100 0_2::ifw_cpu::load=0.672020%;;;0;100 0_3::ifw_cpu::load=0.500612%;;;0;100 0_total::ifw_cpu::load=8.714580%;;;0;100 ``` ```powershell icinga> Invoke-IcingaCheckProcess -Verbosity 3 -Process WmiApSrv -MemoryWarning '1MB' -PageFileWarning '1KiB'; [WARNING] Process Overview: 1 Warning [WARNING] WmiApSrv (All must be [OK]) \_ [WARNING] WmiApSrv (All must be [OK]) \_ [WARNING] WmiApSrv [16476] (All must be [OK]) \_ [INFO] CPU Usage: 0% \_ [WARNING] Memory Usage: Value 1.67MiB is greater than threshold 976.56KiB \_ [INFO] Page File Usage: 2.30KiB \_ [INFO] Thread Count: 5c \_ [INFO] WmiApSrv Summary (All must be [OK]) \_ [INFO] CPU Usage: 0% \_ [INFO] Memory Usage: 1.67MiB \_ [INFO] Page File Usage: 2.30KiB \_ [INFO] Process Count: 1c \_ [INFO] Thread Count: 5c | wmiapsrv::ifw_process::cpu=0%;;;0;100 wmiapsrv::ifw_process::memory=1748992B;;;0;8583315000 wmiapsrv::ifw_process::pagefile=2352B;;;0;34359740000 wmiapsrv::ifw_process::count=1c;;;; wmiapsrv::ifw_process::threads=5c;;;; ```
This commit is contained in:
commit
ff4a0b2bbc
4 changed files with 99 additions and 24 deletions
|
|
@ -19,8 +19,9 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#838](https://github.com/Icinga/icinga-powershell-framework/pull/838) Enhances Icinga for Windows to never load and user PowerShell profiles
|
|
||||||
* [#11](https://github.com/Icinga/icinga-powershell-framework/pull/11) Adds feature to update the cache for performance counter instances to keep track of system changes
|
* [#11](https://github.com/Icinga/icinga-powershell-framework/pull/11) Adds feature to update the cache for performance counter instances to keep track of system changes
|
||||||
|
* [#838](https://github.com/Icinga/icinga-powershell-framework/pull/838) Enhances Icinga for Windows to never load and user PowerShell profiles
|
||||||
|
* [#841](https://github.com/Icinga/icinga-powershell-framework/pull/841) Adds new [INFO] state for notice and un-checked monitoring objects
|
||||||
|
|
||||||
## 1.13.4 (tbd)
|
## 1.13.4 (tbd)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ function New-IcingaCheck()
|
||||||
|
|
||||||
# Override shared function
|
# Override shared function
|
||||||
$IcingaCheck | Add-Member -MemberType ScriptMethod -Force -Name '__SetCheckOutput' -Value {
|
$IcingaCheck | Add-Member -MemberType ScriptMethod -Force -Name '__SetCheckOutput' -Value {
|
||||||
param ($PluginOutput);
|
param ($PluginOutput, $CheckOverride);
|
||||||
|
|
||||||
if ($this.__InLockState()) {
|
if ($this.__InLockState()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -140,9 +140,25 @@ function New-IcingaCheck()
|
||||||
$AddColon = $FALSE;
|
$AddColon = $FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[string]$PluginStatusString = $IcingaEnums.IcingaExitCodeText[$this.__CheckState];
|
||||||
|
|
||||||
|
# If our thresholds are empty, we handle this as notice object
|
||||||
|
if (-not $CheckOverride -And ([string]::IsNullOrEmpty($this.__WarningValue.Threshold.Raw) -and [string]::IsNullOrEmpty($this.__CriticalValue.Threshold.Raw))) {
|
||||||
|
# If our call sets CheckOverride, it means we could have used something like SetWarning() before
|
||||||
|
# By doing so, we actively interact with the object and therefore we should not handle it as notice object
|
||||||
|
$this.__HandleAsNoticeObject = $TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set this object to [INFO] state in case it is a notice or no thresholds are defined
|
||||||
|
# This will ensure we tell the use which check is using active checks
|
||||||
|
if ($this.__IsNoticeObject -or $this.__HandleAsNoticeObject) {
|
||||||
|
$PluginStatusString = '[INFO]';
|
||||||
|
$this.__HandleAsNoticeObject = $TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
$this.__CheckOutput = [string]::Format(
|
$this.__CheckOutput = [string]::Format(
|
||||||
'{0} {1}{2} {3}{4}',
|
'{0} {1}{2} {3}{4}',
|
||||||
$IcingaEnums.IcingaExitCodeText[$this.__CheckState],
|
$PluginStatusString,
|
||||||
$this.Name,
|
$this.Name,
|
||||||
(&{ if ($AddColon) { return ':'; } else { return ''; } }),
|
(&{ if ($AddColon) { return ':'; } else { return ''; } }),
|
||||||
$PluginThresholds,
|
$PluginThresholds,
|
||||||
|
|
@ -296,7 +312,7 @@ function New-IcingaCheck()
|
||||||
$IcingaCheck | Add-Member -MemberType ScriptMethod -Name '__ValidateObject' -Value {
|
$IcingaCheck | Add-Member -MemberType ScriptMethod -Name '__ValidateObject' -Value {
|
||||||
if ($null -eq $this.ObjectExists) {
|
if ($null -eq $this.ObjectExists) {
|
||||||
$this.SetUnknown() | Out-Null;
|
$this.SetUnknown() | Out-Null;
|
||||||
$this.__SetCheckOutput('The object does not exist');
|
$this.__SetCheckOutput('The object does not exist', $TRUE);
|
||||||
$this.__LockState();
|
$this.__LockState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +333,8 @@ function New-IcingaCheck()
|
||||||
'Usage of invalid plugin unit "{0}". Allowed units are: {1}',
|
'Usage of invalid plugin unit "{0}". Allowed units are: {1}',
|
||||||
$this.Unit,
|
$this.Unit,
|
||||||
(($IcingaEnums.IcingaMeasurementUnits.Keys | Sort-Object name) -Join ', ')
|
(($IcingaEnums.IcingaMeasurementUnits.Keys | Sort-Object name) -Join ', ')
|
||||||
)
|
),
|
||||||
|
$TRUE
|
||||||
);
|
);
|
||||||
|
|
||||||
$this.__LockState();
|
$this.__LockState();
|
||||||
|
|
@ -395,8 +412,12 @@ function New-IcingaCheck()
|
||||||
param ([string]$Message, [bool]$Lock);
|
param ([string]$Message, [bool]$Lock);
|
||||||
|
|
||||||
if ($this.__InLockState() -eq $FALSE) {
|
if ($this.__InLockState() -eq $FALSE) {
|
||||||
|
# If we update the state of an object to anything, we actively tell the system
|
||||||
|
# that this is no longer a notice object
|
||||||
|
$this.__HandleAsNoticeObject = $FALSE;
|
||||||
|
|
||||||
$this.__CheckState = $IcingaEnums.IcingaExitCode.Ok;
|
$this.__CheckState = $IcingaEnums.IcingaExitCode.Ok;
|
||||||
$this.__SetCheckOutput($Message);
|
$this.__SetCheckOutput($Message, $TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($Lock) {
|
if ($Lock) {
|
||||||
|
|
@ -410,8 +431,11 @@ function New-IcingaCheck()
|
||||||
param ([string]$Message, [bool]$Lock);
|
param ([string]$Message, [bool]$Lock);
|
||||||
|
|
||||||
if ($this.__InLockState() -eq $FALSE) {
|
if ($this.__InLockState() -eq $FALSE) {
|
||||||
|
# If we update the state of an object to anything, we actively tell the system
|
||||||
|
# that this is no longer a notice object
|
||||||
|
$this.__HandleAsNoticeObject = $FALSE;
|
||||||
$this.__CheckState = $IcingaEnums.IcingaExitCode.Warning;
|
$this.__CheckState = $IcingaEnums.IcingaExitCode.Warning;
|
||||||
$this.__SetCheckOutput($Message);
|
$this.__SetCheckOutput($Message, $TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($Lock) {
|
if ($Lock) {
|
||||||
|
|
@ -425,7 +449,25 @@ function New-IcingaCheck()
|
||||||
param ([string]$Message, [bool]$Lock);
|
param ([string]$Message, [bool]$Lock);
|
||||||
|
|
||||||
if ($this.__InLockState() -eq $FALSE) {
|
if ($this.__InLockState() -eq $FALSE) {
|
||||||
|
# If we update the state of an object to anything, we actively tell the system
|
||||||
|
# that this is no longer a notice object
|
||||||
|
$this.__HandleAsNoticeObject = $FALSE;
|
||||||
$this.__CheckState = $IcingaEnums.IcingaExitCode.Critical;
|
$this.__CheckState = $IcingaEnums.IcingaExitCode.Critical;
|
||||||
|
$this.__SetCheckOutput($Message, $TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Lock) {
|
||||||
|
$this.__LockState();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$IcingaCheck | Add-Member -MemberType ScriptMethod -Name 'SetNotice' -Value {
|
||||||
|
param ([string]$Message, [bool]$Lock);
|
||||||
|
|
||||||
|
if ($this.__InLockState() -eq $FALSE) {
|
||||||
|
$this.__IsNoticeObject = $TRUE;
|
||||||
$this.__SetCheckOutput($Message);
|
$this.__SetCheckOutput($Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -440,8 +482,11 @@ function New-IcingaCheck()
|
||||||
param ([string]$Message, [bool]$Lock);
|
param ([string]$Message, [bool]$Lock);
|
||||||
|
|
||||||
if ($this.__InLockState() -eq $FALSE) {
|
if ($this.__InLockState() -eq $FALSE) {
|
||||||
|
# If we update the state of an object to anything, we actively tell the system
|
||||||
|
# that this is no longer a notice object
|
||||||
|
$this.__HandleAsNoticeObject = $FALSE;
|
||||||
$this.__CheckState = $IcingaEnums.IcingaExitCode.Unknown;
|
$this.__CheckState = $IcingaEnums.IcingaExitCode.Unknown;
|
||||||
$this.__SetCheckOutput($Message);
|
$this.__SetCheckOutput($Message, $TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($Lock) {
|
if ($Lock) {
|
||||||
|
|
@ -457,7 +502,7 @@ function New-IcingaCheck()
|
||||||
if ($ThresholdObject.HasError) {
|
if ($ThresholdObject.HasError) {
|
||||||
$this.SetUnknown() | Out-Null;
|
$this.SetUnknown() | Out-Null;
|
||||||
$this.__ThresholdObject = $ThresholdObject;
|
$this.__ThresholdObject = $ThresholdObject;
|
||||||
$this.__SetCheckOutput($this.__ThresholdObject.Message);
|
$this.__SetCheckOutput($this.__ThresholdObject.Message, $TRUE);
|
||||||
$this.__LockState();
|
$this.__LockState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,19 @@ function New-IcingaCheckBaseObject()
|
||||||
{
|
{
|
||||||
$IcingaCheckBaseObject = New-Object -TypeName PSObject;
|
$IcingaCheckBaseObject = New-Object -TypeName PSObject;
|
||||||
|
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name 'Name' -Value '';
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name 'Name' -Value '';
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name 'Verbose' -Value 0;
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name 'Verbose' -Value 0;
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__Hidden' -Value $FALSE;
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__Hidden' -Value $FALSE;
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__SkipSummary' -Value $FALSE;
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__SkipSummary' -Value $FALSE;
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__Parent' -Value $IcingaCheckBaseObject;
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__Parent' -Value $IcingaCheckBaseObject;
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__Indention' -Value 0;
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__Indention' -Value 0;
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__ErrorMessage' -Value '';
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__ErrorMessage' -Value '';
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__CheckState' -Value $IcingaEnums.IcingaExitCode.Ok;
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__CheckState' -Value $IcingaEnums.IcingaExitCode.Ok;
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__CheckCommand' -Value '';
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__CheckCommand' -Value '';
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__CheckOutput' -Value $null;
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__CheckOutput' -Value $null;
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__ObjectType' -Value 'IcingaCheckBaseObject';
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__IsNoticeObject' -Value $FALSE;
|
||||||
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__HandleAsNoticeObject' -Value $FALSE;
|
||||||
|
$IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__ObjectType' -Value 'IcingaCheckBaseObject';
|
||||||
|
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType ScriptMethod -Name '__SetCheckCommand' -Value {
|
$IcingaCheckBaseObject | Add-Member -MemberType ScriptMethod -Name '__SetCheckCommand' -Value {
|
||||||
$CallStack = Get-PSCallStack;
|
$CallStack = Get-PSCallStack;
|
||||||
|
|
@ -103,7 +105,7 @@ function New-IcingaCheckBaseObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType ScriptMethod -Force -Name '__SetCheckOutput' -Value {
|
$IcingaCheckBaseObject | Add-Member -MemberType ScriptMethod -Force -Name '__SetCheckOutput' -Value {
|
||||||
param ($PluginOutput);
|
param ($PluginOutput, $CheckOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
$IcingaCheckBaseObject | Add-Member -MemberType ScriptMethod -Name '__GetCheckOutput' -Value {
|
$IcingaCheckBaseObject | Add-Member -MemberType ScriptMethod -Name '__GetCheckOutput' -Value {
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ function New-IcingaCheckPackage()
|
||||||
[int]$OperatorMax = -1,
|
[int]$OperatorMax = -1,
|
||||||
[array]$Checks = @(),
|
[array]$Checks = @(),
|
||||||
[int]$Verbose = 0,
|
[int]$Verbose = 0,
|
||||||
|
[int]$OverrideExitCode = -1,
|
||||||
[switch]$IgnoreEmptyPackage = $FALSE,
|
[switch]$IgnoreEmptyPackage = $FALSE,
|
||||||
[switch]$Hidden = $FALSE,
|
[switch]$Hidden = $FALSE,
|
||||||
[switch]$AddSummaryHeader = $FALSE
|
[switch]$AddSummaryHeader = $FALSE,
|
||||||
|
[switch]$IsNoticePackage = $FALSE
|
||||||
);
|
);
|
||||||
|
|
||||||
$IcingaCheckPackage = New-IcingaCheckBaseObject;
|
$IcingaCheckPackage = New-IcingaCheckBaseObject;
|
||||||
|
|
@ -28,6 +30,11 @@ function New-IcingaCheckPackage()
|
||||||
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name 'OperatorMax' -Value $OperatorMax;
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name 'OperatorMax' -Value $OperatorMax;
|
||||||
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name 'IgnoreEmptyPackage' -Value $IgnoreEmptyPackage;
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name 'IgnoreEmptyPackage' -Value $IgnoreEmptyPackage;
|
||||||
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name 'AddSummaryHeader' -Value $AddSummaryHeader;
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name 'AddSummaryHeader' -Value $AddSummaryHeader;
|
||||||
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name 'OverrideExitCode' -Value $OverrideExitCode;
|
||||||
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name 'IsNoticePackage' -Value $IsNoticePackage;
|
||||||
|
# Each check package should have at least one element which provides active checks information,
|
||||||
|
# otherwise we should display the package as INFO package
|
||||||
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name '__hasActiveChecks' -Value $FALSE;
|
||||||
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name '__Checks' -Value @();
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name '__Checks' -Value @();
|
||||||
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name '__OkChecks' -Value @();
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name '__OkChecks' -Value @();
|
||||||
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name '__WarningChecks' -Value @();
|
$IcingaCheckPackage | Add-Member -MemberType NoteProperty -Name '__WarningChecks' -Value @();
|
||||||
|
|
@ -93,7 +100,7 @@ function New-IcingaCheckPackage()
|
||||||
|
|
||||||
# Override shared function
|
# Override shared function
|
||||||
$IcingaCheckPackage | Add-Member -MemberType ScriptMethod -Force -Name '__SetCheckOutput' -Value {
|
$IcingaCheckPackage | Add-Member -MemberType ScriptMethod -Force -Name '__SetCheckOutput' -Value {
|
||||||
param ($PluginOutput);
|
param ($PluginOutput, $CheckOverride);
|
||||||
|
|
||||||
$UnknownChecks = '';
|
$UnknownChecks = '';
|
||||||
$CriticalChecks = '';
|
$CriticalChecks = '';
|
||||||
|
|
@ -140,9 +147,18 @@ function New-IcingaCheckPackage()
|
||||||
$HasContent = $TRUE;
|
$HasContent = $TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[string]$PluginStatusString = $IcingaEnums.IcingaExitCodeText[$this.__GetCheckState()];
|
||||||
|
|
||||||
|
# Set this object to [INFO] state in case it is a notice package or no active checks are found
|
||||||
|
# This will ensure we tell the use which check is using active checks
|
||||||
|
if ($this.IsNoticePackage -or $this.__hasActiveChecks -eq $FALSE) {
|
||||||
|
$PluginStatusString = '[INFO]';
|
||||||
|
$this.__HandleAsNoticeObject = $TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
$this.__CheckOutput = [string]::Format(
|
$this.__CheckOutput = [string]::Format(
|
||||||
'{0} {1}{2}{3}{4}{5}{6}{7}{8}',
|
'{0} {1}{2}{3}{4}{5}{6}{7}{8}',
|
||||||
$IcingaEnums.IcingaExitCodeText[$this.__GetCheckState()],
|
$PluginStatusString,
|
||||||
$this.Name,
|
$this.Name,
|
||||||
(&{ if ($HasContent) { return ':'; } else { return ''; } }),
|
(&{ if ($HasContent) { return ':'; } else { return ''; } }),
|
||||||
$CheckSummary.ToString(),
|
$CheckSummary.ToString(),
|
||||||
|
|
@ -179,6 +195,13 @@ function New-IcingaCheckPackage()
|
||||||
|
|
||||||
$check.Compile();
|
$check.Compile();
|
||||||
|
|
||||||
|
# If we find at least one check which is not a notice object, we can consider
|
||||||
|
# this package as active checks package
|
||||||
|
if ($check.__HandleAsNoticeObject -eq $FALSE) {
|
||||||
|
$this.__hasActiveChecks = $TRUE;
|
||||||
|
$this.__HandleAsNoticeObject = $FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
if ($check.__IsHidden() -Or $check.__NoHeaderReport()) {
|
if ($check.__IsHidden() -Or $check.__NoHeaderReport()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -317,6 +340,10 @@ function New-IcingaCheckPackage()
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this.IsNoticePackage) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
if ($this.OperatorAnd) {
|
if ($this.OperatorAnd) {
|
||||||
return ' (All must be [OK])';
|
return ' (All must be [OK])';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue