126 lines
5.0 KiB
PowerShell
126 lines
5.0 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$StatusPath = (Join-Path $env:LOCALAPPDATA 'Domovoy\monitoring\status.json'),
|
|
[string]$StatePath = (Join-Path $env:LOCALAPPDATA 'Domovoy\monitoring\notification-state.json'),
|
|
[ValidateRange(1, 168)]
|
|
[int]$ReminderHours = 6,
|
|
[switch]$Force,
|
|
[switch]$NoDisplay
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version Latest
|
|
|
|
function Write-AtomicJson {
|
|
param([Parameter(Mandatory)]$Value, [Parameter(Mandatory)][string]$Path)
|
|
|
|
$directory = Split-Path -Parent $Path
|
|
if (-not (Test-Path -LiteralPath $directory -PathType Container)) {
|
|
New-Item -ItemType Directory -Path $directory -Force | Out-Null
|
|
}
|
|
$temporary = Join-Path $directory ('.{0}.{1}.tmp' -f (Split-Path -Leaf $Path), [guid]::NewGuid().ToString('N'))
|
|
try {
|
|
$Value | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $temporary -Encoding utf8NoBOM
|
|
Move-Item -LiteralPath $temporary -Destination $Path -Force
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $temporary) { Remove-Item -LiteralPath $temporary -Force }
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $StatusPath -PathType Leaf)) { throw "Health status not found: $StatusPath" }
|
|
$status = Get-Content -LiteralPath $StatusPath -Raw | ConvertFrom-Json
|
|
if ($status.version -ne 1 -or $status.overall -notin @('healthy', 'degraded')) { throw 'Unsupported health status document' }
|
|
|
|
$now = [DateTimeOffset]::UtcNow
|
|
$previous = $null
|
|
if (Test-Path -LiteralPath $StatePath -PathType Leaf) {
|
|
$previous = Get-Content -LiteralPath $StatePath -Raw | ConvertFrom-Json
|
|
}
|
|
|
|
$kind = $null
|
|
if ($Force) {
|
|
$kind = if ($status.overall -eq 'healthy') { 'recovered' } else { 'degraded' }
|
|
}
|
|
elseif ($status.overall -eq 'degraded') {
|
|
if ($null -eq $previous -or $previous.last_status -ne 'degraded') {
|
|
$kind = 'degraded'
|
|
}
|
|
elseif ($null -ne $previous.last_notification_at_utc) {
|
|
$lastNotification = if ($previous.last_notification_at_utc -is [DateTime]) {
|
|
[DateTimeOffset]$previous.last_notification_at_utc
|
|
}
|
|
else {
|
|
[DateTimeOffset]::Parse([string]$previous.last_notification_at_utc, [Globalization.CultureInfo]::InvariantCulture)
|
|
}
|
|
if (($now - $lastNotification).TotalHours -ge $ReminderHours) { $kind = 'reminder' }
|
|
}
|
|
}
|
|
elseif ($null -ne $previous -and $previous.last_status -eq 'degraded') {
|
|
$kind = 'recovered'
|
|
}
|
|
|
|
$lastNotificationAt = if ($null -ne $previous -and $null -ne $previous.last_notification_at_utc) {
|
|
([DateTimeOffset]$previous.last_notification_at_utc).ToString('o')
|
|
}
|
|
else { $null }
|
|
$lastNotificationKind = if ($null -ne $previous) { $previous.last_notification_kind } else { $null }
|
|
|
|
if ($null -ne $kind) {
|
|
$failedNames = @($status.checks | Where-Object { -not $_.ok } | ForEach-Object { [string]$_.name })
|
|
switch ($kind) {
|
|
'degraded' {
|
|
$title = 'Домовой: требуется внимание'
|
|
$body = if ($failedNames.Count -gt 0) { 'Сбой: ' + ($failedNames -join ', ') } else { 'Мониторинг обнаружил сбой.' }
|
|
$icon = 'Error'
|
|
}
|
|
'reminder' {
|
|
$title = 'Домовой: сбой продолжается'
|
|
$body = if ($failedNames.Count -gt 0) { 'Не исправлено: ' + ($failedNames -join ', ') } else { 'Состояние остается ухудшенным.' }
|
|
$icon = 'Warning'
|
|
}
|
|
default {
|
|
$title = 'Домовой: работа восстановлена'
|
|
$body = 'Все проверки снова проходят успешно.'
|
|
$icon = 'Info'
|
|
}
|
|
}
|
|
|
|
if (-not $NoDisplay) {
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
Add-Type -AssemblyName System.Drawing
|
|
$notification = [System.Windows.Forms.NotifyIcon]::new()
|
|
try {
|
|
$notification.Icon = switch ($icon) {
|
|
'Error' { [System.Drawing.SystemIcons]::Error }
|
|
'Warning' { [System.Drawing.SystemIcons]::Warning }
|
|
default { [System.Drawing.SystemIcons]::Information }
|
|
}
|
|
$notification.Visible = $true
|
|
$notification.BalloonTipIcon = [System.Enum]::Parse([System.Windows.Forms.ToolTipIcon], $icon)
|
|
$notification.BalloonTipTitle = $title
|
|
$notification.BalloonTipText = $body
|
|
$notification.ShowBalloonTip(8000)
|
|
Start-Sleep -Seconds 9
|
|
}
|
|
finally {
|
|
$notification.Dispose()
|
|
}
|
|
}
|
|
|
|
$lastNotificationAt = $now.ToString('o')
|
|
$lastNotificationKind = $kind
|
|
}
|
|
|
|
$state = [ordered]@{
|
|
version = 1
|
|
last_status = [string]$status.overall
|
|
last_observed_at_utc = $now.ToString('o')
|
|
last_notification_at_utc = $lastNotificationAt
|
|
last_notification_kind = $lastNotificationKind
|
|
}
|
|
Write-AtomicJson -Value $state -Path $StatePath
|
|
|
|
$result = if ($null -eq $kind) { 'suppressed' } elseif ($NoDisplay) { 'simulated' } else { 'sent' }
|
|
Write-Output "notification=$result status=$($status.overall) kind=$kind"
|