From 372ffa15c0ab33262e8f8a3b396509fb415d5df3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 13 Apr 2022 19:38:25 +0200 Subject: [PATCH] Allow browsing predefined dashlets when creating a new dashlet --- .../controllers/DashboardsController.php | 14 +- .../forms/Dashboard/BaseDashboardForm.php | 21 +- .../forms/Dashboard/BaseSetupDashboard.php | 271 ++++++++++++++++++ application/forms/Dashboard/DashletForm.php | 253 +++++++++------- application/forms/Dashboard/WelcomeForm.php | 1 + .../Web/Dashboard/Common/DashboardManager.php | 5 +- .../Dashboard/ItemList/DashletListItem.php | 8 +- .../Web/Dashboard/ItemList/EmptyDashlet.php | 40 +++ library/Icinga/Web/Dashboard/Pane.php | 13 +- .../Web/Dashboard/Setup/SetupNewDashboard.php | 229 ++------------- 10 files changed, 519 insertions(+), 336 deletions(-) create mode 100644 application/forms/Dashboard/BaseSetupDashboard.php create mode 100644 library/Icinga/Web/Dashboard/ItemList/EmptyDashlet.php diff --git a/application/controllers/DashboardsController.php b/application/controllers/DashboardsController.php index fc42fc06d..70f97ecce 100644 --- a/application/controllers/DashboardsController.php +++ b/application/controllers/DashboardsController.php @@ -11,7 +11,6 @@ use Icinga\Forms\Dashboard\NewHomePaneForm; use Icinga\Forms\Dashboard\RemoveDashletForm; use Icinga\Forms\Dashboard\RemoveHomePaneForm; use Icinga\Forms\Dashboard\WelcomeForm; -use Icinga\Model\ModuleDashlet; use Icinga\Util\Json; use Icinga\Web\Dashboard\Dashboard; use Icinga\Web\Dashboard\DashboardHome; @@ -219,7 +218,15 @@ class DashboardsController extends CompatController public function newDashletAction() { - $this->setTitle(t('Add Dashlet To Dashboard')); + if (isset($this->getRequest()->getPost()['btn_next'])) { + // Set compact view to prevent the controls from being + // rendered in the modal view when redirecting + $this->view->compact = true; + + $this->setTitle(t('Add Dashlet To Dashboard')); + } else { + $this->setTitle(t('Select Dashlets')); + } $dashletForm = new DashletForm($this->dashboard); $dashletForm->populate($this->getRequest()->getPost()); @@ -388,10 +395,7 @@ class DashboardsController extends CompatController $this->setTitle(t('Add Dashlet')); } - $query = ModuleDashlet::on(Dashboard::getConn()); - $setupForm = new SetupNewDashboard($this->dashboard); - $setupForm->initDashlets(Dashboard::getModuleDashlets($query)); $setupForm->on(SetupNewDashboard::ON_SUCCESS, function () use ($setupForm) { $this->redirectNow($setupForm->getRedirectUrl()); })->handleRequest(ServerRequest::fromGlobals()); diff --git a/application/forms/Dashboard/BaseDashboardForm.php b/application/forms/Dashboard/BaseDashboardForm.php index f08fd1bdf..0bd1ab987 100644 --- a/application/forms/Dashboard/BaseDashboardForm.php +++ b/application/forms/Dashboard/BaseDashboardForm.php @@ -8,6 +8,7 @@ use Icinga\Web\Dashboard\Common\BaseDashboard; use Icinga\Web\Dashboard\Dashboard; use ipl\Html\Contract\FormElement; use ipl\Html\HtmlElement; +use ipl\Html\ValidHtml; use ipl\Web\Compat\CompatForm; use ipl\Web\Url; use ipl\Web\Widget\Icon; @@ -37,6 +38,16 @@ abstract class BaseDashboardForm extends CompatForm { $this->dashboard = $dashboard; + $this->init(); + } + + /** + * Initialize this form + * + * @return void + */ + protected function init() + { // This is needed for the modal views $this->setAction((string) Url::fromRequest()); } @@ -64,9 +75,9 @@ abstract class BaseDashboardForm extends CompatForm * * @return HtmlElement */ - protected function createFormControls() + protected function createFormControls(): ValidHtml { - return HtmlElement::create('div', ['class' => 'control-group form-controls']); + return HtmlElement::create('div', ['class' => ['control-group', 'form-controls']]); } /** @@ -74,7 +85,7 @@ abstract class BaseDashboardForm extends CompatForm * * @return HtmlElement */ - protected function createCancelButton() + protected function createCancelButton(): ValidHtml { return HtmlElement::create('button', [ 'type' => 'button', @@ -91,7 +102,7 @@ abstract class BaseDashboardForm extends CompatForm * * @return FormElement */ - protected function createRemoveButton(Url $action, string $label) + protected function createRemoveButton(Url $action, string $label): ValidHtml { return $this->createElement('submitButton', 'btn_remove', [ 'class' => 'btn-remove', @@ -107,7 +118,7 @@ abstract class BaseDashboardForm extends CompatForm * * @return FormElement */ - protected function registerSubmitButton(string $label) + protected function registerSubmitButton(string $label): ValidHtml { $submitElement = $this->createElement('submit', 'submit', ['class' => 'btn-primary', 'label' => $label]); $this->registerElement($submitElement); diff --git a/application/forms/Dashboard/BaseSetupDashboard.php b/application/forms/Dashboard/BaseSetupDashboard.php new file mode 100644 index 000000000..4ff3b0ee6 --- /dev/null +++ b/application/forms/Dashboard/BaseSetupDashboard.php @@ -0,0 +1,271 @@ + $dashlets) { + /** @var Dashlet $dashlet */ + foreach ($dashlets as $dashlet) { + $element = str_replace(' ', '_', $module . '|' . $dashlet->getName()); + if ($this->getPopulatedValue($element) === 'y' || (! $strict && $this->getPopulatedValue($element))) { + $title = $this->getPopulatedValue($element); + $url = $this->getPopulatedValue($element . '_url'); + + if (! $strict && $title && $url) { + $dashlet + ->setUrl($url) + ->setName($title) + ->setTitle($title); + } + + $choosenDashlets[$module][$dashlet->getName()] = $dashlet; + } + } + + if (isset($choosenDashlets[$module]) && ! $this->duplicateCustomDashlet) { + $this->duplicateCustomDashlet = array_key_exists($this->getPopulatedValue('dashlet'), $choosenDashlets[$module]); + } + } + + self::$moduleDashlets = $choosenDashlets; + } + + /** + * Get whether we are updating an existing dashlet + * + * @return bool + */ + protected function isUpdatingADashlet() + { + return Url::fromRequest()->getPath() === Dashboard::BASE_ROUTE . '/edit-dashlet'; + } + + /** + * Assemble the next page of the modal view + * + * @return void + */ + protected function assembleNextPage() + { + if (! $this->getPopulatedValue('btn_next')) { + return; + } + + $this->dumpArbitaryDashlets(); + $this->assembleNextPageDashboardPart(); + $this->assembleNexPageDashletPart(); + } + + /** + * Assemble the browsed module dashlets on the initial view + * + * @return void + */ + protected function assembleSelectDashletView() + { + if ($this->getPopulatedValue('btn_next')) { + return; + } + + $emptyList = new EmptyDashlet(); + $emptyList->setCheckBox($this->createElement('checkbox', 'custom_url', ['class' => 'sr-only'])); + + $listControl = $this->createFormListControls(); + $listControl->addHtml($emptyList); + + $this->addHtml($listControl); + + foreach (self::$moduleDashlets as $module => $dashlets) { + $listControl = $this->createFormListControls(true); + $list = HtmlElement::create('ul', ['class' => 'dashlet-item-list']); + $listControl->addHtml(HtmlElement::create('span', null, ucfirst($module))); + + /** @var Dashlet $dashlet */ + foreach ($dashlets as $dashlet) { + $multi = new DashletListMultiSelect($dashlet); + $multi->setCheckBox($this->createElement( + 'checkbox', $module . '|' . $dashlet->getName(), + ['class' => 'sr-only'] + )); + + $list->addHtml($multi); + } + + $this->addHtml($listControl->addHtml($list)); + } + } + + /** + * Assemble the dashboard part of elements on the next page + * + * @return void + */ + protected function assembleNextPageDashboardPart() + { + $this->addElement('text', 'pane', [ + 'required' => true, + 'label' => t('Dashboard Title'), + 'description' => t('Enter a title for the new dashboard you want to add the dashlets to'), + ]); + } + + /** + * Assemble the dashlet part of elements on the next page + * + * @return void + */ + protected function assembleNexPageDashletPart() + { + if ($this->getPopulatedValue('custom_url') === 'y') { + $this->addHtml(HtmlElement::create('hr')); + $this->assembleDashletElements(); + } + + if (! empty(self::$moduleDashlets)) { + foreach (self::$moduleDashlets as $module => $dashlets) { + /** @var Dashlet $dashlet */ + foreach ($dashlets as $dashlet) { + $listControl = $this->createFormListControls(true); + $listControl->getAttributes()->add('class', 'multi-dashlets'); + + $dashletName = $this->createElement('text', $module . '|' . $dashlet->getName(), [ + 'required' => true, + 'label' => t('Dashlet Title'), + 'value' => $dashlet->getTitle(), + 'description' => t('Enter a title for the dashlet'), + ]); + + $dashletUrl = $this->createElement('textarea', $module . '|' . $dashlet->getName() . '_url', [ + 'required' => true, + 'label' => t('Url'), + 'value' => $dashlet->getUrl()->getRelativeUrl(), + 'description' => t( + 'Enter url to be loaded in the dashlet. You can paste the full URL, including filters' + ) + ]); + + $this->registerElement($dashletName)->decorate($dashletName); + $this->registerElement($dashletUrl)->decorate($dashletUrl); + + $listControl->addHtml(HtmlElement::create('span', null, t($dashlet->getTitle()))); + + $listControl->addHtml($dashletName); + $listControl->addHtml($dashletUrl); + + $this->addHtml($listControl); + } + } + } + } + + protected function assembleDashletElements() + { + $this->addElement('text', 'dashlet', [ + 'required' => true, + 'label' => t('Dashlet Title'), + 'placeholder' => t('Enter a dashlet title'), + 'description' => t('Enter a title for the dashlet.'), + ]); + + $this->addElement('textarea', 'url', [ + 'required' => true, + 'label' => t('Url'), + 'placeholder' => t('Enter dashlet url'), + 'description' => t( + 'Enter url to be loaded in the dashlet. You can paste the full URL, including filters.' + ), + ]); + } + + protected function assemble() + { + if ($this->getPopulatedValue('btn_next')) { // Configure Dashlets + $submitButtonLabel = t('Add Dashlets'); + $this->assembleNextPage(); + } else { + $submitButtonLabel = t('Next'); + $this->assembleSelectDashletView(); + } + + $submitButton = $this->registerSubmitButton($submitButtonLabel); + if (! $this->getPopulatedValue('btn_next')) { + $submitButton + ->setName('btn_next') + ->getAttributes()->add('class', 'autosubmit'); + } + + $formControls = $this->createFormControls(); + $formControls->add([$submitButton, $this->createCancelButton()]); + + $this->addHtml($formControls); + } + + /** + * Create form list controls (can be collapsible if you want) + * + * @param bool $makeCollapsible + * + * @return ValidHtml + */ + protected function createFormListControls(bool $makeCollapsible = false): ValidHtml + { + $listControls = HtmlElement::create('div', [ + 'class' => ['control-group', 'form-list-control'], + ]); + + if ($makeCollapsible) { + $listControls + ->getAttributes() + ->add('data-toggle-element', '.' . self::DATA_TOGGLE_ELEMENT) + ->add('class', 'collapsible'); + + $listControls->addHtml(HtmlElement::create('div', ['class' => self::DATA_TOGGLE_ELEMENT], [ + new Icon('angle-down', ['class' => 'expand-icon', 'title' => t('Expand')]), + new Icon('angle-up', ['class' => 'collapse-icon', 'title' => t('Collapse')]) + ])); + } + + return $listControls; + } +} diff --git a/application/forms/Dashboard/DashletForm.php b/application/forms/Dashboard/DashletForm.php index 8ffc2763a..5032ca250 100644 --- a/application/forms/Dashboard/DashletForm.php +++ b/application/forms/Dashboard/DashletForm.php @@ -14,118 +14,129 @@ use Icinga\Web\Dashboard\Pane; use ipl\Html\HtmlElement; use ipl\Web\Url; -class DashletForm extends BaseDashboardForm +class DashletForm extends BaseSetupDashboard { + protected function assembleNextPage() + { + if (! $this->getPopulatedValue('btn_next')) { + return; + } + + $this->dumpArbitaryDashlets(); + $this->assembleNexPageDashletPart(); + } + protected function assemble() { - $requestUrl = Url::fromRequest(); + if ($this->isUpdatingADashlet() || $this->getPopulatedValue('btn_next')) { + $requestUrl = Url::fromRequest(); - $homes = $this->dashboard->getEntryKeyTitleArr(); - $activeHome = $this->dashboard->getActiveHome(); - $currentHome = $requestUrl->getParam('home', reset($homes)); - $populatedHome = $this->getPopulatedValue('home', $currentHome); + $homes = $this->dashboard->getEntryKeyTitleArr(); + $activeHome = $this->dashboard->getActiveHome(); + $currentHome = $requestUrl->getParam('home', reset($homes)); + $populatedHome = $this->getPopulatedValue('home', $currentHome); - $panes = []; - if ($currentHome === $populatedHome && $populatedHome !== self::CREATE_NEW_HOME) { - if (! $currentHome || ! $activeHome) { - // Home param isn't passed through, so let's try to load based on the first home - $firstHome = $this->dashboard->rewindEntries(); - if ($firstHome) { - $this->dashboard->loadDashboardEntries($firstHome->getName()); + $panes = []; + if ($currentHome === $populatedHome && $populatedHome !== self::CREATE_NEW_HOME) { + if (! $currentHome || ! $activeHome) { + // Home param isn't passed through, so let's try to load based on the first home + $firstHome = $this->dashboard->rewindEntries(); + if ($firstHome) { + $this->dashboard->loadDashboardEntries($firstHome->getName()); - $panes = $firstHome->getEntryKeyTitleArr(); + $panes = $firstHome->getEntryKeyTitleArr(); + } + } else { + $panes = $activeHome->getEntryKeyTitleArr(); } - } else { - $panes = $activeHome->getEntryKeyTitleArr(); + } elseif ($this->dashboard->hasEntry($populatedHome)) { + $this->dashboard->loadDashboardEntries($populatedHome); + + $panes = $this->dashboard->getActiveHome()->getEntryKeyTitleArr(); } - } elseif ($this->dashboard->hasEntry($populatedHome)) { - $this->dashboard->loadDashboardEntries($populatedHome); - $panes = $this->dashboard->getActiveHome()->getEntryKeyTitleArr(); - } + $this->addElement('hidden', 'org_pane', ['required' => false]); + $this->addElement('hidden', 'org_home', ['required' => false]); + $this->addElement('hidden', 'org_dashlet', ['required' => false]); - $this->addElement('hidden', 'org_pane', ['required' => false]); - $this->addElement('hidden', 'org_home', ['required' => false]); - $this->addElement('hidden', 'org_dashlet', ['required' => false]); + if ($this->isUpdatingADashlet()) { + $this->assembleDashletElements(); - $this->addElement('text', 'dashlet', [ - 'required' => true, - 'label' => t('Dashlet Title'), - 'placeholder' => t('Enter a dashlet title'), - 'description' => t('Enter a title for the dashlet.'), - ]); + $this->addHtml(new HtmlElement('hr')); + } - $this->addElement('textarea', 'url', [ - 'required' => true, - 'label' => t('Url'), - 'placeholder' => t('Enter dashlet url'), - 'description' => t( - 'Enter url to be loaded in the dashlet. You can paste the full URL, including filters.' - ), - ]); - - $this->addHtml(new HtmlElement('hr')); - - $this->addElement('select', 'home', [ - 'class' => 'autosubmit', - 'required' => true, - 'disabled' => empty($homes) ?: null, - 'value' => $populatedHome, - 'multiOptions' => array_merge([self::CREATE_NEW_HOME => self::CREATE_NEW_HOME], $homes), - 'label' => t('Select Home'), - 'description' => t('Select a dashboard home you want to add the dashboard pane to.') - ]); - - if (empty($homes) || $populatedHome === self::CREATE_NEW_HOME) { - $this->addElement('text', 'new_home', [ - 'required' => true, - 'label' => t('Home Title'), - 'placeholder' => t('Enter dashboard home title'), - 'description' => t('Enter a title for the new dashboard home.') + $this->addElement('select', 'home', [ + 'class' => 'autosubmit', + 'required' => true, + 'disabled' => empty($homes) ?: null, + 'value' => $populatedHome, + 'multiOptions' => array_merge([self::CREATE_NEW_HOME => self::CREATE_NEW_HOME], $homes), + 'label' => t('Select Home'), + 'description' => t('Select a dashboard home you want to add the dashboard pane to.') ]); - } - $populatedPane = $this->getPopulatedValue('pane'); - // Pane element's values are depending on the home element's value - if ($populatedPane !== self::CREATE_NEW_PANE && ! in_array($populatedPane, $panes)) { - $this->clearPopulatedValue('pane'); - } + if (empty($homes) || $populatedHome === self::CREATE_NEW_HOME) { + $this->addElement('text', 'new_home', [ + 'required' => true, + 'label' => t('Home Title'), + 'placeholder' => t('Enter dashboard home title'), + 'description' => t('Enter a title for the new dashboard home.') + ]); + } - $populatedPane = $this->getPopulatedValue('pane', $requestUrl->getParam('pane', reset($panes))); - $disable = empty($panes) || $populatedHome === self::CREATE_NEW_HOME; - $this->addElement('select', 'pane', [ - 'class' => 'autosubmit', - 'required' => true, - 'disabled' => $disable ?: null, - 'value' => ! $disable ? $populatedPane : self::CREATE_NEW_PANE, - 'multiOptions' => array_merge([self::CREATE_NEW_PANE => self::CREATE_NEW_PANE], $panes), - 'label' => t('Select Dashboard'), - 'description' => t('Select a dashboard you want to add the dashlet to.'), - ]); + $populatedPane = $this->getPopulatedValue('pane'); + // Pane element's values are depending on the home element's value + if ($populatedPane !== self::CREATE_NEW_PANE && ! in_array($populatedPane, $panes)) { + $this->clearPopulatedValue('pane'); + } - if ($disable || $this->getPopulatedValue('pane') === self::CREATE_NEW_PANE) { - $this->addElement('text', 'new_pane', [ - 'required' => true, - 'label' => t('Dashboard Title'), - 'placeholder' => t('Enter dashboard title'), - 'description' => t('Enter a title for the new dashboard.'), + $populatedPane = $this->getPopulatedValue('pane', $requestUrl->getParam('pane', reset($panes))); + $disable = empty($panes) || $populatedHome === self::CREATE_NEW_HOME; + $this->addElement('select', 'pane', [ + 'class' => 'autosubmit', + 'required' => true, + 'disabled' => $disable ?: null, + 'value' => ! $disable ? $populatedPane : self::CREATE_NEW_PANE, + 'multiOptions' => array_merge([self::CREATE_NEW_PANE => self::CREATE_NEW_PANE], $panes), + 'label' => t('Select Dashboard'), + 'description' => t('Select a dashboard you want to add the dashlet to.'), ]); + + if ($disable || $this->getPopulatedValue('pane') === self::CREATE_NEW_PANE) { + $this->addElement('text', 'new_pane', [ + 'required' => true, + 'label' => t('Dashboard Title'), + 'placeholder' => t('Enter dashboard title'), + 'description' => t('Enter a title for the new dashboard.'), + ]); + } + + if ($this->isUpdatingADashlet()) { + $targetUrl = (clone $requestUrl)->setPath(Dashboard::BASE_ROUTE . '/remove-dashlet'); + $removeButton = $this->createRemoveButton($targetUrl, t('Remove Dashlet')); + + $formControls = $this->createFormControls(); + $formControls->add([ + $this->registerSubmitButton(t('Add to Dashboard')), + $removeButton, + $this->createCancelButton() + ]); + + $this->addHtml($formControls); + } else { + $this->assembleNextPage(); + + $formControls = $this->createFormControls(); + $formControls->add([ + $this->registerSubmitButton(t('Add to Dashboard')), + $this->createCancelButton() + ]); + + $this->addHtml($formControls); + } + } else { + parent::assemble(); } - - $removeButton = null; - if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/edit-dashlet') { - $targetUrl = (clone $requestUrl)->setPath(Dashboard::BASE_ROUTE . '/remove-dashlet'); - $removeButton = $this->createRemoveButton($targetUrl, t('Remove Dashlet')); - } - - $formControls = $this->createFormControls(); - $formControls->add([ - $this->registerSubmitButton(t('Add to Dashboard')), - $removeButton, - $this->createCancelButton() - ]); - - $this->addHtml($formControls); } protected function onSuccess() @@ -144,7 +155,7 @@ class DashletForm extends BaseDashboardForm $selectedPane = $this->getPopulatedValue('new_pane'); } - if (Url::fromRequest()->getPath() === Dashboard::BASE_ROUTE . '/new-dashlet') { + if (! $this->isUpdatingADashlet()) { $currentHome = new DashboardHome($selectedHome); if ($dashboard->hasEntry($currentHome->getName())) { $currentHome = clone $dashboard->getEntry($currentHome->getName()); @@ -159,15 +170,19 @@ class DashletForm extends BaseDashboardForm $currentPane = clone $currentHome->getEntry($currentPane->getName()); } - $dashlet = new Dashlet($this->getValue('dashlet'), $this->getValue('url'), $currentPane); - if ($currentPane->hasEntry($dashlet->getName())) { - Notification::error(sprintf( - t('Dashlet "%s" already exists within the "%s" dashboard pane'), - $dashlet->getTitle(), - $currentPane->getTitle() - )); + $customDashlet = null; + if (($dashlet = $this->getPopulatedValue('dashlet')) && ($url = $this->getPopulatedValue('url'))) { + $customDashlet = new Dashlet($dashlet, $url, $currentPane); - return; + if ($currentPane->hasEntry($customDashlet->getName()) || $this->duplicateCustomDashlet) { + Notification::error(sprintf( + t('Dashlet "%s" already exists within the "%s" dashboard pane'), + $customDashlet->getTitle(), + $currentPane->getTitle() + )); + + return; + } } $conn->beginTransaction(); @@ -175,7 +190,35 @@ class DashletForm extends BaseDashboardForm try { $dashboard->manageEntry($currentHome); $currentHome->manageEntry($currentPane); - $currentPane->manageEntry($dashlet); + + if ($customDashlet) { + $currentPane->manageEntry($customDashlet); + } + + // Avoid the hassle of iterating through the module dashlets each time to check if exits, + // even though the current pane doesn't have any entries + if (! $this->getPopulatedValue('new_pane') && $currentPane->hasEntries()) { + $this->dumpArbitaryDashlets(false); + + foreach (self::$moduleDashlets as $_ => $dashlets) { + /** @var Dashlet $dashlet */ + foreach ($dashlets as $dashlet) { + if ($currentPane->hasEntry($dashlet->getName()) || $this->duplicateCustomDashlet) { + Notification::error(sprintf( + t('Dashlet "%s" already exists within the "%s" dashboard pane'), + $dashlet->getTitle(), + $currentPane->getTitle() + )); + + return; + } + + $currentPane->manageEntry($dashlet); + } + } + } else { + $currentPane->manageEntry(self::$moduleDashlets); + } $conn->commitTransaction(); } catch (Exception $err) { @@ -185,7 +228,7 @@ class DashletForm extends BaseDashboardForm throw $err; } - Notification::success(sprintf(t('Created dashlet "%s" successfully'), $dashlet->getTitle())); + Notification::success(t('Created dashlet(s) successfully')); } else { $orgHome = $dashboard->getEntry($this->getValue('org_home')); $orgPane = $orgHome->getEntry($this->getValue('org_pane')); diff --git a/application/forms/Dashboard/WelcomeForm.php b/application/forms/Dashboard/WelcomeForm.php index 124de4746..4951d900d 100644 --- a/application/forms/Dashboard/WelcomeForm.php +++ b/application/forms/Dashboard/WelcomeForm.php @@ -48,6 +48,7 @@ class WelcomeForm extends CompatForm $conn->beginTransaction(); try { + // Default Home might have been disabled, so we have to update it first $this->dashboard->manageEntry($home); $home->manageEntry($this->dashboard->getSystemDefaults(), null, true); diff --git a/library/Icinga/Web/Dashboard/Common/DashboardManager.php b/library/Icinga/Web/Dashboard/Common/DashboardManager.php index c1339866a..829eaf1b4 100644 --- a/library/Icinga/Web/Dashboard/Common/DashboardManager.php +++ b/library/Icinga/Web/Dashboard/Common/DashboardManager.php @@ -358,13 +358,12 @@ trait DashboardManager /** * Get module dashlets from the database * - * @param Query $query - * * @return array */ - public static function getModuleDashlets(Query $query): array + public static function getModuleDashlets(): array { $dashlets = []; + $query = Model\ModuleDashlet::on(self::getConn()); foreach ($query as $moduleDashlet) { $dashlet = new Dashlet($moduleDashlet->name, $moduleDashlet->url); if ($moduleDashlet->description) { diff --git a/library/Icinga/Web/Dashboard/ItemList/DashletListItem.php b/library/Icinga/Web/Dashboard/ItemList/DashletListItem.php index 0a71f6964..477aac462 100644 --- a/library/Icinga/Web/Dashboard/ItemList/DashletListItem.php +++ b/library/Icinga/Web/Dashboard/ItemList/DashletListItem.php @@ -57,9 +57,7 @@ class DashletListItem extends BaseHtmlElement { $title = HtmlElement::create('h1', ['class' => 'dashlet-header']); - if (! $this->dashlet) { - $title->add(t('Custom Url')); - } elseif ($this->renderEditButton) { + if ($this->renderEditButton) { $title->addHtml(new Link( t($this->dashlet->getTitle()), $this->dashlet->getUrl()->getUrlWithout(['showCompact', 'limit'])->getRelativeUrl(), @@ -95,9 +93,7 @@ class DashletListItem extends BaseHtmlElement { $section = HtmlElement::create('section', ['class' => 'caption']); - if (! $this->dashlet) { - $section->add(t('Create a dashlet with custom url and filter')); - } else { + if ($this->dashlet) { $description = $this->dashlet->getDescription() ?: t('There is no provided description.'); $section->getAttributes()->set('title', $description); diff --git a/library/Icinga/Web/Dashboard/ItemList/EmptyDashlet.php b/library/Icinga/Web/Dashboard/ItemList/EmptyDashlet.php new file mode 100644 index 000000000..5873ea415 --- /dev/null +++ b/library/Icinga/Web/Dashboard/ItemList/EmptyDashlet.php @@ -0,0 +1,40 @@ + 'dashlet-item-list empty-list']); + } + + protected function assembleHeader(): HtmlElement + { + $header = HtmlElement::create('h1', ['class' => 'dashlet-header']); + $header->add(t('Custom Url')); + + return $header; + } + + protected function assembleSummary() + { + $section = HtmlElement::create('section', ['class' => 'caption']); + $section->add(t('Create a dashlet with custom url and filter')); + + return $section; + } + + protected function assemble() + { + $this->addHtml($this->assembleHeader()); + $this->addHtml($this->assembleSummary()); + + $this->addWrapper($this->createLabel()); + $this->addWrapper($this->createEmptyList()); + } +} diff --git a/library/Icinga/Web/Dashboard/Pane.php b/library/Icinga/Web/Dashboard/Pane.php index 8b23cd209..8fd28bf92 100644 --- a/library/Icinga/Web/Dashboard/Pane.php +++ b/library/Icinga/Web/Dashboard/Pane.php @@ -156,14 +156,19 @@ class Pane extends BaseDashboard implements Sortable ]); if ($dashlet->isModuleDashlet()) { - $data = $dashlet->getModule(); - if (($pane = $dashlet->getPane())) { - $data .= $pane->getName(); + $systemUuid = $dashlet->getUuid(); + if (! $systemUuid) { + $data = $dashlet->getModule(); + if (($pane = $dashlet->getPane())) { + $data .= $pane->getName(); + } + + $systemUuid = Dashboard::getSHA1($data . $dashlet->getName()); } $conn->insert('icingaweb_system_dashlet', [ 'dashlet_id' => $uuid, - 'module_dashlet_id' => Dashboard::getSHA1($data . $dashlet->getName()) + 'module_dashlet_id' => $systemUuid ]); } } elseif (! $this->hasEntry($dashlet->getName()) || ! $origin diff --git a/library/Icinga/Web/Dashboard/Setup/SetupNewDashboard.php b/library/Icinga/Web/Dashboard/Setup/SetupNewDashboard.php index 2f33d4a0e..3b47af36d 100644 --- a/library/Icinga/Web/Dashboard/Setup/SetupNewDashboard.php +++ b/library/Icinga/Web/Dashboard/Setup/SetupNewDashboard.php @@ -5,6 +5,7 @@ namespace Icinga\Web\Dashboard\Setup; use Icinga\Forms\Dashboard\BaseDashboardForm; +use Icinga\Forms\Dashboard\BaseSetupDashboard; use Icinga\Web\Dashboard\Dashboard; use Icinga\Web\Dashboard\DashboardHome; use Icinga\Web\Dashboard\Dashlet; @@ -16,242 +17,54 @@ use ipl\Html\ValidHtml; use ipl\Web\Url; use ipl\Web\Widget\Icon; -class SetupNewDashboard extends BaseDashboardForm +class SetupNewDashboard extends BaseSetupDashboard { - /** @var array Module dashlets from the DB */ - private $dashlets = []; - - public function __construct(Dashboard $dashboard) + protected function init() { - parent::__construct($dashboard); + parent::init(); $this->setRedirectUrl((string) Url::fromPath(Dashboard::BASE_ROUTE)); $this->setAction($this->getRedirectUrl() . '/setup-dashboard'); } - /** - * Initialize module dashlets - * - * @param array $dashlets - * - * @return $this - */ - public function initDashlets(array $dashlets): self - { - $this->dashlets = $dashlets; - - return $this; - } - - protected function assemble() - { - if ($this->getPopulatedValue('btn_next')) { // Configure Dashlets - $this->dumpArbitaryDashlets(); - - $submitButtonLabel = t('Add Dashlets'); - $this->addElement('text', 'pane', [ - 'required' => true, - 'label' => t('Dashboard Title'), - 'description' => t('Enter a title for the new dashboard you want to add the dashlets to'), - ]); - - if (empty($this->dashlets) - || (count(array_keys($this->dashlets)) == 1 // Only one module - && count(reset($this->dashlets)) == 1 // Only one module dashlet - ) - ) { - $this->addHtml(HtmlElement::create('hr')); - - $this->addElement('text', 'dashlet', [ - 'required' => true, - 'label' => t('Dashlet Title'), - 'description' => t('Enter a title for the dashlet'), - ]); - - $this->addElement('textarea', 'url', [ - 'required' => true, - 'label' => t('Url'), - 'description' => t( - 'Enter url to be loaded in the dashlet. You can paste the full URL, including filters' - ) - ]); - - foreach ($this->dashlets as $_ => $dashlets) { - /** @var Dashlet $dashlet */ - foreach ($dashlets as $dashlet) { - $this->getElement('dashlet')->getAttributes()->set('value', $dashlet->getTitle()); - $this->getElement('url')->getAttributes()->set('value', $dashlet->getUrl()->getRelativeUrl()); - } - } - } else { - foreach ($this->dashlets as $module => $dashlets) { - /** @var Dashlet $dashlet */ - foreach ($dashlets as $dashlet) { - $listControl = $this->createFormListControl(); - $listControl->getAttributes()->add('class', 'multi-dashlets'); - - $listControl->addHtml(HtmlElement::create('div', ['class' => 'dashlets-list-info'], [ - new Icon('angle-down', ['class' => 'expand-icon', 'title' => t('Expand')]), - new Icon('angle-up', ['class' => 'collapse-icon', 'title' => t('Collapse')]) - ])); - - $dashletName = $this->createElement('text', $module . $dashlet->getName(), [ - 'required' => true, - 'label' => t('Dashlet Title'), - 'value' => $dashlet->getTitle(), - 'description' => t('Enter a title for the dashlet'), - ]); - - $dashletUrl = $this->createElement('textarea', $module . $dashlet->getName() . '_url', [ - 'required' => true, - 'label' => t('Url'), - 'value' => $dashlet->getUrl()->getRelativeUrl(), - 'description' => t( - 'Enter url to be loaded in the dashlet. You can paste the full URL, including filters' - ) - ]); - - $this->registerElement($dashletName)->decorate($dashletName); - $this->registerElement($dashletUrl)->decorate($dashletUrl); - - $listControl->addHtml(HtmlElement::create('span', null, t($dashlet->getTitle()))); - - $listControl->addHtml($dashletName); - $listControl->addHtml($dashletUrl); - - $this->addHtml($listControl); - } - } - } - } else { // Select Dashlets - $submitButtonLabel = t('Next'); - $list = HtmlElement::create('ul', ['class' => 'dashlet-item-list empty-list']); - $multi = new DashletListMultiSelect(); - $multi->setCheckBox($this->createElement('checkbox', 'custom_url', ['class' => 'sr-only'])); - - $listControl = $this->createFormListControl(); - $listControl->getAttributes()->remove('class', 'collapsible'); - - $this->addHtml($listControl->addHtml($list->addHtml($multi))); - - foreach ($this->dashlets as $module => $dashlets) { - $listControl = $this->createFormListControl(); - $listControl->addHtml(HtmlElement::create('div', ['class' => 'dashlets-list-info'], [ - new Icon('angle-down', ['class' => 'expand-icon', 'title' => t('Expand')]), - new Icon('angle-up', ['class' => 'collapse-icon', 'title' => t('Collapse')]) - ])); - - $list = HtmlElement::create('ul', ['class' => 'dashlet-item-list ' . $module]); - $listControl->addHtml(HtmlElement::create('span', null, ucfirst($module))); - - /** @var Dashlet $dashlet */ - foreach ($dashlets as $dashlet) { - $multi = new DashletListMultiSelect($dashlet); - $multi->setCheckBox( - $this->createElement('checkbox', $module . '|' . $dashlet->getName(), ['class' => 'sr-only']) - ); - - $list->addHtml($multi); - } - - $this->addHtml($listControl->addHtml($list)); - } - } - - $submitButton = $this->registerSubmitButton($submitButtonLabel); - if (! $this->getPopulatedValue('btn_next')) { - $submitButton - ->setName('btn_next') - ->getAttributes()->add('class', 'autosubmit'); - } - - $formControls = $this->createFormControls(); - $formControls->add([$submitButton, $this->createCancelButton()]); - - $this->addHtml($formControls); - } - protected function onSuccess() { if ($this->getPopulatedValue('submit')) { $conn = Dashboard::getConn(); $pane = new Pane($this->getPopulatedValue('pane')); + $home = $this->dashboard->getEntry(DashboardHome::DEFAULT_HOME); $conn->beginTransaction(); try { - $this->dashboard->getEntry(DashboardHome::DEFAULT_HOME)->manageEntry($pane); + $this->dashboard->manageEntry($home); + $home->manageEntry($pane); + + $this->dumpArbitaryDashlets(false); - // If element name "dashlet" and "url" are set we need to only store one dashlet if (($name = $this->getPopulatedValue('dashlet')) && ($url = $this->getPopulatedValue('url'))) { + if ($this->duplicateCustomDashlet) { + Notification::error(sprintf( + t('Failed to create new dahlets. Dashlet "%s" exists within the selected one'), + $name + )); + + return; + } + $dashlet = new Dashlet($name, $url, $pane); $pane->manageEntry($dashlet); - } else { - foreach ($this->dashlets as $module => $dashlets) { - $moduleDashlets = []; - - /** @var Dashlet $dashlet */ - foreach ($dashlets as $dashlet) { - $element = str_replace(' ', '_', $module . $dashlet->getName()); - if (! $this->getPopulatedValue($element)) { - continue; - } - - $title = $this->getPopulatedValue($element); - $url = $this->getPopulatedValue($element . '_url'); - $dashlet - ->setUrl($url) - ->setTitle($title); - - $moduleDashlets[$dashlet->getName()] = $dashlet; - } - - $pane->manageEntry($moduleDashlets); - } } + $pane->manageEntry(self::$moduleDashlets); + $conn->commitTransaction(); } catch (\Exception $err) { $conn->rollBackTransaction(); throw $err; } - Notification::success(t('Created dashboard successfully')); + Notification::success(t('Added new dashlet(s) successfully')); } } - - /** - * Dump all module dashlets which are not selected by the user - * from the member variable - * - * @return void - */ - private function dumpArbitaryDashlets(): void - { - $choosenDashlets = []; - foreach ($this->dashlets as $module => $dashlets) { - /** @var Dashlet $dashlet */ - foreach ($dashlets as $dashlet) { - $element = str_replace(' ', '_', $module . '|' . $dashlet->getName()); - if ($this->getPopulatedValue($element) === 'y') { - $choosenDashlets[$module][$dashlet->getName()] = $dashlet; - } - } - } - - $this->dashlets = $choosenDashlets; - } - - /** - * Create collapsible form list control - * - * @return ValidHtml - */ - private function createFormListControl(): ValidHtml - { - return HtmlElement::create('div', [ - 'class' => ['control-group', 'form-list-control', 'collapsible'], - 'data-toggle-element' => '.dashlets-list-info' - ]); - } }