From 01f2f697f39a0a060f6d44bc0357c2b7a30b6e14 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 24 Jul 2023 13:59:19 +0200 Subject: [PATCH] Render pending migration overview as child of `Status` nav --- application/controllers/StatusController.php | 154 +++++++++++++++++- library/Icinga/Web/Navigation/ConfigMenu.php | 6 +- .../Renderer/StatusNavigationRenderer.php | 9 +- 3 files changed, 165 insertions(+), 4 deletions(-) diff --git a/application/controllers/StatusController.php b/application/controllers/StatusController.php index b93eb11032..3db0ab1a0c 100644 --- a/application/controllers/StatusController.php +++ b/application/controllers/StatusController.php @@ -4,12 +4,22 @@ namespace Icinga\Controllers; +use Icinga\Application\Hook; use Icinga\Application\Hook\HealthHook; +use Icinga\Application\Hook\MigrationHook; +use Icinga\Web\Notification; use Icinga\Web\View\AppHealth; +use Icinga\Web\Widget\Migrations; use Icinga\Web\Widget\Tabextension\OutputFormat; +use ipl\Html\Form; use ipl\Html\Html; +use ipl\Html\HtmlElement; use ipl\Html\HtmlString; +use ipl\Html\Text; use ipl\Web\Compat\CompatController; +use ipl\Web\Url; +use ipl\Web\Widget\ActionLink; +use ipl\Web\Widget\StateBadge; class StatusController extends CompatController { @@ -46,11 +56,127 @@ public function indexAction() ])); $this->addControl(HtmlString::create((string) $this->view->filterEditor)); - $this->addTitleTab($this->translate('Health')); + $this->initTabs(); + $this->getTabs()->activate('health'); + $this->setAutorefreshInterval(10); $this->addContent(new AppHealth($query)); } + public function migrationListAction() + { + $migrations = MigrationHook::collectMigrations() + ->select(); + + $this->setupSortControl( + [ + 'name' => $this->translate('Name'), + 'module' => $this->translate('Module'), + 'count' => $this->translate('Migration Counts'), + ], + $migrations, + ['name' => 'DESC'] + ); + $this->setupLimitControl(); + $this->setupPaginationControl($migrations); + $this->setupFilterControl($migrations, [ + 'name' => $this->translate('Name'), + 'state' => $this->translate('State'), + ], ['name']); + + $this->addControl(HtmlString::create((string) $this->view->paginator)); + $this->addControl(HtmlElement::create('div', ['class' => 'sort-controls-container'], [ + HtmlString::create((string) $this->view->limiter), + HtmlString::create((string) $this->view->sortBox) + ])); + $this->addControl(HtmlString::create((string) $this->view->filterEditor)); + + $this->controls->getAttributes()->add('class', 'separated'); + + $this->initTabs(); + $this->getTabs()->activate('migrations'); + $this->getTabs()->disableLegacyExtensions(); + + $this->setAutorefreshInterval(30); + $this->addContent(new Migrations($migrations, function (Form $form) { + $form->on(Form::ON_SUCCESS, function (Form $form) { + $this->assertPermission('application/migrations'); + + $name = $form->getValue(MigrationHook::MIGRATE_PARAM); + if ($name !== MigrationHook::ALL_MODULES) { + $migration = MigrationHook::getMigration($name); + if ($migration->apply()) { + Notification::success($this->translate('Applied pending migrations successfully')); + } else { + Notification::error( + $this->translate('Failed to apply pending migration(s). See logs for details') + ); + } + } else { + $succeeded = true; + /** @var MigrationHook $hook */ + foreach (Hook::all('migration') as $hook) { + if (! $hook->isModule()) { + continue; + } + + if (! $hook->apply() && $succeeded) { + $succeeded = false; + } + } + + if (! $succeeded) { + Notification::error($this->translate( + 'Applied migrations successfully. Though, one or more migration hooks failed to run.' + . ' See logs for details' + )); + } else { + Notification::success($this->translate('Applied all migrations successfully')); + } + } + + $this->redirectNow(Url::fromRequest()); + }) + ->handleRequest($this->getServerRequest()); + })); + } + + public function migrationHintAction() + { + $name = $this->getRequest()->getParam(MigrationHook::MIGRATE_PARAM); + $hook = MigrationHook::getMigration($name); + + $hint = HtmlElement::create('div', ['class' => 'pending-migrations-hint']); + $hint->addHtml(HtmlElement::create('h2', null, Text::create($this->translate('Error!')))); + $hint->addHtml( + HtmlElement::create( + 'p', + null, + Text::create( + sprintf( + $this->translatePlural( + '%s has %d pending migration.', + '%s has %d pending migrations.', + $hook->count() + ), + $hook->getName(), + $hook->count() + ) + ) + ) + ); + + $hint->addHtml( + HtmlElement::create('p', null, Text::create($this->translate('Please apply the migrations first.'))), + new ActionLink($this->translate('View pending Migrations'), Url::fromPath('status/migration-list')) + ); + + $this->addTitleTab($this->translate('Error')); + $this->getTabs()->disableLegacyExtensions(); + + $this->addContent($hint); + } + protected function handleFormatRequest($query) { $formatJson = $this->params->get('format') === 'json'; @@ -63,4 +189,30 @@ protected function handleFormatRequest($query) ->setSuccessData($query->fetchAll()) ->sendResponse(); } + + protected function initTabs() + { + [$state, $_, $healthCount] = HealthHook::getHealthCount(); + $healthTab = [ + 'label' => $this->translate('Health'), + 'url' => Url::fromPath('status'), + ]; + if ($state) { + $healthTab['stateBadge'] = new StateBadge($healthCount, $state); + } + + $migrationsTab = [ + 'label' => $this->translate('Migrations'), + 'url' => Url::fromPath('status/migration-list'), + ]; + + $migrationCount = MigrationHook::countAll(); + if ($migrationCount > 0) { + $migrationsTab['stateBadge'] = new StateBadge($migrationCount, 'warning'); + } + + $this->getTabs() + ->add('health', $healthTab) + ->add('migrations', $migrationsTab); + } } diff --git a/library/Icinga/Web/Navigation/ConfigMenu.php b/library/Icinga/Web/Navigation/ConfigMenu.php index 3588fe38d7..e102c275d1 100644 --- a/library/Icinga/Web/Navigation/ConfigMenu.php +++ b/library/Icinga/Web/Navigation/ConfigMenu.php @@ -187,8 +187,10 @@ protected function createHealthBadge() [$state, $_, $healthCount] = HealthHook::getHealthCount(); $this->state = $state; - if ($healthCount > 0) { - $stateBadge = new StateBadge($healthCount, $this->state); + $migrationsCount = MigrationHook::countAll(); + $total = $healthCount + $migrationsCount; + if ($total > 0) { + $stateBadge = new StateBadge($total, $healthCount > 0 ? $this->state : static::STATE_WARNING); $stateBadge->addAttributes(['class' => 'disabled']); } diff --git a/library/Icinga/Web/Navigation/Renderer/StatusNavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/StatusNavigationRenderer.php index 39306794b1..c68becfd76 100644 --- a/library/Icinga/Web/Navigation/Renderer/StatusNavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/StatusNavigationRenderer.php @@ -4,6 +4,7 @@ namespace Icinga\Web\Navigation\Renderer; use Icinga\Application\Hook\HealthHook; +use Icinga\Application\Hook\MigrationHook; class StatusNavigationRenderer extends BadgeNavigationItemRenderer { @@ -14,6 +15,12 @@ public function getCount() $this->title = $title; $this->state = $state; - return $count; + $migrationCount = MigrationHook::countAll(); + if ($count === 0 && $migrationCount > 0) { + $this->state = static::STATE_WARNING; + $this->title = t('Pending migrations'); + } + + return $count + $migrationCount; } }