From 39c7451664f950e6a5a25f72d1ace0f8dcc060d6 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 29 Nov 2016 15:12:33 +0100 Subject: [PATCH] Implement announcements refs #11198 --- .../controllers/AnnouncementsController.php | 100 +++++++++++ .../ApplicationStateController.php | 22 ++- application/controllers/LayoutController.php | 5 + .../AcknowledgeAnnouncementForm.php | 79 +++++++++ .../forms/Announcement/AnnouncementForm.php | 117 +++++++++++++ application/forms/Security/RoleForm.php | 3 + application/layouts/scripts/body.phtml | 3 + .../views/scripts/announcements/index.phtml | 61 +++++++ .../views/scripts/layout/announcements.phtml | 1 + library/Icinga/Application/Web.php | 9 +- library/Icinga/Web/Announcement.php | 158 +++++++++++++++++ .../Web/Announcement/AnnouncementCookie.php | 137 +++++++++++++++ .../AnnouncementIniRepository.php | 159 ++++++++++++++++++ library/Icinga/Web/Widget/Announcements.php | 54 ++++++ public/css/icinga/forms.less | 5 +- public/css/icinga/widgets.less | 45 +++++ public/js/icinga/loader.js | 4 + 17 files changed, 957 insertions(+), 5 deletions(-) create mode 100644 application/controllers/AnnouncementsController.php create mode 100644 application/forms/Announcement/AcknowledgeAnnouncementForm.php create mode 100644 application/forms/Announcement/AnnouncementForm.php create mode 100644 application/views/scripts/announcements/index.phtml create mode 100644 application/views/scripts/layout/announcements.phtml create mode 100644 library/Icinga/Web/Announcement.php create mode 100644 library/Icinga/Web/Announcement/AnnouncementCookie.php create mode 100644 library/Icinga/Web/Announcement/AnnouncementIniRepository.php create mode 100644 library/Icinga/Web/Widget/Announcements.php diff --git a/application/controllers/AnnouncementsController.php b/application/controllers/AnnouncementsController.php new file mode 100644 index 000000000..458b9a40a --- /dev/null +++ b/application/controllers/AnnouncementsController.php @@ -0,0 +1,100 @@ +getTabs()->add( + 'announcements', + array( + 'active' => true, + 'label' => $this->translate('Announcements'), + 'title' => $this->translate('List All Announcements'), + 'url' => Url::fromPath('announcements') + ) + ); + + $repo = new AnnouncementIniRepository(); + $this->view->announcements = $repo + ->select(array('id', 'author', 'message', 'start', 'end')) + ->order('start'); + } + + /** + * Create an announcement + */ + public function newAction() + { + $this->assertPermission('admin'); + + $form = $this->prepareForm()->add(); + $form->handleRequest(); + $this->renderForm($form, $this->translate('New Announcement')); + } + + /** + * Update an announcement + */ + public function updateAction() + { + $this->assertPermission('admin'); + + $form = $this->prepareForm()->edit($this->params->getRequired('id')); + try { + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound($this->translate('Announcement not found')); + } + $this->renderForm($form, $this->translate('Update Announcement')); + } + + /** + * Remove an announcement + */ + public function removeAction() + { + $this->assertPermission('admin'); + + $form = $this->prepareForm()->remove($this->params->getRequired('id')); + try { + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound($this->translate('Announcement not found')); + } + $this->renderForm($form, $this->translate('Remove Announcement')); + } + + public function acknowledgeAction() + { + $this->assertHttpMethod('POST'); + $this->getResponse()->setHeader('X-Icinga-Container', 'ignore', true); + $form = new AcknowledgeAnnouncementForm(); + $form->handleRequest(); + } + + /** + * Assert permission admin and return a prepared RepositoryForm + * + * @return AnnouncementForm + */ + protected function prepareForm() + { + $form = new AnnouncementForm(); + return $form + ->setRepository(new AnnouncementIniRepository()) + ->setRedirectUrl(Url::fromPath('announcements')); + } +} diff --git a/application/controllers/ApplicationStateController.php b/application/controllers/ApplicationStateController.php index caae26f54..cba019843 100644 --- a/application/controllers/ApplicationStateController.php +++ b/application/controllers/ApplicationStateController.php @@ -4,6 +4,8 @@ namespace Icinga\Controllers; use Icinga\Application\Icinga; +use Icinga\Web\Announcement\AnnouncementCookie; +use Icinga\Web\Announcement\AnnouncementIniRepository; use Icinga\Web\Controller; use Icinga\Web\Session; @@ -14,6 +16,7 @@ class ApplicationStateController extends Controller { public function indexAction() { + $this->_helper->layout()->disableLayout(); if (isset($_COOKIE['icingaweb2-session'])) { $last = (int) $_COOKIE['icingaweb2-session']; } else { @@ -34,6 +37,23 @@ class ApplicationStateController extends Controller ); $_COOKIE['icingaweb2-session'] = $now; } - Icinga::app()->getResponse()->setHeader('X-Icinga-Container', 'ignore', true); + $announcementCookie = new AnnouncementCookie(); + $announcementRepo = new AnnouncementIniRepository(); + if ($announcementCookie->getEtag() !== $announcementRepo->getEtag()) { + $announcementCookie + ->setEtag($announcementRepo->getEtag()) + ->setNextActive($announcementRepo->findNextActive()); + $this->getResponse()->setCookie($announcementCookie); + $this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true); + } else { + $nextActive = $announcementCookie->getNextActive(); + if ($nextActive && $nextActive <= $now) { + $announcementCookie->setNextActive($announcementRepo->findNextActive()); + $this->getResponse()->setCookie($announcementCookie); + $this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true); + } + } + + $this->getResponse()->setHeader('X-Icinga-Container', 'ignore', true); } } diff --git a/application/controllers/LayoutController.php b/application/controllers/LayoutController.php index f7bbd5102..32295a688 100644 --- a/application/controllers/LayoutController.php +++ b/application/controllers/LayoutController.php @@ -20,4 +20,9 @@ class LayoutController extends ActionController $this->_helper->layout()->disableLayout(); $this->view->menuRenderer = Icinga::app()->getMenu()->getRenderer(); } + + public function announcementsAction() + { + $this->_helper->layout()->disableLayout(); + } } diff --git a/application/forms/Announcement/AcknowledgeAnnouncementForm.php b/application/forms/Announcement/AcknowledgeAnnouncementForm.php new file mode 100644 index 000000000..f4b86fe73 --- /dev/null +++ b/application/forms/Announcement/AcknowledgeAnnouncementForm.php @@ -0,0 +1,79 @@ +setAction('announcements/acknowledge'); + $this->setAttrib('class', 'form-inline acknowledge-announcement-control'); + $this->setRedirectUrl('layout/announcements'); + } + + /** + * {@inheritdoc} + */ + public function addSubmitButton() + { + $this->addElement( + 'button', + 'btn_submit', + array( + 'class' => 'link-button spinner', + 'decorators' => array( + 'ViewHelper', + array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls')) + ), + 'escape' => false, + 'ignore' => true, + 'label' => $this->getView()->icon('cancel'), + 'title' => $this->translate('Acknowledge this announcement'), + 'type' => 'submit' + ) + ); + return $this; + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData = array()) + { + $this->addElements( + array( + array( + 'hidden', + 'hash', + array( + 'required' => true, + 'validators' => array('NotEmpty'), + 'decorators' => array('ViewHelper') + ) + ) + ) + ); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function onSuccess() + { + $cookie = new AnnouncementCookie(); + $acknowledged = $cookie->getAcknowledged(); + $acknowledged[] = $this->getElement('hash')->getValue(); + $cookie->setAcknowledged($acknowledged); + $this->getResponse()->setCookie($cookie); + return true; + } +} diff --git a/application/forms/Announcement/AnnouncementForm.php b/application/forms/Announcement/AnnouncementForm.php new file mode 100644 index 000000000..2c712337c --- /dev/null +++ b/application/forms/Announcement/AnnouncementForm.php @@ -0,0 +1,117 @@ +addElement( + 'text', + 'author', + array( + 'required' => true, + 'value' => Auth::getInstance()->getUser()->getUsername(), + 'disabled' => true + ) + ); + $this->addElement( + 'textarea', + 'message', + array( + 'required' => true, + 'label' => $this->translate('Message'), + 'description' => $this->translate('The message to display to users') + ) + ); + $this->addElement( + 'dateTimePicker', + 'start', + array( + 'required' => true, + 'label' => $this->translate('Start'), + 'description' => $this->translate('The time to display the announcement from') + ) + ); + $this->addElement( + 'dateTimePicker', + 'end', + array( + 'required' => true, + 'label' => $this->translate('End'), + 'description' => $this->translate('The time to display the announcement until') + ) + ); + + $this->setTitle($this->translate('Create a new announcement')); + $this->setSubmitLabel($this->translate('Create')); + } + + /** + * {@inheritDoc} + */ + protected function createUpdateElements(array $formData) + { + $this->createInsertElements($formData); + $this->setTitle(sprintf($this->translate('Edit announcement %s'), $this->getIdentifier())); + $this->setSubmitLabel($this->translate('Save')); + } + + /** + * {@inheritDoc} + */ + protected function createDeleteElements(array $formData) + { + $this->setTitle(sprintf($this->translate('Remove announcement %s?'), $this->getIdentifier())); + $this->setSubmitLabel($this->translate('Yes')); + } + + /** + * {@inheritDoc} + */ + protected function createFilter() + { + return Filter::where('id', $this->getIdentifier()); + } + + /** + * {@inheritDoc} + */ + protected function getInsertMessage($success) + { + return $success + ? $this->translate('Announcement created') + : $this->translate('Failed to create announcement'); + } + + /** + * {@inheritDoc} + */ + protected function getUpdateMessage($success) + { + return $success + ? $this->translate('Announcement updated') + : $this->translate('Failed to update announcement'); + } + + /** + * {@inheritDoc} + */ + protected function getDeleteMessage($success) + { + return $success + ? $this->translate('Announcement removed') + : $this->translate('Failed to remove announcement'); + } +} diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index 1873123c2..28330130f 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -44,6 +44,9 @@ class RoleForm extends ConfigForm ) . ' (application/stacktraces)', 'application/log' => $this->translate('Allow to view the application log') . ' (application/log)', + 'admin' => $this->translate( + 'Grant admin permissions, e.g. manage announcements' + ) . ' (admin)', 'config/*' => $this->translate('Allow config access') . ' (config/*)' ); diff --git a/application/layouts/scripts/body.phtml b/application/layouts/scripts/body.phtml index 0423fc453..792fa69d9 100644 --- a/application/layouts/scripts/body.phtml +++ b/application/layouts/scripts/body.phtml @@ -18,6 +18,9 @@ if ($this->layout()->autorefreshInterval) { ?>