diff --git a/application/controllers/DashboardsController.php b/application/controllers/DashboardsController.php new file mode 100644 index 0000000000..eec5f6b0a4 --- /dev/null +++ b/application/controllers/DashboardsController.php @@ -0,0 +1,520 @@ +dashboard = new Dashboard(); + $this->dashboard->setUser($this->Auth()->getUser()); + $this->dashboard->setTabs($this->getTabs()); + } + + public function indexAction() + { + $this->dashboard->load(DashboardHome::DEFAULT_HOME); + + $this->createTabs(); + + $activeHome = $this->dashboard->getActiveHome(); + if (! $activeHome || ! $activeHome->hasEntries()) { + $this->addTitleTab(t('Welcome')); + + // Setup dashboard introduction form + $welcomeForm = new WelcomeForm($this->dashboard); + $welcomeForm->on(WelcomeForm::ON_SUCCESS, function () use ($welcomeForm) { + $this->redirectNow($welcomeForm->getRedirectUrl()); + })->handleRequest($this->getServerRequest()); + + $this->content->getAttributes()->add('class', 'welcome-view'); + $this->dashboard->setWelcomeForm($welcomeForm); + } else { + $pane = $this->getParam('pane'); + if (! $pane) { + $pane = $this->dashboard->getActivePane()->getName(); + } + + $this->dashboard->activate($pane); + } + + $this->addContent($this->dashboard); + } + + /** + * Display all the dashboards assigned to a Home set in the `home` request param + * + * If no pane param is submitted, the default pane is displayed (usually the first one) + */ + public function homeAction() + { + $this->dashboard->load($this->params->getRequired('home')); + + $activeHome = $this->dashboard->getActiveHome(); + if (! $activeHome->getEntries()) { + $this->addTitleTab($activeHome->getTitle()); + } + + // Not to render the cog icon before the above tab + $this->createTabs(); + + if ($activeHome->hasEntries()) { + $pane = $this->getParam('pane'); + if (! $pane) { + $pane = $this->dashboard->getActivePane()->getName(); + } + + $this->dashboard->activate($pane); + } + + $this->addContent($this->dashboard); + } + + public function newHomeAction() + { + $this->setTitle(t('Add new Dashboard Home')); + $this->dashboard->load(); + + $paneForm = (new NewHomePaneForm($this->dashboard)) + ->on(NewHomePaneForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + }) + ->handleRequest($this->getServerRequest()); + + $this->addContent($paneForm); + } + + public function editHomeAction() + { + $this->setTitle(t('Update Home')); + $this->dashboard->load(); + + $home = $this->params->getRequired('home'); + if (! $this->dashboard->hasEntry($home)) { + $this->httpNotFound(sprintf(t('Home "%s" not found'), $home)); + } + + // TODO: Shouldn't be necessary. load() should get the name already and + // the form should otherwise ensure it has the required data + $this->dashboard->activateHome($this->dashboard->getEntry($home)); + + $homeForm = (new HomePaneForm($this->dashboard)) + ->on(HomePaneForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + }) + ->handleRequest($this->getServerRequest()); + + $homeForm->load($this->dashboard->getActiveHome()); + $this->addContent($homeForm); + } + + public function removeHomeAction() + { + $this->setTitle(t('Remove Home')); + $this->dashboard->load(); + + $home = $this->params->getRequired('home'); + if (! $this->dashboard->hasEntry($home)) { + $this->httpNotFound(sprintf(t('Home "%s" not found'), $home)); + } + + // TODO: Shouldn't be necessary. load() should get the name already and + // the form should otherwise ensure it has the required data + $this->dashboard->activateHome($this->dashboard->getEntry($home)); + + $homeForm = (new RemoveHomePaneForm($this->dashboard)) + ->on(RemoveHomePaneForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + }) + ->handleRequest($this->getServerRequest()); + + $this->addContent($homeForm); + } + + public function newPaneAction() + { + $this->setTitle(t('Add new Dashboard')); + $this->dashboard->load(); + + $home = $this->params->getRequired('home'); + if (! $this->dashboard->hasEntry($home)) { + $this->httpNotFound(sprintf(t('Home "%s" not found'), $home)); + } + + // TODO: Shouldn't be necessary. load() should get the name already and + // the form should otherwise ensure it has the required data + $this->dashboard->activateHome($this->dashboard->getEntry($home)); + + $paneForm = (new NewHomePaneForm($this->dashboard)) + ->on(NewHomePaneForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + }) + ->handleRequest($this->getServerRequest()); + + $this->addContent($paneForm); + } + + public function editPaneAction() + { + $this->setTitle(t('Update Pane')); + $this->dashboard->load(); + + $pane = $this->params->getRequired('pane'); + $home = $this->params->getRequired('home'); + + if (! $this->dashboard->hasEntry($home)) { + $this->httpNotFound(sprintf(t('Home "%s" not found'), $home)); + } + + // TODO: Shouldn't be necessary. load() should get the name already and + // the form should otherwise ensure it has the required data + $this->dashboard->activateHome($this->dashboard->getEntry($home)); + + if (! $this->dashboard->getActiveHome()->hasEntry($pane)) { + $this->httpNotFound(sprintf(t('Pane "%s" not found'), $pane)); + } + + $paneForm = (new HomePaneForm($this->dashboard)) + ->on(HomePaneForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + }) + ->handleRequest($this->getServerRequest()); + + $paneForm->load($this->dashboard->getActiveHome()->getEntry($pane)); + + $this->addContent($paneForm); + } + + public function removePaneAction() + { + $this->setTitle(t('Remove Pane')); + $this->dashboard->load(); + + $home = $this->params->getRequired('home'); + $paneParam = $this->params->getRequired('pane'); + + if (! $this->dashboard->hasEntry($home)) { + $this->httpNotFound(sprintf(t('Home "%s" not found'), $home)); + } + + // TODO: Shouldn't be necessary. load() should get the name already and + // the form should otherwise ensure it has the required data + $this->dashboard->activateHome($this->dashboard->getEntry($home)); + + if (! $this->dashboard->getActiveHome()->hasEntry($paneParam)) { + $this->httpNotFound(sprintf(t('Pane "%s" not found'), $paneParam)); + } + + $paneForm = new RemoveHomePaneForm($this->dashboard); + $paneForm->populate(['org_name' => $paneParam]); + $paneForm->on(RemoveHomePaneForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + })->handleRequest($this->getServerRequest()); + + $this->addContent($paneForm); + } + + public function newDashletAction() + { + if (isset($this->getRequest()->getPost()['btn_next'])) { + $this->setTitle(t('Add Dashlet To Dashboard')); + } else { + $this->setTitle(t('Select Dashlets')); + } + + $this->dashboard->load(); + + $home = $this->params->getRequired('home'); + // TODO: Shouldn't be necessary. load() should get the name already and + // the form should otherwise ensure it has the required data + $this->dashboard->activateHome($this->dashboard->getEntry($home)); + + $dashletForm = new DashletForm($this->dashboard); + $dashletForm->populate($this->getRequest()->getPost()); + $dashletForm->on(DashletForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + })->handleRequest($this->getServerRequest()); + + $this->addContent($dashletForm); + } + + public function editDashletAction() + { + $this->setTitle(t('Edit Dashlet')); + $this->dashboard->load(); + + $pane = $this->validateDashletParams(); + $dashlet = $pane->getEntry($this->getParam('dashlet')); + + $dashletForm = (new DashletForm($this->dashboard)) + ->on(DashletForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + }) + ->handleRequest($this->getServerRequest()); + + $dashletForm->getElement('submit')->setLabel(t('Update Dashlet')); + + $dashletForm->load($dashlet); + $this->addContent($dashletForm); + } + + public function removeDashletAction() + { + $this->setTitle(t('Remove Dashlet')); + $this->dashboard->load(); + + $this->validateDashletParams(); + + $removeForm = (new RemoveDashletForm($this->dashboard)) + ->on(RemoveDashletForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + }) + ->handleRequest($this->getServerRequest()); + + $this->addContent($removeForm); + } + + /** + * Handles all widgets drag and drop requests + */ + public function reorderWidgetsAction() + { + $this->assertHttpMethod('post'); + $dashboards = $this->getRequest()->getPost(); + if (! isset($dashboards['dashboardData'])) { + $this->httpBadRequest(t('Invalid request data')); + } + + $dashboards = Json::decode($dashboards['dashboardData'], true); + $originals = $dashboards['originals']; + unset($dashboards['originals']); + + $this->dashboard->load(); + + $orgHome = null; + $orgPane = null; + if ($originals && isset($originals['originalHome'])) { + /** @var DashboardHome $orgHome */ + $orgHome = $this->dashboard->getEntry($originals['originalHome']); + $orgHome->setActive()->loadDashboardEntries(); + + if (isset($originals['originalPane'])) { + $orgPane = $orgHome->getEntry($originals['originalPane']); + $orgHome->setEntries([$orgPane->getName() => $orgPane]); + } + } + + $duplicatedError = false; + foreach ($dashboards as $home => $value) { + if (! $this->dashboard->hasEntry($home)) { + Notification::error(sprintf(t('Dashboard home "%s" not found'), $home)); + break; + } + + $home = $this->dashboard->getEntry($home); + /** @var DashboardHome $home */ + if (! is_array($value)) { + $this->dashboard->reorderWidget($home, (int) $value); + + Notification::success(sprintf(t('Updated dashboard home "%s" successfully'), $home->getTitle())); + break; + } + + $home->setActive()->loadDashboardEntries(); + foreach ($value as $pane => $indexOrValues) { + if (! $home->hasEntry($pane) && (! $orgHome || ! $orgHome->hasEntry($pane))) { + Notification::error(sprintf(t('Dashboard pane "%s" not found'), $pane)); + break; + } + + $pane = $home->hasEntry($pane) ? $home->getEntry($pane) : $orgHome->getEntry($pane); + /** @var Pane $pane */ + if (! is_array($indexOrValues)) { + if ($orgHome && $orgHome->hasEntry($pane->getName()) && $home->hasEntry($pane->getName())) { + Notification::error(sprintf( + t('Dashboard "%s" already exists within "%s" home'), + $pane->getTitle(), + $home->getTitle() + )); + + $duplicatedError = true; + break; + } + + // Perform DB updates + $home->reorderWidget($pane, (int) $indexOrValues, $orgHome); + if ($orgHome) { + // In order to properly update the dashlets id (user + home + pane + dashlet) + $pane->manageEntry($pane->getEntries()); + } + + Notification::success(sprintf( + t('%s dashboard pane "%s" successfully'), + $orgHome ? 'Moved' : 'Updated', + $pane->getTitle() + )); + break; + } + + foreach ($indexOrValues as $dashlet => $index) { + if (! $pane->hasEntry($dashlet) && (! $orgPane || ! $orgPane->hasEntry($dashlet))) { + Notification::error(sprintf(t('Dashlet "%s" not found'), $dashlet)); + break; + } + + if ($orgPane && $orgPane->hasEntry($dashlet) && $pane->hasEntry($dashlet)) { + Notification::error(sprintf( + t('Dashlet "%s" already exists within "%s" dashboard pane'), + $dashlet, + $pane->getTitle() + )); + + $duplicatedError = true; + break; + } + + $dashlet = $pane->hasEntry($dashlet) ? $pane->getEntry($dashlet) : $orgPane->getEntry($dashlet); + $pane->reorderWidget($dashlet, (int) $index, $orgPane); + + Notification::success(sprintf( + t('%s dashlet "%s" successfully'), + $orgPane ? 'Moved' : 'Updated', + $dashlet->getTitle() + )); + } + } + } + + if ($duplicatedError) { + // Even though the drop action couldn't be performed successfully from our server, Sortable JS has + // already dropped the draggable element though, so we need to redirect here to undo it. + $this->redirectNow(Dashboard::BASE_ROUTE . '/settings'); + } + + $this->createTabs(); + $this->getTabs()->setRefreshUrl(Url::fromPath(Dashboard::BASE_ROUTE . '/settings')); + $this->dashboard->activate('dashboard_settings'); + $this->sendMultipartUpdate(); + } + + /** + * Provides a mini wizard which guides a new user through the dashboard creation + * process and helps them get a first impression of Icinga Web 2. + */ + public function setupDashboardAction() + { + 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('Configure Dashlets')); + } else { + $this->setTitle(t('Add Dashlet')); + } + + $this->dashboard->load(); + + $setupForm = new SetupNewDashboardForm($this->dashboard); + $setupForm->on(SetupNewDashboardForm::ON_SUCCESS, function () use ($setupForm) { + $this->redirectNow($setupForm->getRedirectUrl()); + })->handleRequest($this->getServerRequest()); + + $this->addContent($setupForm); + } + + public function settingsAction() + { + $this->dashboard->load(); + $this->createTabs(); + + $activeHome = $this->dashboard->getActiveHome(); + // We can't grant access the user to the dashboard manager if there aren't any dashboards to manage + if (! $activeHome || (! $activeHome->hasEntries() && count($this->dashboard->getEntries()) === 1)) { + $this->redirectNow(Dashboard::BASE_ROUTE); + } + + $this->dashboard->activate('dashboard_settings'); + + $this->addControl(new ActionLink( + t('Add new Home'), + Url::fromPath(Dashboard::BASE_ROUTE . '/new-home'), + 'plus', + [ + 'class' => 'add-home', + 'data-icinga-modal' => true, + 'data-no-icinga-ajax' => true + ] + )); + + $this->content->getAttributes()->add('class', 'dashboard-manager'); + $this->controls->getAttributes()->add('class', 'dashboard-manager-controls'); + + $this->addContent(new Settings($this->dashboard)); + } + + /** + * Create tab aggregation + */ + private function createTabs() + { + $tabs = $this->dashboard->getTabs(); + $activeHome = $this->dashboard->getActiveHome(); + if ($activeHome && ($activeHome->getName() !== DashboardHome::DEFAULT_HOME || $activeHome->hasEntries())) { + $tabs->extend(new DashboardSettings()); + } + + return $tabs; + } + + private function validateDashletParams() + { + $home = $this->params->getRequired('home'); + $pane = $this->params->getRequired('pane'); + $dashlet = $this->params->getRequired('dashlet'); + + if (! $this->dashboard->hasEntry($home)) { + $this->httpNotFound(sprintf(t('Home "%s" not found'), $home)); + } + + // TODO: Shouldn't be necessary. load() should get the name already and + // the form should otherwise ensure it has the required data + $this->dashboard->activateHome($this->dashboard->getEntry($home)); + + if (! $this->dashboard->getActiveHome()->hasEntry($pane)) { + $this->httpNotFound(sprintf(t('Pane "%s" not found'), $pane)); + } + + $pane = $this->dashboard->getActiveHome()->getEntry($pane); + if (! $pane->hasEntry($dashlet)) { + $this->httpNotFound(sprintf(t('Dashlet "%s" not found'), $dashlet)); + } + + return $pane; + } +} diff --git a/application/controllers/SearchController.php b/application/controllers/SearchController.php index 92aeabe54d..2fdba56621 100644 --- a/application/controllers/SearchController.php +++ b/application/controllers/SearchController.php @@ -3,23 +3,21 @@ namespace Icinga\Controllers; -use Icinga\Web\Controller\ActionController; -use Icinga\Web\Widget; use Icinga\Web\Widget\SearchDashboard; +use ipl\Web\Compat\CompatController; /** * Search controller */ -class SearchController extends ActionController +class SearchController extends CompatController { public function indexAction() { $searchDashboard = new SearchDashboard(); $searchDashboard->setUser($this->Auth()->getUser()); - $this->view->dashboard = $searchDashboard->search($this->params->get('q')); - // NOTE: This renders the dashboard twice. Remove this once we can catch exceptions thrown in view scripts. - $this->view->dashboard->render(); + $this->controls->setTabs($searchDashboard->getTabs()); + $this->addContent($searchDashboard->search($this->getParam('q'))); } public function hintAction() diff --git a/application/forms/Authentication/LoginForm.php b/application/forms/Authentication/LoginForm.php index 8a71ecf554..fd7173b7ea 100644 --- a/application/forms/Authentication/LoginForm.php +++ b/application/forms/Authentication/LoginForm.php @@ -11,6 +11,7 @@ use Icinga\Authentication\User\ExternalBackend; use Icinga\Common\Database; use Icinga\User; +use Icinga\Web\Dashboard\Dashboard; use Icinga\Web\Form; use Icinga\Web\RememberMe; use Icinga\Web\Url; @@ -27,7 +28,7 @@ class LoginForm extends Form /** * Redirect URL */ - const REDIRECT_URL = 'dashboard'; + const REDIRECT_URL = Dashboard::BASE_ROUTE; public static $defaultElementDecorators = [ ['ViewHelper', ['separator' => '']], diff --git a/application/forms/Dashboard/BaseDashboardForm.php b/application/forms/Dashboard/BaseDashboardForm.php new file mode 100644 index 0000000000..0bd1ab9874 --- /dev/null +++ b/application/forms/Dashboard/BaseDashboardForm.php @@ -0,0 +1,128 @@ +dashboard = $dashboard; + + $this->init(); + } + + /** + * Initialize this form + * + * @return void + */ + protected function init() + { + // This is needed for the modal views + $this->setAction((string) Url::fromRequest()); + } + + public function hasBeenSubmitted() + { + // We don't use addElement() for the form controls, so the form has no way of knowing + // that we do have a submit button and will always be submitted with autosubmit elements + return $this->hasBeenSent() && $this->getPopulatedValue('submit'); + } + + /** + * Populate form data from config + * + * @param BaseDashboard $dashboard + * + * @return void + */ + public function load(BaseDashboard $dashboard) + { + } + + /** + * Create custom form controls + * + * @return HtmlElement + */ + protected function createFormControls(): ValidHtml + { + return HtmlElement::create('div', ['class' => ['control-group', 'form-controls']]); + } + + /** + * Create a cancel button + * + * @return HtmlElement + */ + protected function createCancelButton(): ValidHtml + { + return HtmlElement::create('button', [ + 'type' => 'button', + 'class' => 'btn-cancel', + 'data-icinga-modal-cancel' => true + ])->setContent(t('Cancel')); + } + + /** + * Create a remove button + * + * @param Url $action + * @param string $label + * + * @return FormElement + */ + protected function createRemoveButton(Url $action, string $label): ValidHtml + { + return $this->createElement('submitButton', 'btn_remove', [ + 'class' => 'btn-remove', + 'label' => [new Icon('trash'), $label], + 'formaction' => (string) $action + ]); + } + + /** + * Create and register a submit button + * + * @param string $label + * + * @return FormElement + */ + protected function registerSubmitButton(string $label): ValidHtml + { + $submitElement = $this->createElement('submit', 'submit', ['class' => 'btn-primary', 'label' => $label]); + $this->registerElement($submitElement); + + return $submitElement; + } +} diff --git a/application/forms/Dashboard/DashletForm.php b/application/forms/Dashboard/DashletForm.php index 1af65a954a..68e07225a7 100644 --- a/application/forms/Dashboard/DashletForm.php +++ b/application/forms/Dashboard/DashletForm.php @@ -3,169 +3,329 @@ namespace Icinga\Forms\Dashboard; -use Icinga\Web\Form; -use Icinga\Web\Form\Validator\InternalUrlValidator; -use Icinga\Web\Form\Validator\UrlValidator; -use Icinga\Web\Url; -use Icinga\Web\Widget\Dashboard; -use Icinga\Web\Widget\Dashboard\Dashlet; - -/** - * Form to add an url a dashboard pane - */ -class DashletForm extends Form +use Exception; +use Icinga\Application\Logger; +use Icinga\Web\Dashboard\Common\BaseDashboard; +use Icinga\Web\Dashboard\Dashboard; +use Icinga\Web\Dashboard\DashboardHome; +use Icinga\Web\Notification; +use Icinga\Web\Dashboard\Dashlet; +use Icinga\Web\Dashboard\Pane; +use ipl\Html\HtmlElement; +use ipl\Web\Url; + +class DashletForm extends SetupNewDashboardForm { - /** - * @var Dashboard - */ - private $dashboard; - - /** - * Initialize this form - */ - public function init() + protected function init() { - $this->setName('form_dashboard_addurl'); - if (! $this->getSubmitLabel()) { - $this->setSubmitLabel($this->translate('Add To Dashboard')); - } - $this->setAction(Url::fromRequest()); + parent::init(); + + $this->setAction((string) Url::fromRequest()); + } + + public function load(BaseDashboard $dashboard) + { + $home = Url::fromRequest()->getParam('home'); + /** @var Dashlet $dashboard */ + $this->populate(array( + 'org_home' => $home, + 'org_pane' => $dashboard->getPane()->getName(), + 'org_dashlet' => $dashboard->getName(), + 'dashlet' => $dashboard->getTitle(), + 'url' => $dashboard->getUrl()->getRelativeUrl() + )); } - /** - * Build AddUrl form elements - * - * @see Form::createElements() - */ - public function createElements(array $formData) + protected function assembleNextPageDashboardPart() { - $groupElements = array(); - $panes = array(); + $requestUrl = Url::fromRequest(); - if ($this->dashboard) { - $panes = $this->dashboard->getPaneKeyTitleArray(); - } + $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()); - $sectionNameValidator = ['Callback', true, [ - 'callback' => function ($value) { - if (strpos($value, '[') === false && strpos($value, ']') === false) { - return true; + $panes = $firstHome->getEntryKeyTitleArr(); } - }, - 'messages' => [ - 'callbackValue' => $this->translate('Brackets ([, ]) cannot be used here') - ] - ]]; - - $this->addElement( - 'hidden', - 'org_pane', - array( - 'required' => false - ) - ); - - $this->addElement( - 'hidden', - 'org_dashlet', - array( - 'required' => false - ) - ); - - $this->addElement( - 'textarea', - 'url', - array( - 'required' => true, - 'label' => $this->translate('Url'), - 'description' => $this->translate( - 'Enter url to be loaded in the dashlet. You can paste the full URL, including filters.' - ), - 'validators' => array(new UrlValidator(), new InternalUrlValidator()) - ) - ); - $this->addElement( - 'text', - 'dashlet', - array( - 'required' => true, - 'label' => $this->translate('Dashlet Title'), - 'description' => $this->translate('Enter a title for the dashlet.'), - 'validators' => [$sectionNameValidator] - ) - ); - $this->addElement( - 'note', - 'note', - array( - 'decorators' => array( - array('HtmlTag', array('tag' => 'hr')) - ) - ) - ); - $this->addElement( - 'checkbox', - 'create_new_pane', - array( - 'autosubmit' => true, - 'required' => false, - 'label' => $this->translate('New dashboard'), - 'description' => $this->translate('Check this box if you want to add the dashlet to a new dashboard') - ) - ); - if (empty($panes) || ((isset($formData['create_new_pane']) && $formData['create_new_pane'] != false))) { - $this->addElement( - 'text', - 'pane', - array( - 'required' => true, - 'label' => $this->translate('New Dashboard Title'), - 'description' => $this->translate('Enter a title for the new dashboard'), - 'validators' => [$sectionNameValidator] - ) - ); - } else { - $this->addElement( - 'select', - 'pane', - array( - 'required' => true, - 'label' => $this->translate('Dashboard'), - 'multiOptions' => $panes, - 'description' => $this->translate('Select a dashboard you want to add the dashlet to') - ) - ); + } else { + $panes = $activeHome->loadDashboardEntries()->getEntryKeyTitleArr(); + } + } elseif ($this->dashboard->hasEntry($populatedHome)) { + $this->dashboard->loadDashboardEntries($populatedHome); + + $panes = $this->dashboard->getActiveHome()->getEntryKeyTitleArr(); } - } - /** - * @param \Icinga\Web\Widget\Dashboard $dashboard - */ - public function setDashboard(Dashboard $dashboard) - { - $this->dashboard = $dashboard; + $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->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.') + ]); + } + + $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'); + } + + $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.'), + ]); + } } - /** - * @return \Icinga\Web\Widget\Dashboard - */ - public function getDashboard() + protected function assemble() { - return $this->dashboard; + if ($this->isUpdatingADashlet() || $this->getPopulatedValue('btn_next') || $this->hasBeenSent()) { + $this->assembleNextPage(); + + if ($this->isUpdatingADashlet()) { + $targetUrl = (clone Url::fromRequest())->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 { + $formControls = $this->createFormControls(); + $formControls->add([ + $this->registerSubmitButton(t('Add to Dashboard')), + $this->createCancelButton() + ]); + + $this->addHtml($formControls); + } + } else { + // Just setup the initial view of the modal view + parent::assemble(); + } } - /** - * @param Dashlet $dashlet - */ - public function load(Dashlet $dashlet) + protected function onSuccess() { - $this->populate(array( - 'pane' => $dashlet->getPane()->getTitle(), - 'org_pane' => $dashlet->getPane()->getName(), - 'dashlet' => $dashlet->getTitle(), - 'org_dashlet' => $dashlet->getName(), - 'url' => $dashlet->getUrl()->getRelativeUrl() - )); + $conn = Dashboard::getConn(); + $dashboard = $this->dashboard; + + $selectedHome = $this->getPopulatedValue('home'); + if (! $selectedHome || $selectedHome === self::CREATE_NEW_HOME) { + $selectedHome = $this->getPopulatedValue('new_home'); + } + + $selectedPane = $this->getPopulatedValue('pane'); + // If "pane" element is disabled, there will be no populated value for it + if (! $selectedPane || $selectedPane === self::CREATE_NEW_PANE) { + $selectedPane = $this->getPopulatedValue('new_pane'); + } + + if (! $this->isUpdatingADashlet()) { + $currentHome = new DashboardHome($selectedHome); + if ($dashboard->hasEntry($currentHome->getName())) { + $currentHome = clone $dashboard->getEntry($currentHome->getName()); + if ($currentHome->getName() !== $dashboard->getActiveHome()->getName()) { + $currentHome->setActive(); + $currentHome->loadDashboardEntries(); + } + } + + $currentPane = new Pane($selectedPane); + if ($currentHome->hasEntry($currentPane->getName())) { + $currentPane = clone $currentHome->getEntry($currentPane->getName()); + } + + $customDashlet = null; + if (($dashlet = $this->getPopulatedValue('dashlet')) && ($url = $this->getPopulatedValue('url'))) { + $customDashlet = new Dashlet($dashlet, $url, $currentPane); + + 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(); + + try { + $dashboard->manageEntry($currentHome); + $currentHome->manageEntry($currentPane); + + if ($customDashlet) { + $currentPane->manageEntry($customDashlet); + } + + $this->dumpArbitaryDashlets(false); + // 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()) { + 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) { + Logger::error($err); + $conn->rollBackTransaction(); + + throw $err; + } + + Notification::success(t('Created dashlet(s) successfully')); + } else { + $orgHome = $dashboard->getEntry($this->getValue('org_home')); + $orgPane = $orgHome->getEntry($this->getValue('org_pane')); + $orgDashlet = $orgPane->getEntry($this->getValue('org_dashlet')); + + $currentHome = new DashboardHome($selectedHome); + if ($dashboard->hasEntry($currentHome->getName())) { + $currentHome = clone $dashboard->getEntry($currentHome->getName()); + $activeHome = $dashboard->getActiveHome(); + if ($currentHome->getName() !== $activeHome->getName()) { + $currentHome->setActive(); + $currentHome->loadDashboardEntries(); + } + } + + $currentPane = new Pane($selectedPane); + if ($currentHome->hasEntry($currentPane->getName())) { + $currentPane = clone $currentHome->getEntry($currentPane->getName()); + } + + $currentPane->setHome($currentHome); + // When the user wishes to create a new dashboard pane, we have to explicitly reset the dashboard panes + // of the original home, so that it isn't considered as we want to move the pane even though it isn't + // supposed to when the original home contains a dashboard with the same name + // @see DashboardHome::manageEntries() for details + $selectedPane = $this->getPopulatedValue('pane'); + if ((! $selectedPane || $selectedPane === self::CREATE_NEW_PANE) + && ! $currentHome->hasEntry($currentPane->getName())) { + $orgHome->setEntries([]); + } + + $currentDashlet = clone $orgDashlet; + $currentDashlet + ->setPane($currentPane) + ->setUrl($this->getValue('url')) + ->setTitle($this->getValue('dashlet')); + + if ($orgPane->getName() !== $currentPane->getName() + && $currentPane->hasEntry($currentDashlet->getName())) { + Notification::error(sprintf( + t('Failed to move dashlet "%s": Dashlet already exists within the "%s" dashboard pane'), + $currentDashlet->getTitle(), + $currentPane->getTitle() + )); + + return; + } + + $paneDiff = array_filter(array_diff_assoc($currentPane->toArray(), $orgPane->toArray())); + $dashletDiff = array_filter( + array_diff_assoc($currentDashlet->toArray(), $orgDashlet->toArray()), + function ($val) { + return $val !== null; + } + ); + + // Prevent meaningless updates when there weren't any changes, + // e.g. when the user just presses the update button without changing anything + if (empty($dashletDiff) && empty($paneDiff)) { + return; + } + + if (empty($paneDiff)) { + // No dashboard diff means the dashlet is still in the same pane, so just + // reset the dashlets of the original pane + $orgPane->setEntries([]); + } + + $conn->beginTransaction(); + + try { + $dashboard->manageEntry($currentHome); + $currentHome->manageEntry($currentPane, $orgHome); + $currentPane->manageEntry($currentDashlet, $orgPane); + + $conn->commitTransaction(); + } catch (Exception $err) { + Logger::error($err); + $conn->rollBackTransaction(); + + throw $err; + } + + Notification::success(sprintf(t('Updated dashlet "%s" successfully'), $currentDashlet->getTitle())); + } } } diff --git a/application/forms/Dashboard/HomePaneForm.php b/application/forms/Dashboard/HomePaneForm.php new file mode 100644 index 0000000000..d17f909f48 --- /dev/null +++ b/application/forms/Dashboard/HomePaneForm.php @@ -0,0 +1,151 @@ +dashboard->getActiveHome(); + $requestUrl = Url::fromRequest(); + $removeTargetUrl = (clone $requestUrl)->setPath(Dashboard::BASE_ROUTE . '/remove-home'); + + $this->addElement('hidden', 'org_name', ['required' => false]); + $this->addElement('hidden', 'org_title', ['required' => false]); + + if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/edit-pane') { + $titleDesc = t('Edit the title of this dashboard pane'); + $buttonLabel = t('Update Pane'); + $removeButtonLabel = t('Remove Pane'); + } + + $this->addElement('text', 'title', [ + 'required' => true, + 'label' => t('Title'), + 'description' => $titleDesc + ]); + + if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/edit-pane') { + $removeTargetUrl = (clone $requestUrl)->setPath(Dashboard::BASE_ROUTE . '/remove-pane'); + + $homes = $this->dashboard->getEntryKeyTitleArr(); + $populatedHome = $this->getPopulatedValue('home', $activeHome->getName()); + + $this->addElement('select', 'home', [ + 'class' => 'autosubmit', + 'required' => true, + 'value' => $populatedHome, + 'multiOptions' => array_merge([self::CREATE_NEW_HOME => self::CREATE_NEW_HOME], $homes), + 'label' => t('Assign to Home'), + 'description' => t('Select a dashboard home you want to move the dashboard to.'), + ]); + + if (empty($homes) || $this->getPopulatedValue('home') === self::CREATE_NEW_HOME) { + $this->addElement('text', 'new_home', [ + 'required' => true, + 'label' => t('Dashboard Home'), + 'placeholder' => t('Enter dashboard home title'), + 'description' => t('Enter a title for the new dashboard home.'), + ]); + } + } + + $formControls = $this->createFormControls(); + $formControls->add([ + $this->registerSubmitButton($buttonLabel), + $this->createRemoveButton($removeTargetUrl, $removeButtonLabel), + $this->createCancelButton() + ]); + + $this->addHtml($formControls); + } + + protected function onSuccess() + { + $requestUrl = Url::fromRequest(); + if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/edit-pane') { + $orgHome = $this->dashboard->getEntry($requestUrl->getParam('home')); + + $selectedHome = $this->getPopulatedValue('home'); + if (! $selectedHome || $selectedHome === self::CREATE_NEW_HOME) { + $selectedHome = $this->getPopulatedValue('new_home'); + } + + $currentHome = new DashboardHome($selectedHome); + if ($this->dashboard->hasEntry($currentHome->getName())) { + /** @var DashboardHome $currentHome */ + $currentHome = clone $this->dashboard->getEntry($currentHome->getName()); + $activeHome = $this->dashboard->getActiveHome(); + if ($currentHome->getName() !== $activeHome->getName()) { + $currentHome->setActive()->loadDashboardEntries(); + } + } + + $currentPane = clone $orgHome->getEntry($this->getValue('org_name')); + $currentPane + ->setHome($currentHome) + ->setTitle($this->getValue('title')); + + if ($orgHome->getName() !== $currentHome->getName() && $currentHome->hasEntry($currentPane->getName())) { + Notification::error(sprintf( + t('Failed to move dashboard "%s": Dashbaord pane already exists within the "%s" dashboard home'), + $currentPane->getTitle(), + $currentHome->getTitle() + )); + + return; + } + + if ($currentHome->getName() === $orgHome->getName()) { + // There is no dashboard home diff so clear all the dashboard pane + // of the org home + $orgHome->setEntries([]); + } + + $conn = Dashboard::getConn(); + $conn->beginTransaction(); + + try { + $this->dashboard->manageEntry($currentHome); + $currentHome->manageEntry($currentPane, $orgHome); + // We have to update all the dashlet ids too sha1(username + home + pane + dashlet) + $currentPane->manageEntry($currentPane->getEntries()); + + $conn->commitTransaction(); + } catch (\Exception $err) { + Logger::error($err); + $conn->rollBackTransaction(); + } + + Notification::success(sprintf(t('Updated dashboard pane "%s" successfully'), $currentPane->getTitle())); + } else { + $home = $this->dashboard->getActiveHome(); + $home->setTitle($this->getValue('title')); + + $this->dashboard->manageEntry($home); + Notification::success(sprintf(t('Updated dashboard home "%s" successfully'), $home->getTitle())); + } + } + + public function load(BaseDashboard $dashboard) + { + $this->populate([ + 'org_title' => $dashboard->getTitle(), + 'title' => $dashboard->getTitle(), + 'org_name' => $dashboard->getName() + ]); + } +} diff --git a/application/forms/Dashboard/NewHomePaneForm.php b/application/forms/Dashboard/NewHomePaneForm.php new file mode 100644 index 0000000000..f686c61d37 --- /dev/null +++ b/application/forms/Dashboard/NewHomePaneForm.php @@ -0,0 +1,123 @@ +hasParam('home')) { + $this->populate(['home' => $requestUrl->getParam('home')]); + } + } + + protected function assemble() + { + $requestUrl = Url::fromRequest(); + if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/new-pane') { + $placeHolder = t('Create new Dashboard'); + $description = t('Add new dashboard to this home.'); + $btnLabel = t('Add Dashboard'); + } else { + $placeHolder = t('Create new Dashboard Home'); + $description = t('Add new dashboard home.'); + $btnLabel = t('Add Home'); + } + + $this->addElement('text', 'title', [ + 'required' => true, + 'label' => t('Title'), + 'placeholder' => $placeHolder, + 'description' => $description + ]); + + if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/new-pane') { + $homes = array_merge( + [self::CREATE_NEW_HOME => self::CREATE_NEW_HOME], + $this->dashboard->getEntryKeyTitleArr() + ); + $this->addElement('select', 'home', [ + 'required' => true, + 'class' => 'autosubmit', + 'value' => $requestUrl->getParam('home', reset($homes)), + 'multiOptions' => $homes, + 'label' => t('Assign to Home'), + 'description' => t('A dashboard home you want to assign the new dashboard to.'), + ]); + + if ($this->getPopulatedValue('home') === self::CREATE_NEW_HOME) { + $this->addElement('text', 'new_home', [ + 'required' => true, + 'label' => t('Dashboard Home'), + 'placeholder' => t('Enter dashboard home title'), + 'description' => t('Enter a title for the new dashboard home.'), + ]); + } + } + + $formControls = $this->createFormControls(); + $formControls->add([ + $this->registerSubmitButton($btnLabel), + $this->createCancelButton() + ]); + + $this->addHtml($formControls); + } + + protected function onSuccess() + { + $requestUrl = Url::fromRequest(); + $conn = Dashboard::getConn(); + + if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/new-pane') { + $selectedHome = $this->getPopulatedValue('home'); + if (! $selectedHome || $selectedHome === self::CREATE_NEW_HOME) { + $selectedHome = $this->getPopulatedValue('new_home'); + } + + $currentHome = new DashboardHome($selectedHome); + if ($this->dashboard->hasEntry($currentHome->getName())) { + $currentHome = clone $this->dashboard->getEntry($currentHome->getName()); + if ($currentHome->getName() !== $this->dashboard->getActiveHome()->getName()) { + $currentHome->setActive()->loadDashboardEntries(); + } + } + + $pane = new Pane($this->getPopulatedValue('title')); + $conn->beginTransaction(); + + try { + $this->dashboard->manageEntry($currentHome); + $currentHome->manageEntry($pane); + + $conn->commitTransaction(); + } catch (\Exception $err) { + $conn->rollBackTransaction(); + throw $err; + } + + Notification::success('Added dashboard successfully'); + } else { // New home + $home = new DashboardHome($this->getPopulatedValue('title')); + if ($this->dashboard->hasEntry($home->getName())) { + Notification::error(sprintf(t('Dashboard home "%s" already exists'), $home->getName())); + return; + } + + $this->dashboard->manageEntry($home); + + Notification::success('Added dashboard home successfully'); + } + } +} diff --git a/application/forms/Dashboard/RemoveDashletForm.php b/application/forms/Dashboard/RemoveDashletForm.php new file mode 100644 index 0000000000..3aad587166 --- /dev/null +++ b/application/forms/Dashboard/RemoveDashletForm.php @@ -0,0 +1,42 @@ +hasBeenSent() && $this->getPopulatedValue('btn_remove'); + } + + protected function assemble() + { + $this->addHtml(HtmlElement::create('h1', null, sprintf( + t('Please confirm removal of dashlet "%s"'), + Url::fromRequest()->getParam('dashlet') + ))); + + $submit = $this->registerSubmitButton(t('Remove Dashlet')); + $submit->setName('btn_remove'); + + $this->addHtml($submit); + } + + protected function onSuccess() + { + $requestUrl = Url::fromRequest(); + $home = $this->dashboard->getActiveHome(); + $pane = $home->getEntry($requestUrl->getParam('pane')); + + $dashlet = $requestUrl->getParam('dashlet'); + $pane->removeEntry($dashlet); + + Notification::success(sprintf(t('Removed dashlet "%s" successfully'), $dashlet)); + } +} diff --git a/application/forms/Dashboard/RemoveHomePaneForm.php b/application/forms/Dashboard/RemoveHomePaneForm.php new file mode 100644 index 0000000000..8116da81bc --- /dev/null +++ b/application/forms/Dashboard/RemoveHomePaneForm.php @@ -0,0 +1,53 @@ +hasBeenSent() && $this->getPopulatedValue('btn_remove'); + } + + protected function assemble() + { + $requestRoute = Url::fromRequest(); + $label = t('Remove Home'); + $message = sprintf(t('Please confirm removal of dashboard home "%s"'), $requestRoute->getParam('home')); + if ($requestRoute->getPath() === Dashboard::BASE_ROUTE . '/remove-pane') { + $label = t('Remove Pane'); + $message = sprintf(t('Please confirm removal of dashboard pane "%s"'), $requestRoute->getParam('pane')); + } + + $this->addHtml(HtmlElement::create('h1', null, $message)); + + $submit = $this->registerSubmitButton($label); + $submit->setName('btn_remove'); + + $this->addHtml($submit); + } + + protected function onSuccess() + { + $requestUrl = Url::fromRequest(); + $home = $this->dashboard->getActiveHome(); + + if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/remove-home') { + $this->dashboard->removeEntry($home); + + Notification::success(sprintf(t('Removed dashboard home "%s" successfully'), $home->getTitle())); + } else { + $pane = $home->getEntry($requestUrl->getParam('pane')); + $home->removeEntry($pane); + + Notification::success(sprintf(t('Removed dashboard pane "%s" successfully'), $pane->getTitle())); + } + } +} diff --git a/application/forms/Dashboard/SetupNewDashboardForm.php b/application/forms/Dashboard/SetupNewDashboardForm.php new file mode 100644 index 0000000000..43df2d84ba --- /dev/null +++ b/application/forms/Dashboard/SetupNewDashboardForm.php @@ -0,0 +1,320 @@ +setRedirectUrl((string) Url::fromPath(Dashboard::BASE_ROUTE)); + $this->setAction($this->getRedirectUrl() . '/setup-dashboard'); + } + + /** + * Dump all module dashlets which are not selected by the user + * from the member variable + * + * @param bool $strict Whether to match populated of the dashlet against a 'y' + * + * @return void + */ + protected function dumpArbitaryDashlets(bool $strict = true): void + { + $chosenDashlets = []; + foreach (self::$moduleDashlets as $module => $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); + } + + $chosenDashlets[$module][$dashlet->getName()] = $dashlet; + } + } + + if (isset($chosenDashlets[$module]) && ! $this->duplicateCustomDashlet) { + $this->duplicateCustomDashlet = array_key_exists( + $this->getPopulatedValue('dashlet'), + $chosenDashlets[$module] + ); + } + } + + self::$moduleDashlets = $chosenDashlets; + } + + /** + * 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() + { + $strict = $this->isUpdatingADashlet() || $this->getPopulatedValue('btn_next') || ! $this->hasBeenSent(); + $this->dumpArbitaryDashlets($strict); + $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('hidden', 'custom_url', ['required' => false, 'value' => 'y']); + $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); + } + + 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->manageEntry($home); + $home->manageEntry($pane); + + $this->dumpArbitaryDashlets(false); + + 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); + } + + $pane->manageEntry(self::$moduleDashlets); + + $conn->commitTransaction(); + } catch (\Exception $err) { + $conn->rollBackTransaction(); + throw $err; + } + + Notification::success(t('Added new dashlet(s) successfully')); + } + } + + /** + * 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/WelcomeForm.php b/application/forms/Dashboard/WelcomeForm.php new file mode 100644 index 0000000000..4951d900de --- /dev/null +++ b/application/forms/Dashboard/WelcomeForm.php @@ -0,0 +1,62 @@ +dashboard = $dashboard; + $this->setRedirectUrl((string) Url::fromPath(Dashboard::BASE_ROUTE)); + } + + public function hasBeenSubmitted() + { + return parent::hasBeenSubmitted() || $this->getPressedSubmitElement(); + } + + protected function assemble() + { + $element = $this->createElement('submit', 'btn_use_defaults', ['label' => t('Use System Defaults')]); + $this->registerElement($element)->decorate($element); + + $this->addElement('submit', 'btn_customize_dashlets', [ + 'label' => t('Add Dashlets Now'), + 'href' => Url::fromPath(Dashboard::BASE_ROUTE . '/setup-dashboard'), + 'data-icinga-modal' => true, + 'data-no-icinga-ajax' => true + ]); + + $this->getElement('btn_customize_dashlets')->setWrapper($element->getWrapper()); + } + + protected function onSuccess() + { + if ($this->getPopulatedValue('btn_use_defaults')) { + $home = $this->dashboard->getEntry(DashboardHome::DEFAULT_HOME); + $conn = Dashboard::getConn(); + $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); + + $conn->commitTransaction(); + } catch (\Exception $err) { + $conn->rollBackTransaction(); + throw $err; + } + } + } +} diff --git a/application/layouts/scripts/body.phtml b/application/layouts/scripts/body.phtml index 87b570bfe5..fea90ee7bc 100644 --- a/application/layouts/scripts/body.phtml +++ b/application/layouts/scripts/body.phtml @@ -1,5 +1,6 @@ layout()->inlineLayout) {
qlink( '', - Auth::getInstance()->isAuthenticated() ? 'dashboard' : '', + Auth::getInstance()->isAuthenticated() ? Dashboard::BASE_ROUTE : '', null, array( 'aria-hidden' => 'true', diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml index 1800f2c157..c4417c6c95 100644 --- a/application/layouts/scripts/layout.phtml +++ b/application/layouts/scripts/layout.phtml @@ -87,7 +87,7 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';