ES
games/xencenter-xenadmin-xcp-jumphp / src / Form / AlertsForm.php · commit fb1b7c9
<?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';    }}