#!/usr/bin/php -q
<?PHP
/* Copyright 2005-2023, Lime Technology
 * Copyright 2012-2023, Bergware International.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 */
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/Preselect.php";

// Multi-language handling
if (!function_exists('_')) {
  function _($text) {return $text;}
}
// Exit when settings are not yet initialized
if (!file_exists("/var/local/emhttp/var.ini")) exit;

$var   = (array)@parse_ini_file("/var/local/emhttp/var.ini");
$devs  = (array)@parse_ini_file("/var/local/emhttp/devs.ini",true);
$disks = (array)@parse_ini_file("/var/local/emhttp/disks.ini",true);

require_once "$docroot/webGui/include/CustomMerge.php";

extract(parse_plugin_cfg("dynamix",true));

$notify = "$docroot/webGui/scripts/notify";
$ram    = "/var/local/emhttp/monitor.ini";
$rom    = "/boot/config/plugins/dynamix/monitor.ini";
$saved  = @parse_ini_file($ram,true);
$high1  = _var($display,'critical',0);
$high2  = _var($display,'warning',0);
$server = strtoupper(_var($var,'NAME','tower'));
$pools  = pools_filter($disks);
$errors = [];
$top    = 120;

function panelcontrol_monitor_has_show_flag() {
    return true;  
}

function panelcontrol_monitor_state_rank($state) {
  switch ((string)$state) {
    case 'alert': return 5;
    case 'warning': return 4;
    case 'normal-blink': return 3;
    case 'normal': return 2;
    case 'off': return 1;
    default: return 0;
  }
}

function panelcontrol_monitor_push_event(&$events, $event, $scope, $target, $state, $reason, $details=[]) {
  $events[] = [
    'event' => (string)$event,
    'scope' => (string)$scope,
    'target' => (string)$target,
    'state' => (string)$state,
    'reason' => (string)$reason,
    'details' => is_array($details) ? $details : [],
  ];
}

function panelcontrol_monitor_build_payload($var, $disks, $devs, $saved, $display, $server, $high1, $high2, $top) {
  $events = [];

  foreach ((array)$disks as $disk) {
    $name = _var($disk,'name');
    if ($name === 'flash' || substr(_var($disk,'status'),-3) === '_NP') {
      continue;
    }

    $diskName = no_tilde((string)$name);
    $diskDevice = strtolower(trim((string)_var($disk,'device','')));
    $diskId = trim((string)_var($disk,'id',''));
    $temp = _var($disk,'temp','*');
    $spundownRaw = _var($disk,'spundown','');
    $spundownValue = strtolower(trim((string)$spundownRaw));
    $status = strtolower((string)_var($disk,'status',''));
    $isNoDeviceStatus = strpos($status, 'np') !== false;
    $isDisabledStatus = strpos($status, 'dsbl') !== false || strpos($status, 'disabled') !== false;
    if (($diskDevice === '' && $diskId === '') || $isNoDeviceStatus || $isDisabledStatus) {
      continue;
    }
    $target = $diskDevice !== '' ? $diskDevice : $diskId;
    $color = strtolower((string)strtok(_var($disk,'color'),'-'));
    $eventsBeforeDisk = count($events);
    $isSpunDown = in_array($spundownValue, ['1', 'true', 'yes', 'on'], true)
      || strpos($status, 'sby') !== false
      || strpos($status, 'standby') !== false;
    $identity = [
      'disk_name' => $diskName,
      'disk_device' => $diskDevice,
      'disk_id' => $diskId,
      'disk_temp' => is_numeric($temp) ? (int)$temp : null,
      'disk_spundown' => $spundownRaw,
    ];

    if ($color === 'red') {
      panelcontrol_monitor_push_event($events, 'monitor.storage.offline', 'disk', $target, 'alert', 'disk-error-state', [
        'status' => $status,
      ] + $identity);
    } elseif ($color === 'yellow') {
      panelcontrol_monitor_push_event($events, 'monitor.storage.activity', 'disk', $target, 'normal-blink', 'disk-rebuild-or-sync', [
        'status' => $status,
      ] + $identity);
    }

    if ($isSpunDown) {
      panelcontrol_monitor_push_event($events, 'monitor.storage.spindown', 'disk', $target, 'normal', 'disk-standby', [
        'status' => $status,
      ] + $identity);
    }

    if (!$isSpunDown) {
      if (is_numeric($temp)) {
        panelcontrol_monitor_push_event($events, 'monitor.system.temperature', 'disk', $target, 'normal', 'temperature-sample', [
          'temp' => (int)$temp,
        ] + $identity);
      }

      [$hotNVME,$maxNVME] = _var($disk,'transport')=='nvme' ? get_nvme_info(_var($disk,'device'),'temp') : [-1,-1];
      $hot = _var($disk,'hotTemp',-1)>=0 ? $disk['hotTemp'] : ($hotNVME>=0 ? $hotNVME : (_var($disk,'rotational',1)==0 && $display['hotssd']>=0 ? $display['hotssd'] : $display['hot']));
      $max = _var($disk,'maxTemp',-1)>=0 ? $disk['maxTemp'] : ($maxNVME>=0 ? $maxNVME : (_var($disk,'rotational',1)==0 && $display['maxssd']>=0 ? $display['maxssd'] : $display['max']));
      $tempState = exceed($temp,$max,$top) ? 'alert' : (exceed($temp,$hot,$top) ? 'warning' : '');
      if ($tempState !== '') {
        panelcontrol_monitor_push_event($events, 'monitor.system.temperature', 'disk', $target, $tempState, 'temperature-threshold', [
          'temp' => (int)$temp,
          'warning' => (int)$hot,
          'critical' => (int)$max,
        ] + $identity);
      }
    }

    $numErrors = (int)_var($disk,'numErrors',0);
    if ($numErrors > 0) {
      panelcontrol_monitor_push_event($events, 'monitor.storage.health', 'disk', $target, 'alert', 'read-errors', [
        'errors' => $numErrors,
      ] + $identity);
    }

    if (count($events) === $eventsBeforeDisk) {
      panelcontrol_monitor_push_event($events, 'monitor.storage.health', 'disk', $target, 'normal', 'disk-present', [
        'status' => $status,
      ] + $identity);
    }
  }

  foreach ((array)$devs as $dev) {
    $name = _var($dev,'name','no-name');
    $target = 'device:' . no_tilde((string)$name);
    $temp = _var($dev,'temp','*');
    if (!is_numeric($temp)) {
      continue;
    }

    $tempInt = (int)$temp;
    $hot = (int)_var($display,'hot',0);
    $max = (int)_var($display,'max',0);
    $tempState = exceed($tempInt,$max,$top) ? 'alert' : (exceed($tempInt,$hot,$top) ? 'warning' : '');
    if ($tempState !== '') {
      panelcontrol_monitor_push_event($events, 'monitor.system.temperature', 'device', $target, $tempState, 'temperature-threshold', [
        'temp' => $tempInt,
        'warning' => $hot,
        'critical' => $max,
      ]);
    }
  }

  $targets = [];
  $counts = ['off' => 0, 'normal' => 0, 'normal-blink' => 0, 'warning' => 0, 'alert' => 0];
  foreach ($events as $event) {
    $target = (string)$event['target'];
    $state = (string)$event['state'];
    if ($target === '' || $state === '') {
      continue;
    }
    if (!isset($targets[$target]) || panelcontrol_monitor_state_rank($state) > panelcontrol_monitor_state_rank($targets[$target])) {
      $targets[$target] = $state;
    }
  }

  foreach ($targets as $state) {
    if (isset($counts[$state])) {
      $counts[$state]++;
    }
  }

  $payloadSavedState = is_array($saved) ? $saved : [];
  if (isset($payloadSavedState['used'])) {
    unset($payloadSavedState['used']);
  }

  return [
    'schema' => 'panelcontrol.monitor-payload.v1',
    'source' => 'unraid.monitor.copy',
    'generatedAt' => gmdate(DATE_RFC3339),
    'server' => (string)$server,
    'thresholds' => [
      'docker_critical' => (int)$high1,
      'docker_warning' => (int)$high2,
    ],
    'saved_state' => $payloadSavedState,
    'events' => $events,
    'targets' => $targets,
    'summary' => [
      'event_count' => count($events),
      'targets_count' => count($targets),
      'state_counts' => $counts,
    ],
  ];
}

$panelcontrolShow = panelcontrol_monitor_has_show_flag();

function check_temp(&$disk,$text,$info) {
  global $notify,$saved,$server,$display,$top;
  $name  = _var($disk,'name');
  $named = no_tilde($name);
  $temp  = _var($disk,'temp','*');
  [$hotNVME,$maxNVME] = _var($disk,'transport')=='nvme' ? get_nvme_info(_var($disk,'device'),'temp') : [-1,-1];
  $hot   = _var($disk,'hotTemp',-1)>=0 ? $disk['hotTemp'] : ($hotNVME>=0 ? $hotNVME : (_var($disk,'rotational',1)==0 && $display['hotssd']>=0 ? $display['hotssd'] : $display['hot']));
  $max   = _var($disk,'maxTemp',-1)>=0 ? $disk['maxTemp'] : ($maxNVME>=0 ? $maxNVME : (_var($disk,'rotational',1)==0 && $display['maxssd']>=0 ? $display['maxssd'] : $display['max']));
  $warn  = exceed($temp,$max,$top) ? 'alert' : (exceed($temp,$hot,$top) ? 'warning' : false);
  $item  = 'temp';
  $last  = $saved[$item][$named] ?? 0;
  if ($warn) {
    if ($temp>$last) {
      exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text temperature")." -s ".escapeshellarg(ucfirst($warn)." [$server] - $text ".($warn=='alert'?'overheated (':'is hot (').my_temp($temp).")")." -d ".escapeshellarg("$info")." -i \"$warn\" 2>/dev/null");
      $saved[$item][$named] = max($max,$temp);
    }
  } else {
    if ($last && $temp<=$top) {
      exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text message")." -s ".escapeshellarg("Notice [$server] - $text returned to normal temperature")." -d ".escapeshellarg("$info")." 2>/dev/null");
      unset($saved[$item][$named]);
    }
  }
}
function check_smart(&$disk,$port,$text,$info) {
  global $notify,$saved,$server,$numbers;
  $name   = _var($disk,'name');
  $named  = no_tilde($name);
  $select = get_value($disk,'smSelect',0);
  $level  = get_value($disk,'smLevel',1);
  $events = explode('|',get_value($disk,'smEvents',$numbers));
  $type   = get_value($disk,'smType','');
  get_ctlr_options($type, $disk);
  $file = "/var/local/emhttp/smart/$name";
  exec("awk 'NR>7{print $1,$2,$4,$6,$9,$10}' ".escapeshellarg($file)." 2>/dev/null", $codes);
  $item = 'smart';
  foreach ($codes as $code) {
    if (!$code || !is_numeric($code[0])) continue;
    [$id,$class,$value,$thres,$when,$raw] = my_explode(' ',$code,7);
    $fail = strpos($when,'FAILING_NOW')!==false;
    if (!$fail && !in_array($id,$events)) continue;
    $word = str_replace(['_',' (-)'],[' ',''],strtolower("$class ($when)"));
    $ack = "$named.ack";
    switch ($select) {
    case 0:
      $attr = "$named.$id";
      $last = ($saved[$item][$attr] ?? 0)*$level;
      if ($raw>0 || $fail) {
        if ($raw>$last) {
          exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text SMART health [$id]")." -s ".escapeshellarg("Warning [$server] - $word is $raw")." -d ".escapeshellarg("$info")." -i \"warning\" 2>/dev/null");
          $saved[$item][$attr] = $raw;
          unset($saved[$item][$ack]);
        }
      } else {
        if ($last>0) {
          exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text SMART message [$id]")." -s ".escapeshellarg("Notice [$server] - $word returned to normal value")." -d ".escapeshellarg("$info")." 2>/dev/null");
          unset($saved[$item][$attr]);
          unset($saved[$item][$ack]);
        }
      }
      break;
    case 1:
      $attr = "$named.{$id}n";
      $last = $saved[$item][$attr] ?? 255;
      if (($thres>0 && $value<=$thres*$level) || $fail) {
        if ($value*($value>$thres?$level:1)<$last) {
          exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text SMART health [$id]")." -s ".escapeshellarg("Warning [$server] - $word is $value")." -d ".escapeshellarg("$info")." -i \"warning\" 2>/dev/null");
          $saved[$item][$attr] = $value;
          unset($saved[$item][$ack]);
        }
      } else {
        if ($last<255) {
          exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text SMART message [$id]")." -s ".escapeshellarg("Notice [$server] - $word returned to normal value")." -d ".escapeshellarg("$info")." 2>/dev/null");
          unset($saved[$item][$attr]);
          unset($saved[$item][$ack]);
        }
      }
      break;
    }
  }
}
function check_usage(&$disk,$used,$text,$info) {
  global $notify,$saved,$server,$display;
  if ($used == -1) return;
  $name     = _var($disk,'name');
  $named    = no_tilde($name);
  $critical = _var($disk,'critical',-1)>=0 ? $disk['critical'] : $display['critical'];
  $warning  = _var($disk,'warning',-1)>=0 ? $disk['warning'] : $display['warning'];
  $warn     = exceed($used,$critical) ? 'alert' : (exceed($used,$warning) ? 'warning' : false);
  $item     = 'used';
  $last     = $saved[$item][$named] ?? 0;
  if ($warn) {
    if ($used>$last) {
      exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text disk utilization")." -s ".escapeshellarg(ucfirst($warn)." [$server] - $text is ".($warn=='alert'?'low on space':'high on usage')." ({$used}%)")." -d ".escapeshellarg("$info")." -i \"$warn\" 2>/dev/null");
      $saved[$item][$named] = max($critical,$used);
    }
  } else {
    if ($last && $used<=100) {
      exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text message")." -s ".escapeshellarg("Notice [$server] - $text returned to normal utilization level")." -d ".escapeshellarg("$info")." 2>/dev/null");
      unset($saved[$item][$named]);
    }
  }
}

// check array devices
foreach ($disks as $disk) {
  $name   = _var($disk,'name');
  if ($name=='flash' || substr(_var($disk,'status'),-3)=='_NP') continue;
  $named  = no_tilde($name);
  $text   = my_disk($name).(in_array($name,$pools)||$name=='parity'?' disk':'');
  $device = _var($disk,'device');
  $info   = !empty($disk['id']) ? "{$disk['id']} ($device)" : "No device identification ($device)";
// process disk temperature notifications
  check_temp($disk,$text,$info);
// process disk SMART notifications
  check_smart($disk,port_name($disk['smDevice'] ?? $device),$text,$info);
// process disk usage notifications
  check_usage($disk,_var($disk,'fsSize',0)>0?100-round(100*_var($disk,'fsFree',0)/$disk['fsSize']):-1,$text,$info);
// process disk operation notifications
  $warn = strtok(_var($disk,'color'),'-');
  $item = 'disk';
  $last = $saved[$item][$named] ?? '';
  switch ($warn) {
  case 'red':
    if ($warn!=$last) {
      if (_var($var,'fsState')!='Stopped') {
        $status = strtolower(str_replace(['NP_','_'],['',' '],_var($disk,'status')));
        exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text error")." -s ".escapeshellarg("Alert [$server] - $text in error state ($status)")." -d ".escapeshellarg("$info")." -i \"alert\" 2>/dev/null");
      }
      $saved[$item][$named] = $warn;
    }
  break;
  case 'yellow':
    if ($warn!=$last) {
      if (_var($var,'fsState')!='Stopped') {
        $status = $name=='parity' ? "parity-sync in progress" : " is being reconstructed and is available for normal operation";
        exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text message")." -s ".escapeshellarg("Notice [$server] - $text, $status")." -d ".escapeshellarg("$info")." 2>/dev/null");
      }
      $saved[$item][$named] = $warn;
    }
  break;
  default:
    if ($last) {
      if (_var($var,'fsState')!='Stopped') {
        exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text message")." -s ".escapeshellarg("Notice [$server] - $text returned to normal operation")." -d ".escapeshellarg("$info")." 2>/dev/null");
      }
      unset($saved[$item][$named]);
    }
  break;}
// count disk errors
  if (_var($disk,'numErrors',0)>0) $errors[] = "$text - $info (errors {$disk['numErrors']})";
// check file system of cache pool
  $item = 'pool';
  if (in_array($name,$pools) && strpos(_var($disk,'fsType'),'btrfs')!==false && _var($disk,'uuid')!=="") {
    $attr = 'missing';
    if (exec("/sbin/btrfs filesystem show "._var($disk,'uuid')." 2>/dev/null|grep -c 'missing'")>0) {
      if (empty($saved[$item][$attr])) {
        exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text message")." -s ".escapeshellarg("Warning [$server] - Cache pool BTRFS missing device(s)")." -d ".escapeshellarg("$info")." -i \"warning\" 2>/dev/null");
        $saved[$item][$attr] = 1;
      }
    } elseif (isset($saved[$item][$attr])) unset($saved[$item][$attr]);
    $attr = "profile-$named";
    if (exec("/sbin/btrfs filesystem df /mnt/$name 2>/dev/null|grep -c '^Data'")>1) {
      if (empty($saved[$item][$attr])) {
        exec("$notify -l '/Main' -e ".escapeshellarg("Unraid $text message")." -s ".escapeshellarg("Warning [$server] - $pool pool BTRFS too many profiles (You can ignore this warning when a pool balance operation is in progress)")." -d ".escapeshellarg("$info")." -i \"warning\" 2>/dev/null");
        $saved[$item][$attr] = 1;
      }
    } elseif (isset($saved[$item][$attr])) unset($saved[$item][$attr]);
  }
}

// check unassigned devices
foreach ($devs as $dev) {
  $name = _var($dev,'name','no name');
  $id   = _var($dev,'id');
  $port = port_name($name);
  $text = "device $name";
  $info = !empty($id) ? "$id ($name)": "No device identification ($name)";
// process disk temperature notifications
  check_temp($dev,$text,$info);
// process disk SMART notifications
  check_smart($dev,$port,$text,$info);
}

// report array read errors
$item = 'array';
$name = 'errors';
$last = $saved[$item][$name] ?? 0;
$warn = count($errors);
$info = "Array has $warn disk".($warn==1 ? "" : "s")." with read errors";
if ($warn>0) {
  if ($warn<>$last) {
    $message = implode('\n', $errors);
    exec("$notify -l '/Main' -e \"Unraid array errors\" -s ".escapeshellarg("Warning [$server] - array has errors")." -d ".escapeshellarg("$info")." -m ".escapeshellarg("$message")." -i \"warning\" 2>/dev/null");
    $saved[$item][$name] = $warn;
  }
} else {
  if ($last) {
    exec("$notify -l '/Main' -e \"Unraid array errors\" -s ".escapeshellarg("Notice [$server] - array turned good")." -d ".escapeshellarg("$info")." 2>/dev/null");
    unset($saved[$item][$name]);
  }
}


/* Process parity check, parity sync, and data-rebuild notifications */
$name = 'parity';
$last = $saved[$item][$name] ?? '';

if ($var['mdResyncPos']) {
	if (!$last) {
		$action = preg_split('/\s+/', $var['mdResyncAction']);
		switch ($action[0]) {
			case 'recon':
				$last = $action[1] == 'P' ? 'Parity-Sync' : 'Data-Rebuild';
				break;
			case 'check':
				$last = count($action) > 1 ? 'Parity-Check' : 'Read-Check';
				break;
			case 'clear':
				$last = 'Disk-Clear';
				break;
			default:
				$last = '';
		}
		$info = "Size: " . my_scale($var['mdResyncSize'] * 1024, $unit) . " $unit";
		exec("$notify -l '/Main' -e " . escapeshellarg("Unraid $last") . " -s " . escapeshellarg("Notice [$server] - $last started") . " -d " . escapeshellarg("$info") . " -i \"warning\" 2>/dev/null");
		$saved[$item][$name] = $last;
	}
} else {
	if ($last) {
		/* File for the latest parity check. */
		$resync	= '/var/tmp/resync.ini';

		if (file_exists($resync)) {
			list($duration, $speed, $status, $error, $action, $size) = last_parity_check();
		} else {
			list($date, $duration, $speed, $status, $error, $action, $size) = last_parity_log();
		}

		$info = ($status == 0) ? "Duration: " . my_check($duration, $speed) : ($status == -4 ? "Canceled" : "Error code: $status");
		$level = ($status == 0 && $var['sbSyncErrs'] == 0) ? "normal" : "warning";
		exec("$notify -l '/Main' -e " . escapeshellarg("Unraid $last") . " -s " . escapeshellarg("Notice [$server] - $last finished ($error errors)") . " -d " . escapeshellarg("$info") . " -i \"$level\" 2>/dev/null");

		unset($saved[$item][$name]);
	}
}

// check read-write status of Boot drive
$name = 'flash';
$last = $saved[$item][$name] ?? '';
$warn = exec("awk '$2==\"/boot\" && $4 ~ /(^|,)rw(,|$)/ {print \"rw\"}' /proc/mounts");
$info = "{$disks['flash']['id']} ({$disks['flash']['device']})";
if ($warn!="rw") {
  if ($warn!=$last) {
    exec("$notify -l '/Main' -e \"Boot drive failure\" -s ".escapeshellarg("Alert [$server] - Boot drive is not read-write")." -d ".escapeshellarg("$info")." -i \"alert\" 2>/dev/null");
    $saved[$item][$name] = $warn;
  }
} else {
  if ($last) {
    exec("$notify -l '/Main' -e \"Boot drive operation\" -s ".escapeshellarg("Notice [$server] - Boot drive returned to normal operation")." -d ".escapeshellarg("$info")." 2>/dev/null");
    unset($saved[$item][$name]);
  }
}

// check pool status
$item = 'poolsstatus';
$poolsJson = json_decode(storagePoolsJson(), true);
if (isset($poolsJson['pools']) && is_array($poolsJson['pools'])) {
  foreach ($poolsJson['pools'] as $poolName => $pool) {
    $attr = $poolName;
    $lastStatus = $saved[$item][$attr] ?? 'UNKNOWN';
    $currentStatus = _var($pool, 'overall_status', 'UNKNOWN');
    $fstype = _var($pool, 'fstype', 'unknown');
    
    // Check if status changed
    if ($currentStatus !== $lastStatus) {
      $mountpoint = _var($pool, 'mountpoint', 'unknown');
      $info = "Pool: $poolName ($fstype) - Mountpoint: $mountpoint";
      
      if ($currentStatus === 'DEGRADED' || $currentStatus === 'FAULTED') {
        // Status degraded or faulted - send alert
        exec("$notify -l '/Main' -e ".escapeshellarg("Unraid pool status")." -s ".escapeshellarg("Alert [$server] - Pool $poolName is $currentStatus")." -d ".escapeshellarg("$info")." -i \"alert\" 2>/dev/null");
        $saved[$item][$attr] = $currentStatus;
      } elseif ($currentStatus === 'ONLINE - ERRORS') {
        // Status online with errors - send warning
        exec("$notify -l '/Main' -e ".escapeshellarg("Unraid pool status")." -s ".escapeshellarg("Warning [$server] - Pool $poolName has errors")." -d ".escapeshellarg("$info")." -i \"warning\" 2>/dev/null");
        $saved[$item][$attr] = $currentStatus;
      } elseif ($currentStatus === 'ONLINE') {
        // Status recovered to good - send notice
        if ($lastStatus !== 'UNKNOWN') {
          exec("$notify -l '/Main' -e ".escapeshellarg("Unraid pool status")." -s ".escapeshellarg("Notice [$server] - Pool $poolName recovered to $currentStatus")." -d ".escapeshellarg("$info")." 2>/dev/null");
        }
        $saved[$item][$attr] = $currentStatus;
      } else {
        // Unknown status - still track it
        $saved[$item][$attr] = $currentStatus;
      }
    }
  }
}

// check docker image disk utilization
system('mountpoint -q /var/lib/docker', $retval);
if ($retval===0 && exec("df /var/lib/docker|grep -Po '^/dev/\Kloop'")) {
  $item = 'system';
  $name = 'docker';
  $last = $saved[$item][$name] ?? '';
  if (file_exists("/boot/config/docker.cfg")) {
    $cfg  = parse_ini_file("/boot/config/docker.cfg");
    $info = "Docker utilization of image file {$cfg['DOCKER_IMAGE_FILE']}";
  } else
    $info = "Docker image file not specified";

  $warn = exec("df /var/lib/docker|awk '/^\//{print $5*1}'");
  if ($warn>=$high1 && $high1>0) {
    if ($warn>$last) {
      exec("$notify -l '/Docker' -e \"Docker critical image disk utilization\" -s ".escapeshellarg("Alert [$server] - Docker image disk utilization of {$warn}%")." -d ".escapeshellarg("$info")." -i \"alert\" 2>/dev/null");
      $saved[$item][$name] = $warn;
    }
  } elseif ($warn>=$high2 && $high2>0) {
    if ($warn>$last) {
      exec("$notify -l '/Docker' -e \"Docker high image disk utilization\" -s ".escapeshellarg("Warning [$server] - Docker image disk utilization of {$warn}%")." -d ".escapeshellarg("$info")." -i \"warning\" 2>/dev/null");
      $saved[$item][$name] = $warn;
    }
  } else {
    if ($last) {
      exec("$notify -l '/Docker' -e \"Docker image disk utilization\" -s ".escapeshellarg("Notice [$server] - Docker image disk utilization returned to normal level")." -d ".escapeshellarg("$info")." 2>/dev/null");
      unset($saved[$item][$name]);
    }
  }
}

// save new status
if ($saved) {
  $text = '';
  foreach ($saved as $item => $block) {
    if ($block) $text .= "[$item]\n";
    foreach ($block as $key => $value) $text .= no_tilde($key)."=\"$value\"\n";
  }
  if ($text) {
    if ($text != @file_get_contents($ram)) file_put_contents($ram, $text);
    if (!file_exists($rom) || exec("diff -q $ram $rom")) file_put_contents($rom, $text);
  } else {
    delete_file($ram,$rom);
  }
}

if ($panelcontrolShow) {
  $payload = panelcontrol_monitor_build_payload($var, $disks, $devs, $saved, $display, $server, $high1, $high2, $top);
  $payloadPath = '/usr/local/emhttp/state/panelcontrol-monitor-payload.json';
  $payloadJson = json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  if ($payloadJson === false) {
    my_logger('Failed to encode panelcontrol monitor payload: '.json_last_error_msg(), 'webgui');
  } else {
    $payloadJson .= "\n";
    $existingPayloadJson = @file_get_contents($payloadPath);
    if ($existingPayloadJson !== $payloadJson) {
      $tmpPath = $payloadPath . '.tmp';
      if (@file_put_contents($tmpPath, $payloadJson, LOCK_EX) !== false) {
        if (!@rename($tmpPath, $payloadPath)) {
          @file_put_contents($payloadPath, $payloadJson, LOCK_EX);
          @unlink($tmpPath);
        }
      } else {
        @file_put_contents($payloadPath, $payloadJson, LOCK_EX);
      }
    }
  }
}

exit(0);
?>
