parent
5a9540aa95
commit
39c7451664
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Controllers;
|
||||
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Forms\Announcement\AcknowledgeAnnouncementForm;
|
||||
use Icinga\Forms\Announcement\AnnouncementForm;
|
||||
use Icinga\Web\Announcement\AnnouncementIniRepository;
|
||||
use Icinga\Web\Controller;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
class AnnouncementsController extends Controller
|
||||
{
|
||||
/**
|
||||
* List all announcements
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->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'));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Forms\Announcement;
|
||||
|
||||
use Icinga\Web\Announcement\AnnouncementCookie;
|
||||
use Icinga\Web\Form;
|
||||
|
||||
class AcknowledgeAnnouncementForm extends Form
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Forms\Announcement;
|
||||
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Forms\RepositoryForm;
|
||||
|
||||
/**
|
||||
* Create, update and delete announcements
|
||||
*/
|
||||
class AnnouncementForm extends RepositoryForm
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function createInsertElements(array $formData)
|
||||
{
|
||||
$this->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');
|
||||
}
|
||||
}
|
|
@ -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/*)'
|
||||
);
|
||||
|
||||
|
|
|
@ -18,6 +18,9 @@ if ($this->layout()->autorefreshInterval) {
|
|||
|
||||
?>
|
||||
<div id="header">
|
||||
<div id="announcements" class="container">
|
||||
<?= $this->widget('announcements') ?>
|
||||
</div>
|
||||
<div id="header-logo-container">
|
||||
<?= $this->qlink(
|
||||
'',
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<?php if (! $compact): ?>
|
||||
<div class="controls">
|
||||
<?= $tabs ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="content">
|
||||
<?php if ($this->hasPermission('admin')) {
|
||||
echo $this->qlink(
|
||||
$this->translate('Create a New Announcement') ,
|
||||
'announcements/new',
|
||||
null,
|
||||
array(
|
||||
'class' => 'button-link',
|
||||
'data-base-target' => '_next',
|
||||
'icon' => 'plus',
|
||||
'title' => $this->translate('Create a new announcement')
|
||||
)
|
||||
);
|
||||
} ?>
|
||||
<?php if (! $announcements->hasResult()): ?>
|
||||
<p><?= $this->translate('No announcements found.') ?></p>
|
||||
</div>
|
||||
<?php return; endif ?>
|
||||
<table data-base-target="_next" class="table-row-selectable common-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $this->translate('Author') ?></th>
|
||||
<th><?= $this->translate('Message') ?></th>
|
||||
<th><?= $this->translate('Start') ?></th>
|
||||
<th><?= $this->translate('End') ?></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($announcements as $announcement): /** @var object $announcement */ ?>
|
||||
<?php if ($this->hasPermission('admin')): ?>
|
||||
<tr href="<?= $this->href('announcements/update', array('id' => $announcement->id)) ?>">
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<?php endif ?>
|
||||
<td><?= $this->escape($announcement->author) ?></td>
|
||||
<td><?= $this->ellipsis($announcement->message, 100) ?></td>
|
||||
<td><?= $this->formatDateTime($announcement->start->getTimestamp()) ?></td>
|
||||
<td><?= $this->formatDateTime($announcement->end->getTimestamp()) ?></td>
|
||||
<?php if ($this->hasPermission('admin')): ?>
|
||||
<td class="icon-col"><?= $this->qlink(
|
||||
null,
|
||||
'announcements/remove',
|
||||
array('id' => $announcement->id),
|
||||
array(
|
||||
'class' => 'action-link',
|
||||
'icon' => 'cancel',
|
||||
'title' => $this->translate('Remove this announcement')
|
||||
)
|
||||
) ?></td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
<?= $this->widget('announcements') ?>
|
|
@ -304,7 +304,12 @@ class Web extends EmbeddedWeb
|
|||
'about' => array(
|
||||
'label' => t('About'),
|
||||
'url' => 'about',
|
||||
'priority' => 701
|
||||
'priority' => 700
|
||||
),
|
||||
'announcements' => array(
|
||||
'label' => t('Announcements'),
|
||||
'url' => 'announcements',
|
||||
'priority' => 710
|
||||
)
|
||||
)
|
||||
),
|
||||
|
@ -366,7 +371,7 @@ class Web extends EmbeddedWeb
|
|||
'label' => t('Application Log'),
|
||||
'url' => 'list/applicationlog',
|
||||
'permission' => 'application/log',
|
||||
'priority' => 710
|
||||
'priority' => 900
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web;
|
||||
|
||||
/**
|
||||
* An announcement to be displayed prominently in the web UI
|
||||
*/
|
||||
class Announcement
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $author;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $start;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $end;
|
||||
|
||||
/**
|
||||
* Hash of the message
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $hash = null;
|
||||
|
||||
/**
|
||||
* Announcement constructor
|
||||
*
|
||||
* @param array $properties
|
||||
*/
|
||||
public function __construct(array $properties = array())
|
||||
{
|
||||
foreach ($properties as $key => $value) {
|
||||
$method = 'set' . ucfirst($key);
|
||||
if (method_exists($this, $method)) {
|
||||
$this->$method($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the author of the acknowledged
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the author of the acknowledged
|
||||
*
|
||||
* @param string $author
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAuthor($author)
|
||||
{
|
||||
$this->author = $author;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message of the acknowledged
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message of the acknowledged
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMessage($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->hash = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start date and time of the acknowledged
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStart()
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start date and time of the acknowledged
|
||||
*
|
||||
* @param int $start
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStart($start)
|
||||
{
|
||||
$this->start = $start;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end date and time of the acknowledged
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEnd()
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the end date and time of the acknowledged
|
||||
*
|
||||
* @param int $end
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEnd($end)
|
||||
{
|
||||
$this->end = $end;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash of the acknowledgement
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
if ($this->hash === null) {
|
||||
$this->hash = md5($this->message);
|
||||
}
|
||||
return $this->hash;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Announcement;
|
||||
|
||||
use Icinga\Web\Cookie;
|
||||
|
||||
/**
|
||||
* Handle acknowledged announcements via cookie
|
||||
*/
|
||||
class AnnouncementCookie extends Cookie
|
||||
{
|
||||
/**
|
||||
* Array of hashes representing acknowledged announcements
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $acknowledged = array();
|
||||
|
||||
/**
|
||||
* ETag of the last known announcements.ini
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $etag;
|
||||
|
||||
/**
|
||||
* Timestamp of the next active acknowledgement, if any
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $nextActive;
|
||||
|
||||
/**
|
||||
* AnnouncementCookie constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('icingaweb2-announcements');
|
||||
$this->setExpire(2147483648);
|
||||
if (isset($_COOKIE['icingaweb2-announcements'])) {
|
||||
$cookie = json_decode($_COOKIE['icingaweb2-announcements'], true);
|
||||
if ($cookie !== null) {
|
||||
if (isset($cookie['acknowledged'])) {
|
||||
$this->setAcknowledged($cookie['acknowledged']);
|
||||
}
|
||||
if (isset($cookie['etag'])) {
|
||||
$this->setEtag($cookie['etag']);
|
||||
}
|
||||
if (isset($cookie['next'])) {
|
||||
$this->setNextActive($cookie['next']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hashes of the acknowledged announcements
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAcknowledged()
|
||||
{
|
||||
return $this->acknowledged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hashes of the acknowledged announcements
|
||||
*
|
||||
* @param string[] $acknowledged
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAcknowledged(array $acknowledged)
|
||||
{
|
||||
$this->acknowledged = $acknowledged;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ETag
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEtag()
|
||||
{
|
||||
return $this->etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ETag
|
||||
*
|
||||
* @param string $etag
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEtag($etag)
|
||||
{
|
||||
$this->etag = $etag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of the next active announcement
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNextActive()
|
||||
{
|
||||
return $this->nextActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timestamp of the next active announcement
|
||||
*
|
||||
* @param int $nextActive
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setNextActive($nextActive)
|
||||
{
|
||||
$this->nextActive = $nextActive;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return json_encode(array(
|
||||
'acknowledged' => $this->getAcknowledged(),
|
||||
'etag' => $this->getEtag(),
|
||||
'next' => $this->getNextActive()
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Announcement;
|
||||
|
||||
use DateTime;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filter\FilterAnd;
|
||||
use Icinga\Data\SimpleQuery;
|
||||
use Icinga\Repository\IniRepository;
|
||||
use Icinga\Web\Announcement;
|
||||
|
||||
/**
|
||||
* A collection of announcements stored in an INI file
|
||||
*/
|
||||
class AnnouncementIniRepository extends IniRepository
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $queryColumns = array('announcement' => array('id', 'author', 'message', 'hash', 'start', 'end'));
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $triggers = array('announcement');
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected $configs = array('announcement' => array(
|
||||
'name' => 'announcements',
|
||||
'keyColumn' => 'id'
|
||||
));
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected $conversionRules = array('announcement' => array(
|
||||
'start' => 'timestamp',
|
||||
'end' => 'timestamp'
|
||||
));
|
||||
|
||||
/**
|
||||
* Create a DateTime from a timestamp
|
||||
*
|
||||
* @param string $timestamp
|
||||
*
|
||||
* @return DateTime|null
|
||||
*/
|
||||
protected function retrieveTimestamp($timestamp)
|
||||
{
|
||||
if ($timestamp !== null) {
|
||||
$dateTime = new DateTime();
|
||||
$dateTime->setTimestamp($timestamp);
|
||||
return $dateTime;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a DateTime's timestamp
|
||||
*
|
||||
* @param DateTime $datetime
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
protected function persistTimestamp(DateTime $datetime)
|
||||
{
|
||||
return $datetime === null ? null : $datetime->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Before-insert trigger (per row)
|
||||
*
|
||||
* @param ConfigObject $new The original data to insert
|
||||
*
|
||||
* @return ConfigObject The eventually modified data to insert
|
||||
*/
|
||||
protected function onInsertAnnouncement(ConfigObject $new)
|
||||
{
|
||||
if (! isset($new->id)) {
|
||||
$new->id = uniqid();
|
||||
}
|
||||
if (! isset($new->hash)) {
|
||||
$announcement = new Announcement($new->toArray());
|
||||
$new->hash = $announcement->getHash();
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Before-update trigger (per row)
|
||||
*
|
||||
* @param ConfigObject $old The original data as currently stored
|
||||
* @param ConfigObject $new The original data to update
|
||||
*
|
||||
* @return ConfigObject The eventually modified data to update
|
||||
*/
|
||||
protected function onUpdateAnnouncement(ConfigObject $old, ConfigObject $new)
|
||||
{
|
||||
if ($new->message !== $old->message) {
|
||||
$announcement = new Announcement($new->toArray());
|
||||
$new->hash = $announcement->getHash();
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ETag of the announcements.ini file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEtag()
|
||||
{
|
||||
$file = $this->getDataSource('announcement')->getConfigFile();
|
||||
$mtime = filemtime($file);
|
||||
$size = filesize($file);
|
||||
return hash('crc32', $mtime . $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query for all active announcements
|
||||
*
|
||||
* @return SimpleQuery
|
||||
*/
|
||||
public function findActive()
|
||||
{
|
||||
$now = new DateTime();
|
||||
$query = $this
|
||||
->select(array('hash', 'message'))
|
||||
->setFilter(new FilterAnd(array(
|
||||
Filter::expression('start', '<=', $now),
|
||||
Filter::expression('end', '>=', $now)
|
||||
)))
|
||||
->order('start');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of the next active announcement
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function findNextActive()
|
||||
{
|
||||
$now = new DateTime();
|
||||
$query = $this
|
||||
->select(array('start'))
|
||||
->setFilter(Filter::expression('start', '>', $now))
|
||||
->order('start')
|
||||
->limit(1);
|
||||
$nextActive = $query->fetchRow();
|
||||
return $nextActive !== false ? $nextActive->start->getTimestamp() : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Widget;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Forms\Announcement\AcknowledgeAnnouncementForm;
|
||||
use Icinga\Web\Announcement\AnnouncementCookie;
|
||||
use Icinga\Web\Announcement\AnnouncementIniRepository;
|
||||
|
||||
/**
|
||||
* Render announcements
|
||||
*/
|
||||
class Announcements extends AbstractWidget
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$repo = new AnnouncementIniRepository();
|
||||
$etag = $repo->getEtag();
|
||||
$cookie = new AnnouncementCookie();
|
||||
if ($cookie->getEtag() !== $etag) {
|
||||
$cookie->setEtag($etag);
|
||||
$cookie->setNextActive($repo->findNextActive());
|
||||
Icinga::app()->getResponse()->setCookie($cookie);
|
||||
}
|
||||
$acked = array();
|
||||
foreach ($cookie->getAcknowledged() as $hash) {
|
||||
$acked[] = Filter::expression('hash', '!=', $hash);
|
||||
}
|
||||
$acked = Filter::matchAll($acked);
|
||||
$announcements = $repo->findActive();
|
||||
$announcements->applyFilter($acked);
|
||||
if ($announcements->hasResult()) {
|
||||
$html = '<ul role="alert" id="announcements">';
|
||||
foreach ($announcements as $announcement) {
|
||||
$ackForm = new AcknowledgeAnnouncementForm();
|
||||
$ackForm->populate(array('hash' => $announcement->hash));
|
||||
$html .= '<li><div>'
|
||||
. $this->view()->escape($announcement->message)
|
||||
. '</div>'
|
||||
. $ackForm
|
||||
. '</li>';
|
||||
}
|
||||
$html .= '</ul>';
|
||||
return $html;
|
||||
}
|
||||
// Force container update on XHR
|
||||
return '<div style="display: none;"></div>';
|
||||
}
|
||||
}
|
|
@ -64,8 +64,9 @@ input.search {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(el): .form-inline control-group { display: inline-block; }
|
||||
form.inline {
|
||||
// TODO(el): .form-controls-inline control-group { display: inline-block; }
|
||||
form.inline,
|
||||
.form-inline {
|
||||
.control-group {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,50 @@
|
|||
/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
|
||||
|
||||
#announcements > ul {
|
||||
background-color: @body-bg-color;
|
||||
font-size: @font-size;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
> li:before {
|
||||
color: @icinga-blue;
|
||||
content: "\e811";
|
||||
font-family: 'ifont';
|
||||
left: 1em;
|
||||
margin-top: -0.8em;
|
||||
padding: 0.3em;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
> li {
|
||||
border-bottom: 1px solid @gray-lighter;
|
||||
padding: 1em 3em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
> li .message {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
> li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.acknowledge-announcement-control {
|
||||
background: none;
|
||||
border: none;
|
||||
display: block;
|
||||
margin-top: -0.6em;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
table.historycolorgrid {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
|
|
@ -445,6 +445,10 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (req.getResponseHeader('X-Icinga-Announcements') === 'refresh') {
|
||||
_this.loadUrl(_this.url('/layout/announcements'), $('#announcements'));
|
||||
}
|
||||
|
||||
// div helps getting an XML tree
|
||||
var $resp = $('<div>' + req.responseText + '</div>');
|
||||
var active = false;
|
||||
|
|
Loading…
Reference in New Issue