<?phpnamespace App\Form;use App\Gui\IconBox;use App\Gui\Icons;use App\Model\Chain;use App\Model\Connection;use App\Model\LogEntry;use App\Model\Session;use App\Xapi\Perfmon;use JumPHP\Gui\Button;use JumPHP\Gui\CheckBox;use JumPHP\Gui\Container;use JumPHP\Gui\Form;use JumPHP\Gui\Label;use JumPHP\Gui\TextBox;/** * The Alerts page of XenCenter's Properties: the performance alarms of a VM (CPU, network, disks), a server (CPU, free memory, * network) or a storage repository (space used, throughput), written as XAPI's perfmon reads them (other_config "perfmon", see * Xapi\Perfmon); for a pool, the e-mail the master sends for each alert (other_config mail-destination, ssmtp-mailhub). Closes * with ['alerts' => [...]], apply() sends it. * * @property IconBox $icon * @property Label $heading * @property Label $where * @property Label $intro * @property Container $cpuRow * @property CheckBox $cpuOn * @property TextBox $cpuLevel * @property TextBox $cpuMinutes * @property Container $memRow * @property CheckBox $memOn * @property TextBox $memLevel * @property TextBox $memMinutes * @property Container $netRow * @property CheckBox $netOn * @property TextBox $netLevel * @property TextBox $netMinutes * @property Container $diskRow * @property CheckBox $diskOn * @property TextBox $diskLevel * @property TextBox $diskMinutes * @property Container $srUseRow * @property CheckBox $srUseOn * @property TextBox $srUseLevel * @property TextBox $srUseMinutes * @property Container $srIoRow * @property CheckBox $srIoOn * @property TextBox $srIoLevel * @property TextBox $srIoMinutes * @property Container $inhibitRow * @property TextBox $inhibit * @property Container $mailPanel * @property CheckBox $mailOn * @property TextBox $mailTo * @property TextBox $mailHub * @property TextBox $mailPort * @property Label $error * @property Button $ok * @property Button $cancel */final class AlertsForm extends Form{ /** row of the form => [perfmon variable, factor from what is typed to perfmon's unit, the kinds it is offered for] */ public const ROWS = [ 'cpu' => ['cpu_usage', 0.01, ['vm', 'host']], // % -> ratio 'mem' => ['memory_free_kib', 1024.0, ['host']], // MB -> KiB 'net' => ['network_usage', 1024.0, ['vm', 'host']], // KB/s -> B/s 'disk' => ['disk_usage', 1024.0, ['vm']], // KB/s -> B/s 'srUse' => ['physical_utilisation', 0.01, ['sr']], // % -> ratio 'srIo' => ['sr_io_throughput_total_per_host', 1.0, ['sr']], // MB/s -> MiB/s ]; private array $params = []; public function __construct(private Session $session) { parent::__construct(); } /** XAPI's answers, events and the console go on while this dialog is open. */ protected function tick(float $dt): void { $this->session->tick($dt); } /** @return array<string, mixed> */ public static function paramsFor(Connection $c, string $ref): array { $r = $c->cache->get($ref) ?? []; $class = (string)$c->cache->classOf($ref); $other = (array)($r['other_config'] ?? []); return ['target' => $ref, 'class' => $class, 'name' => $c->cache->name($ref), 'server' => $c->name(), 'icon' => Icons::of($class, $r, $c->cache), 'alarms' => Perfmon::parse((string)($other['perfmon'] ?? '')), 'mailTo' => (string)($other['mail-destination'] ?? ''), 'mailHub' => (string)($other['ssmtp-mailhub'] ?? '')]; } protected function loaded(array $params = []): void { $this->params = $params; $class = (string)($params['class'] ?? 'vm'); $this->heading->text = "Alerts of '" . ($params['name'] ?? '') . "'"; $this->where->text = "In '" . ($params['server'] ?? '') . "'"; $this->icon->kind = (string)($params['icon'] ?? 'vm'); $alarms = (array)($params['alarms'] ?? []); $inhibit = 3600; foreach (self::ROWS as $row => [$variable, $factor, $kinds]) { /** @var Container $container */ $container = $this->widget($row . 'Row'); $container->visible = in_array($class, $kinds, true); /** @var CheckBox $on */ $on = $this->widget($row . 'On'); $a = $alarms[$variable] ?? null; $on->checked = $a !== null; if ($a !== null) { /** @var TextBox $level */ $level = $this->widget($row . 'Level'); /** @var TextBox $minutes */ $minutes = $this->widget($row . 'Minutes'); $level->text = self::number($a['level'] / $factor); $minutes->text = self::number($a['period'] / 60); $inhibit = $a['inhibit']; } } $this->inhibit->text = self::number($inhibit / 60); $pool = $class === 'pool'; $this->inhibitRow->visible = !$pool; $this->intro->text = $pool ? 'The pool master can mail every alert of the pool (its own SMTP client, ssmtp).' : 'An alert is raised when a figure stays past its level for the time given.'; $this->mailPanel->visible = $pool; [$hub, $port] = explode(':', (string)($params['mailHub'] ?? '') . ':25'); $this->mailOn->checked = ($params['mailTo'] ?? '') !== ''; $this->mailTo->text = (string)($params['mailTo'] ?? ''); $this->mailHub->text = $hub; $this->mailPort->text = $port !== '' ? $port : '25'; $this->error->text = ''; } public function onOkClick(Button $sender): void { $class = (string)($this->params['class'] ?? 'vm'); $alarms = []; $inhibit = (float)str_replace(',', '.', $this->inhibit->text); foreach (self::ROWS as $row => [$variable, $factor, $kinds]) { /** @var CheckBox $on */ $on = $this->widget($row . 'On'); if (!in_array($class, $kinds, true) || !$on->checked) { continue; } /** @var TextBox $level */ $level = $this->widget($row . 'Level'); /** @var TextBox $minutes */ $minutes = $this->widget($row . 'Minutes'); $value = (float)str_replace(',', '.', $level->text); $period = (float)str_replace(',', '.', $minutes->text); if (!is_numeric(str_replace(',', '.', trim($level->text))) || $value < 0 || ($factor === 0.01 && $value > 100)) { $this->error->text = 'Enter a level from 0' . ($factor === 0.01 ? ' to 100 %.' : '.'); return; } if ($period < 1 || $inhibit < 0) { $this->error->text = 'An alert needs at least 1 minute.'; return; } $alarms[$variable] = ['level' => $value * $factor, 'period' => (int)round($period * 60), 'inhibit' => (int)round($inhibit * 60)]; } $mail = null; if ($class === 'pool') { $mail = ['to' => '', 'hub' => '']; if ($this->mailOn->checked) { if (!\App\Model\Address::isEmail(trim($this->mailTo->text)) || trim($this->mailHub->text) === '') { $this->error->text = 'Enter the address to send to and the SMTP server.'; return; } $port = (int)$this->mailPort->text ?: 25; $mail = ['to' => trim($this->mailTo->text), 'hub' => trim($this->mailHub->text) . ($port !== 25 ? ":$port" : '')]; } } $this->close(['alerts' => ['target' => $this->params['target'] ?? '', 'class' => $class, 'name' => $this->params['name'] ?? '', 'alarms' => $alarms, 'mail' => $mail]]); } public function onCancelClick(Button $sender): void { $this->close(); } /** * Sends it: perfmon's key removed then added again (XAPI refuses add_to_other_config on a key that exists), the pool's mail keys * the same way. @param array<string, mixed> $p */ public static function apply(Session $session, array $p): void { $ref = (string)$p['target']; $c = $session->connectionOf($ref); if ($c === null) { return; } $class = match ((string)$p['class']) { 'vm' => 'VM', 'sr' => 'SR', default => (string)$p['class']}; $other = (array)$c->cache->field($ref, 'other_config', []); $steps = []; $set = function (string $key, string $value) use (&$steps, $c, $class, $ref, $other): void { if (($other[$key] ?? null) === $value || ($value === '' && !isset($other[$key]))) { return; } if (isset($other[$key])) { $steps[] = fn () => $c->call("$class.remove_from_other_config", $ref, $key); } if ($value !== '') { $steps[] = fn () => $c->call("$class.add_to_other_config", $ref, $key, $value); } }; if ($p['class'] === 'pool') { $set('mail-destination', (string)($p['mail']['to'] ?? '')); $set('ssmtp-mailhub', (string)($p['mail']['hub'] ?? '')); } else { $set('perfmon', $p['alarms'] === [] ? '' : Perfmon::build($p['alarms'])); } if ($steps === []) { return; } $entry = $session->log("Changing the alerts of '" . $p['name'] . "'", $c->name(), $ref, LogEntry::RUNNING); Chain::run($steps)->then(function () use ($entry, $session): void { $entry->status = LogEntry::DONE; $entry->progress = 1; $entry->finished = $session->clock; }, function (\Throwable $e) use ($entry, $session): void { $entry->status = LogEntry::FAILED; $entry->detail = $e->getMessage(); $entry->finished = $session->clock; }); } private static function number(float $x): string { return rtrim(rtrim(sprintf('%.2F', $x), '0'), '.') ?: '0'; }}