mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-28 08:14:03 +02:00
Merge branch 'master' into feature/api-9606
This commit is contained in:
commit
96fb3b5d4b
@ -131,7 +131,7 @@ class GroupController extends AuthBackendController
|
||||
$removeForm->addElement('button', 'btn_submit', array(
|
||||
'escape' => false,
|
||||
'type' => 'submit',
|
||||
'class' => 'link-like',
|
||||
'class' => 'link-like spinner',
|
||||
'value' => 'btn_submit',
|
||||
'decorators' => array('ViewHelper'),
|
||||
'label' => $this->view->icon('trash'),
|
||||
|
@ -137,7 +137,7 @@ class UserController extends AuthBackendController
|
||||
$removeForm->addElement('button', 'btn_submit', array(
|
||||
'escape' => false,
|
||||
'type' => 'submit',
|
||||
'class' => 'link-like',
|
||||
'class' => 'link-like spinner',
|
||||
'value' => 'btn_submit',
|
||||
'decorators' => array('ViewHelper'),
|
||||
'label' => $this->view->icon('trash'),
|
||||
|
@ -27,6 +27,7 @@ class LoginForm extends Form
|
||||
$this->setRequiredCue(null);
|
||||
$this->setName('form_login');
|
||||
$this->setSubmitLabel($this->translate('Login'));
|
||||
$this->setProgressLabel($this->translate('Logging in'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,6 +25,20 @@ class ApplicationConfigForm extends Form
|
||||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
$this->addElement(
|
||||
'checkbox',
|
||||
'global_show_stacktraces',
|
||||
array(
|
||||
'required' => true,
|
||||
'value' => true,
|
||||
'label' => $this->translate('Show Stacktraces'),
|
||||
'description' => $this->translate(
|
||||
'Set whether to show an exception\'s stacktrace by default. This can also'
|
||||
. ' be set in a user\'s preferences with the appropriate permission.'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->addElement(
|
||||
'text',
|
||||
'global_module_path',
|
||||
|
@ -81,22 +81,6 @@ class LdapResourceForm extends Form
|
||||
)
|
||||
);
|
||||
|
||||
if (isset($formData['encryption']) && $formData['encryption'] !== 'none') {
|
||||
// TODO(jom): Do not show this checkbox unless the connection is actually failing due to certificate errors
|
||||
$this->addElement(
|
||||
'checkbox',
|
||||
'reqcert',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => $this->translate('Require Certificate'),
|
||||
'description' => $this->translate(
|
||||
'When checked, the LDAP server must provide a valid and known (trusted) certificate.'
|
||||
),
|
||||
'value' => 1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->addElement(
|
||||
'text',
|
||||
'root_dn',
|
||||
|
@ -344,9 +344,10 @@ class ResourceConfigForm extends ConfigForm
|
||||
'submit',
|
||||
'resource_validation',
|
||||
array(
|
||||
'ignore' => true,
|
||||
'label' => $this->translate('Validate Configuration'),
|
||||
'decorators' => array('ViewHelper')
|
||||
'ignore' => true,
|
||||
'label' => $this->translate('Validate Configuration'),
|
||||
'data-progress-label' => $this->translate('Validation In Progress'),
|
||||
'decorators' => array('ViewHelper')
|
||||
)
|
||||
);
|
||||
$this->addDisplayGroup(
|
||||
|
@ -464,9 +464,10 @@ class UserBackendConfigForm extends ConfigForm
|
||||
'submit',
|
||||
'backend_validation',
|
||||
array(
|
||||
'ignore' => true,
|
||||
'label' => $this->translate('Validate Configuration'),
|
||||
'decorators' => array('ViewHelper')
|
||||
'ignore' => true,
|
||||
'label' => $this->translate('Validate Configuration'),
|
||||
'data-progress-label' => $this->translate('Validation In Progress'),
|
||||
'decorators' => array('ViewHelper')
|
||||
)
|
||||
);
|
||||
$this->addDisplayGroup(
|
||||
|
@ -5,6 +5,7 @@ namespace Icinga\Forms;
|
||||
|
||||
use Exception;
|
||||
use DateTimeZone;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\User\Preferences;
|
||||
@ -178,6 +179,19 @@ class PreferenceForm extends Form
|
||||
)
|
||||
);
|
||||
|
||||
if (Auth::getInstance()->hasPermission('application/stacktraces')) {
|
||||
$this->addElement(
|
||||
'checkbox',
|
||||
'show_stacktraces',
|
||||
array(
|
||||
'required' => true,
|
||||
'value' => $this->getDefaultShowStacktraces(),
|
||||
'label' => $this->translate('Show Stacktraces'),
|
||||
'description' => $this->translate('Set whether to show an exception\'s stacktrace.')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->addElement(
|
||||
'checkbox',
|
||||
'show_benchmark',
|
||||
@ -220,8 +234,20 @@ class PreferenceForm extends Form
|
||||
)
|
||||
);
|
||||
|
||||
$this->setAttrib('data-progress-element', 'preferences-progress');
|
||||
$this->addElement(
|
||||
'note',
|
||||
'preferences-progress',
|
||||
array(
|
||||
'decorators' => array(
|
||||
'ViewHelper',
|
||||
array('Spinner', array('id' => 'preferences-progress'))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->addDisplayGroup(
|
||||
array('btn_submit_preferences', 'btn_submit_session'),
|
||||
array('btn_submit_preferences', 'btn_submit_session', 'preferences-progress'),
|
||||
'submit_buttons',
|
||||
array(
|
||||
'decorators' => array(
|
||||
@ -257,4 +283,14 @@ class PreferenceForm extends Form
|
||||
$locale = Translator::getPreferredLocaleCode($_SERVER['HTTP_ACCEPT_LANGUAGE']);
|
||||
return $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default global setting for show_stacktraces
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function getDefaultShowStacktraces()
|
||||
{
|
||||
return Config::app()->get('global', 'show_stacktraces', true);
|
||||
}
|
||||
}
|
||||
|
@ -20,35 +20,7 @@ class RoleForm extends ConfigForm
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $providedPermissions = array(
|
||||
'*' => 'Allow everything (*)',
|
||||
'config/*' => 'Allow config access (config/*)',
|
||||
/*
|
||||
// [tg] seems excessive for me, hidden for rc1, tbd
|
||||
'config/application/*' => 'config/application/*',
|
||||
'config/application/general' => 'config/application/general',
|
||||
'config/application/resources' => 'config/application/resources',
|
||||
'config/application/userbackend' => 'config/application/userbackend',
|
||||
'config/application/usergroupbackend' => 'config/application/usergroupbackend',
|
||||
'config/authentication/*' => 'config/authentication/*',
|
||||
'config/authentication/users/*' => 'config/authentication/users/*',
|
||||
'config/authentication/users/show' => 'config/authentication/users/show',
|
||||
'config/authentication/users/add' => 'config/authentication/users/add',
|
||||
'config/authentication/users/edit' => 'config/authentication/users/edit',
|
||||
'config/authentication/users/remove' => 'config/authentication/users/remove',
|
||||
'config/authentication/groups/*' => 'config/authentication/groups/*',
|
||||
'config/authentication/groups/show' => 'config/authentication/groups/show',
|
||||
'config/authentication/groups/add' => 'config/authentication/groups/add',
|
||||
'config/authentication/groups/edit' => 'config/authentication/groups/edit',
|
||||
'config/authentication/groups/remove' => 'config/authentication/groups/remove',
|
||||
'config/authentication/roles/*' => 'config/authentication/roles/*',
|
||||
'config/authentication/roles/show' => 'config/authentication/roles/show',
|
||||
'config/authentication/roles/add' => 'config/authentication/roles/add',
|
||||
'config/authentication/roles/edit' => 'config/authentication/roles/edit',
|
||||
'config/authentication/roles/remove' => 'config/authentication/roles/remove',
|
||||
'config/modules' => 'config/modules'
|
||||
*/
|
||||
);
|
||||
protected $providedPermissions;
|
||||
|
||||
/**
|
||||
* Provided restrictions by currently loaded modules
|
||||
@ -62,6 +34,40 @@ class RoleForm extends ConfigForm
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->providedPermissions = array(
|
||||
'*' => $this->translate('Allow everything') . ' (*)',
|
||||
'application/stacktraces' => $this->translate(
|
||||
'Allow to adjust in the preferences whether to show stacktraces'
|
||||
) . ' (application/stacktraces)',
|
||||
'config/*' => $this->translate('Allow config access') . ' (config/*)',
|
||||
/*
|
||||
// [tg] seems excessive for me, hidden for rc1, tbd
|
||||
'config/application/*' => 'config/application/*',
|
||||
'config/application/general' => 'config/application/general',
|
||||
'config/application/resources' => 'config/application/resources',
|
||||
'config/application/userbackend' => 'config/application/userbackend',
|
||||
'config/application/usergroupbackend' => 'config/application/usergroupbackend',
|
||||
'config/authentication/*' => 'config/authentication/*',
|
||||
'config/authentication/users/*' => 'config/authentication/users/*',
|
||||
'config/authentication/users/show' => 'config/authentication/users/show',
|
||||
'config/authentication/users/add' => 'config/authentication/users/add',
|
||||
'config/authentication/users/edit' => 'config/authentication/users/edit',
|
||||
'config/authentication/users/remove' => 'config/authentication/users/remove',
|
||||
'config/authentication/groups/*' => 'config/authentication/groups/*',
|
||||
'config/authentication/groups/show' => 'config/authentication/groups/show',
|
||||
'config/authentication/groups/add' => 'config/authentication/groups/add',
|
||||
'config/authentication/groups/edit' => 'config/authentication/groups/edit',
|
||||
'config/authentication/groups/remove' => 'config/authentication/groups/remove',
|
||||
'config/authentication/roles/*' => 'config/authentication/roles/*',
|
||||
'config/authentication/roles/show' => 'config/authentication/roles/show',
|
||||
'config/authentication/roles/add' => 'config/authentication/roles/add',
|
||||
'config/authentication/roles/edit' => 'config/authentication/roles/edit',
|
||||
'config/authentication/roles/remove' => 'config/authentication/roles/remove',
|
||||
'config/modules' => 'config/modules'
|
||||
*/
|
||||
);
|
||||
|
||||
|
||||
$helper = new Zend_Form_Element('bogus');
|
||||
$mm = Icinga::app()->getModuleManager();
|
||||
foreach ($mm->listInstalledModules() as $moduleName) {
|
||||
|
@ -39,7 +39,7 @@
|
||||
</td>
|
||||
<td data-base-target="_self">
|
||||
<?php if ($i > 0): ?>
|
||||
<button type="submit" name="backend_newpos" class="link-like icon-only" value="<?= sprintf(
|
||||
<button type="submit" name="backend_newpos" class="link-like icon-only animated move-up" value="<?= sprintf(
|
||||
'%s|%s',
|
||||
$backendNames[$i],
|
||||
$i - 1
|
||||
@ -53,7 +53,7 @@
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($i + 1 < count($backendNames)): ?>
|
||||
<button type="submit" name="backend_newpos" class="link-like icon-only" value="<?= sprintf(
|
||||
<button type="submit" name="backend_newpos" class="link-like icon-only animated move-down" value="<?= sprintf(
|
||||
'%s|%s',
|
||||
$backendNames[$i],
|
||||
$i + 1
|
||||
|
@ -212,9 +212,19 @@ class Web extends EmbeddedWeb
|
||||
$this->frontController = Zend_Controller_Front::getInstance();
|
||||
$this->frontController->setRequest($this->getRequest());
|
||||
$this->frontController->setControllerDirectory($this->getApplicationDir('/controllers'));
|
||||
|
||||
$displayExceptions = $this->config->get('global', 'show_stacktraces', true);
|
||||
if ($this->user !== null && $this->user->can('application/stacktraces')) {
|
||||
$displayExceptions = $this->user->getPreferences()->getValue(
|
||||
'icingaweb',
|
||||
'show_stacktraces',
|
||||
$displayExceptions
|
||||
);
|
||||
}
|
||||
|
||||
$this->frontController->setParams(
|
||||
array(
|
||||
'displayExceptions' => true
|
||||
'displayExceptions' => $displayExceptions
|
||||
)
|
||||
);
|
||||
return $this;
|
||||
|
@ -273,30 +273,10 @@ class Loader
|
||||
|
||||
protected function searchMatch($needle, $haystack)
|
||||
{
|
||||
$stack = $haystack;
|
||||
$search = $needle;
|
||||
$this->lastSuggestions = array();
|
||||
while (strlen($search) > 0) {
|
||||
$len = strlen($search);
|
||||
foreach ($stack as & $s) {
|
||||
$s = substr($s, 0, $len);
|
||||
}
|
||||
|
||||
$res = array_keys($stack, $search, true);
|
||||
if (count($res) === 1) {
|
||||
$found = $haystack[$res[0]];
|
||||
if (substr($found, 0, strlen($needle)) === $needle) {
|
||||
return $found;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} elseif (count($res) > 1) {
|
||||
foreach ($res as $key) {
|
||||
$this->lastSuggestions[] = $haystack[$key];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
$search = substr($search, 0, -1);
|
||||
$this->lastSuggestions = preg_grep(sprintf('/^%s.*$/', preg_quote($needle, '/')), $haystack);
|
||||
$match = array_search($needle, $haystack, true);
|
||||
if (false !== $match) {
|
||||
return $haystack[$match];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -122,13 +122,6 @@ class LdapConnection implements Selectable, Inspectable
|
||||
*/
|
||||
protected $rootDn;
|
||||
|
||||
/**
|
||||
* Whether to load the configuration for strict certificate validation or the one for non-strict validation
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $validateCertificate;
|
||||
|
||||
/**
|
||||
* Whether the bind on this connection has already been performed
|
||||
*
|
||||
@ -176,7 +169,6 @@ class LdapConnection implements Selectable, Inspectable
|
||||
$this->bindPw = $config->bind_pw;
|
||||
$this->rootDn = $config->root_dn;
|
||||
$this->port = $config->get('port', 389);
|
||||
$this->validateCertificate = (bool) $config->get('reqcert', true);
|
||||
|
||||
$this->encryption = $config->encryption;
|
||||
if ($this->encryption !== null) {
|
||||
@ -957,16 +949,9 @@ class LdapConnection implements Selectable, Inspectable
|
||||
$info = new Inspection('');
|
||||
}
|
||||
|
||||
if ($this->encryption === static::STARTTLS || $this->encryption === static::LDAPS) {
|
||||
$this->prepareTlsEnvironment();
|
||||
}
|
||||
|
||||
$hostname = $this->hostname;
|
||||
if ($this->encryption === static::LDAPS) {
|
||||
$info->write('Connect using LDAPS');
|
||||
if (! $this->validateCertificate) {
|
||||
$info->write('Skip certificate validation');
|
||||
}
|
||||
$hostname = 'ldaps://' . $hostname;
|
||||
}
|
||||
|
||||
@ -983,9 +968,6 @@ class LdapConnection implements Selectable, Inspectable
|
||||
if ($this->encryption === static::STARTTLS) {
|
||||
$this->encrypted = true;
|
||||
$info->write('Connect using STARTTLS');
|
||||
if (! $this->validateCertificate) {
|
||||
$info->write('Skip certificate validation');
|
||||
}
|
||||
if (! ldap_start_tls($ds)) {
|
||||
throw new LdapException('LDAP STARTTLS failed: %s', ldap_error($ds));
|
||||
}
|
||||
@ -998,30 +980,6 @@ class LdapConnection implements Selectable, Inspectable
|
||||
return $ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up how to handle StartTLS connections
|
||||
*
|
||||
* @throws LdapException In case the LDAPRC environment variable cannot be set
|
||||
*/
|
||||
protected function prepareTlsEnvironment()
|
||||
{
|
||||
// TODO: allow variable known CA location (system VS Icinga)
|
||||
if (Platform::isWindows()) {
|
||||
putenv('LDAPTLS_REQCERT=never');
|
||||
} else {
|
||||
if ($this->validateCertificate) {
|
||||
$ldap_conf = $this->getConfigDir('ldap_ca.conf');
|
||||
} else {
|
||||
$ldap_conf = $this->getConfigDir('ldap_nocert.conf');
|
||||
}
|
||||
|
||||
putenv('LDAPRC=' . $ldap_conf); // TODO: Does not have any effect
|
||||
if (getenv('LDAPRC') !== $ldap_conf) {
|
||||
throw new LdapException('putenv failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an LDAP entry
|
||||
*
|
||||
@ -1103,6 +1061,13 @@ class LdapConnection implements Selectable, Inspectable
|
||||
try {
|
||||
$ds = $this->prepareNewConnection($insp);
|
||||
} catch (Exception $e) {
|
||||
if ($this->encryption === 'starttls') {
|
||||
// The Exception does not return any proper error messages in case of certificate errors. Connecting
|
||||
// by STARTTLS will usually fail at this point when the certificate is unknown,
|
||||
// so at least try to give some hints.
|
||||
$insp->write('NOTE: There might be an issue with the chosen encryption. Ensure that the LDAP-Server ' .
|
||||
'supports STARTTLS and that the LDAP-Client is configured to accept its certificate.');
|
||||
}
|
||||
return $insp->error($e->getMessage());
|
||||
}
|
||||
|
||||
@ -1116,6 +1081,13 @@ class LdapConnection implements Selectable, Inspectable
|
||||
'***' /* $this->bindPw */
|
||||
);
|
||||
if (! $success) {
|
||||
// ldap_error does not return any proper error messages in case of certificate errors. Connecting
|
||||
// by LDAPS will usually fail at this point when the certificate is unknown, so at least try to give
|
||||
// some hints.
|
||||
if ($this->encryption === 'ldaps') {
|
||||
$insp->write('NOTE: There might be an issue with the chosen encryption. Ensure that the LDAP-Server ' .
|
||||
' supports LDAPS and that the LDAP-Client is configured to accept its certificate.');
|
||||
}
|
||||
return $insp->error(sprintf('%s failed: %s', $msg, ldap_error($ds)));
|
||||
}
|
||||
$insp->write(sprintf($msg . ' successful'));
|
||||
@ -1137,12 +1109,4 @@ class LdapConnection implements Selectable, Inspectable
|
||||
}
|
||||
return $insp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the environment variables set by self::prepareTlsEnvironment()
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
putenv('LDAPRC');
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +97,13 @@ class Form extends Zend_Form
|
||||
*/
|
||||
protected $submitLabel;
|
||||
|
||||
/**
|
||||
* Label to use for showing the user an activity indicator when submitting the form
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $progressLabel;
|
||||
|
||||
/**
|
||||
* The url to redirect to upon success
|
||||
*
|
||||
@ -279,6 +286,29 @@ class Form extends Zend_Form
|
||||
return $this->submitLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label to use for showing the user an activity indicator when submitting the form
|
||||
*
|
||||
* @param string $label
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProgressLabel($label)
|
||||
{
|
||||
$this->progressLabel = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the label to use for showing the user an activity indicator when submitting the form
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProgressLabel()
|
||||
{
|
||||
return $this->progressLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the url to redirect to upon success
|
||||
*
|
||||
@ -654,6 +684,12 @@ class Form extends Zend_Form
|
||||
public function setUseFormAutosubmit($state = true)
|
||||
{
|
||||
$this->useFormAutosubmit = (bool) $state;
|
||||
if ($this->useFormAutosubmit) {
|
||||
$this->setAttrib('data-progress-element', 'header-' . $this->getId());
|
||||
} else {
|
||||
$this->removeAttrib('data-progress-element');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -777,11 +813,13 @@ class Form extends Zend_Form
|
||||
'submit',
|
||||
'btn_submit',
|
||||
array(
|
||||
'ignore' => true,
|
||||
'label' => $submitLabel,
|
||||
'decorators' => array(
|
||||
'ignore' => true,
|
||||
'label' => $submitLabel,
|
||||
'data-progress-label' => $this->getProgressLabel(),
|
||||
'decorators' => array(
|
||||
'ViewHelper',
|
||||
array('HtmlTag', array('tag' => 'div'))
|
||||
array('Spinner', array('separator' => '')),
|
||||
array('HtmlTag', array('tag' => 'div', 'class' => 'buttons'))
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -840,9 +878,17 @@ class Form extends Zend_Form
|
||||
&& ! array_key_exists('disabledLoadDefaultDecorators', $options)
|
||||
) {
|
||||
$options['decorators'] = static::$defaultElementDecorators;
|
||||
if (! isset($options['data-progress-label']) && ($type === 'submit'
|
||||
|| ($type === 'button' && isset($options['type']) && $options['type'] === 'submit'))
|
||||
) {
|
||||
array_splice($options['decorators'], 1, 0, array(array('Spinner', array('separator' => ''))));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$options = array('decorators' => static::$defaultElementDecorators);
|
||||
if ($type === 'submit') {
|
||||
array_splice($options['decorators'], 1, 0, array(array('Spinner', array('separator' => ''))));
|
||||
}
|
||||
}
|
||||
|
||||
$el = parent::createElement($type, $name, $options);
|
||||
@ -1223,8 +1269,15 @@ class Form extends Zend_Form
|
||||
} else {
|
||||
$this->addDecorator('Description', array('tag' => 'h1'));
|
||||
if ($this->getUseFormAutosubmit()) {
|
||||
$this->addDecorator('Autosubmit', array('accessible' => true))
|
||||
->addDecorator('HtmlTag', array('tag' => 'div', 'class' => 'header'));
|
||||
$this->getDecorator('Description')->setEscape(false);
|
||||
$this->addDecorator(
|
||||
'HtmlTag',
|
||||
array(
|
||||
'tag' => 'div',
|
||||
'class' => 'header',
|
||||
'id' => 'header-' . $this->getId()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->addDecorator('FormDescriptions')
|
||||
@ -1271,6 +1324,25 @@ class Form extends Zend_Form
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve form description
|
||||
*
|
||||
* This will return the escaped description with the autosubmit warning icon if form autosubmit is enabled.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
$description = parent::getDescription();
|
||||
if ($description && $this->getUseFormAutosubmit()) {
|
||||
$autosubmit = $this->_getDecorator('Autosubmit', array('accessible' => true));
|
||||
$autosubmit->setElement($this);
|
||||
$description = $autosubmit->render($this->getView()->escape($description));
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the action to submit this form against
|
||||
*
|
||||
|
@ -96,7 +96,10 @@ class Help extends Zend_Form_Decorator_Abstract
|
||||
$helpContent = $this->getView()->icon(
|
||||
'help',
|
||||
$description . ($description && $requirement ? ' ' : '') . $requirement,
|
||||
array('aria-hidden' => $this->accessible ? 'true' : 'false')
|
||||
array(
|
||||
'class' => 'help',
|
||||
'aria-hidden' => $this->accessible ? 'true' : 'false'
|
||||
)
|
||||
) . $helpContent;
|
||||
}
|
||||
|
||||
|
48
library/Icinga/Web/Form/Decorator/Spinner.php
Normal file
48
library/Icinga/Web/Form/Decorator/Spinner.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Form\Decorator;
|
||||
|
||||
use Zend_Form_Decorator_Abstract;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\View;
|
||||
|
||||
/**
|
||||
* Decorator to add a spinner next to an element
|
||||
*/
|
||||
class Spinner extends Zend_Form_Decorator_Abstract
|
||||
{
|
||||
/**
|
||||
* Return the current view
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
protected function getView()
|
||||
{
|
||||
return Icinga::app()->getViewRenderer()->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a spinner icon to a form element
|
||||
*
|
||||
* @param string $content The html rendered so far
|
||||
*
|
||||
* @return string The updated html
|
||||
*/
|
||||
public function render($content = '')
|
||||
{
|
||||
$spinner = '<div '
|
||||
. ($this->getOption('id') !== null ? ' id="' . $this->getOption('id') . '"' : '')
|
||||
. 'class="spinner ' . ($this->getOption('class') ?: '') . '"'
|
||||
. '>'
|
||||
. $this->getView()->icon('spin6')
|
||||
. '</div>';
|
||||
|
||||
switch ($this->getPlacement()) {
|
||||
case self::APPEND:
|
||||
return $content . $spinner;
|
||||
case self::PREPEND:
|
||||
return $spinner . $content;
|
||||
}
|
||||
}
|
||||
}
|
@ -117,9 +117,19 @@ class Menu implements RecursiveIterator
|
||||
foreach ($props as $key => $value) {
|
||||
$method = 'set' . implode('', array_map('ucfirst', explode('_', strtolower($key))));
|
||||
if ($key === 'renderer') {
|
||||
// nested configuration is used to pass multiple arguments to the item renderer
|
||||
if ($value instanceof ConfigObject) {
|
||||
$args = $value;
|
||||
$value = $value->get('0');
|
||||
}
|
||||
|
||||
$value = '\\' . ltrim($value, '\\');
|
||||
if (class_exists($value)) {
|
||||
$value = new $value;
|
||||
if (isset($args)) {
|
||||
$value = new $value($args);
|
||||
} else {
|
||||
$value = new $value;
|
||||
}
|
||||
} else {
|
||||
$class = '\Icinga\Web\Menu' . $value;
|
||||
if (!class_exists($class)) {
|
||||
@ -127,7 +137,11 @@ class Menu implements RecursiveIterator
|
||||
sprintf('ItemRenderer with class "%s" does not exist', $class)
|
||||
);
|
||||
}
|
||||
$value = new $class;
|
||||
if (isset($args)) {
|
||||
$value = new $class($args);
|
||||
} else {
|
||||
$value = new $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (method_exists($this, $method)) {
|
||||
@ -226,7 +240,6 @@ class Menu implements RecursiveIterator
|
||||
$auth = Auth::getInstance();
|
||||
|
||||
if ($auth->isAuthenticated()) {
|
||||
|
||||
$this->add(t('Dashboard'), array(
|
||||
'url' => 'dashboard',
|
||||
'icon' => 'dashboard',
|
||||
@ -236,7 +249,10 @@ class Menu implements RecursiveIterator
|
||||
$section = $this->add(t('System'), array(
|
||||
'icon' => 'services',
|
||||
'priority' => 700,
|
||||
'renderer' => 'ProblemMenuItemRenderer'
|
||||
'renderer' => array(
|
||||
'SummaryMenuItemRenderer',
|
||||
'state' => 'critical'
|
||||
)
|
||||
));
|
||||
$section->add(t('About'), array(
|
||||
'url' => 'about',
|
||||
@ -255,7 +271,7 @@ class Menu implements RecursiveIterator
|
||||
'priority' => 800
|
||||
));
|
||||
$section->add(t('Application'), array(
|
||||
'url' => 'config',
|
||||
'url' => 'config/general',
|
||||
'permission' => 'config/application/*',
|
||||
'priority' => 810
|
||||
));
|
||||
@ -297,7 +313,10 @@ class Menu implements RecursiveIterator
|
||||
$section->add(t('Logout'), array(
|
||||
'url' => 'authentication/logout',
|
||||
'priority' => 990,
|
||||
'renderer' => 'ForeignMenuItemRenderer'
|
||||
'renderer' => array(
|
||||
'MenuItemRenderer',
|
||||
'target' => '_self'
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
65
library/Icinga/Web/Menu/BadgeMenuItemRenderer.php
Normal file
65
library/Icinga/Web/Menu/BadgeMenuItemRenderer.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Web\Menu;
|
||||
|
||||
use Icinga\Web\Menu;
|
||||
|
||||
abstract class BadgeMenuItemRenderer extends MenuItemRenderer
|
||||
{
|
||||
const STATE_OK = 'ok';
|
||||
const STATE_CRITICAL = 'critical';
|
||||
const STATE_WARNING = 'warning';
|
||||
const STATE_PENDING = 'pending';
|
||||
const STATE_UNKNOWN = 'unknown';
|
||||
|
||||
/**
|
||||
* Defines the color of the badge
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getState();
|
||||
|
||||
/**
|
||||
* The amount of items to display in the badge
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function getCount();
|
||||
|
||||
/**
|
||||
* The tooltip title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getTitle();
|
||||
|
||||
/**
|
||||
* Renders the html content of a single menu item
|
||||
*
|
||||
* @param Menu $menu
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(Menu $menu)
|
||||
{
|
||||
return $this->renderBadge() . $this->createLink($menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the badge
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderBadge()
|
||||
{
|
||||
if ($count = $this->getCount()) {
|
||||
return sprintf(
|
||||
'<div title="%s" class="badge-container"><span class="badge badge-%s">%s</span></div>',
|
||||
$this->getView()->escape($this->getTitle()),
|
||||
$this->getView()->escape($this->getState()),
|
||||
$count
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Menu;
|
||||
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
* A menu item with a link that surpasses the regular navigation link behavior
|
||||
*/
|
||||
class ForeignMenuItemRenderer extends MenuItemRenderer
|
||||
{
|
||||
protected $attributes = array(
|
||||
'target' => '_self'
|
||||
);
|
||||
}
|
@ -7,6 +7,7 @@ use Icinga\Application\Icinga;
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Web\Url;
|
||||
use Icinga\Web\View;
|
||||
use Icinga\Data\ConfigObject;
|
||||
|
||||
/**
|
||||
* Default MenuItemRenderer class
|
||||
@ -14,38 +15,39 @@ use Icinga\Web\View;
|
||||
class MenuItemRenderer
|
||||
{
|
||||
/**
|
||||
* Contains <a> element specific attributes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = array();
|
||||
|
||||
/**
|
||||
* View
|
||||
* The view this menu item is being rendered to
|
||||
*
|
||||
* @var View|null
|
||||
*/
|
||||
protected $view;
|
||||
protected $view = null;
|
||||
|
||||
/**
|
||||
* Set the view
|
||||
* The link target
|
||||
*
|
||||
* @param View $view
|
||||
*
|
||||
* @return $this
|
||||
* @var string
|
||||
*/
|
||||
public function setView(View $view)
|
||||
protected $target = null;
|
||||
|
||||
/**
|
||||
* Create a new instance of MenuItemRenderer
|
||||
*
|
||||
* Is is possible to configure the link target using the option 'target'
|
||||
*
|
||||
* @param ConfigObject|null $configuration
|
||||
*/
|
||||
public function __construct(ConfigObject $configuration = null)
|
||||
{
|
||||
$this->view = $view;
|
||||
return $this;
|
||||
if ($configuration !== null) {
|
||||
$this->target = $configuration->get('target', null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view
|
||||
* Get the view this menu item is being rendered to
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function getView()
|
||||
protected function getView()
|
||||
{
|
||||
if ($this->view === null) {
|
||||
$this->view = Icinga::app()->getViewRenderer()->view;
|
||||
@ -53,6 +55,36 @@ class MenuItemRenderer
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a menu item link element
|
||||
*
|
||||
* @param Menu $menu
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function createLink(Menu $menu)
|
||||
{
|
||||
$attributes = isset($this->target) ? sprintf(' target="%s"', $this->getView()->escape($this->target)) : '';
|
||||
|
||||
if ($menu->getIcon() && strpos($menu->getIcon(), '.') === false) {
|
||||
return sprintf(
|
||||
'<a href="%s"%s><i aria-hidden="true" class="icon-%s"></i>%s</a>',
|
||||
$menu->getUrl() ? : '#',
|
||||
$attributes,
|
||||
$menu->getIcon(),
|
||||
$this->getView()->escape($menu->getTitle())
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<a href="%s"%s>%s%s<span></span></a>',
|
||||
$menu->getUrl() ? : '#',
|
||||
$attributes,
|
||||
$menu->getIcon() ? '<img aria-hidden="true" src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
|
||||
$this->getView()->escape($menu->getTitle())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the html content of a single menu item
|
||||
*
|
||||
@ -64,47 +96,4 @@ class MenuItemRenderer
|
||||
{
|
||||
return $this->createLink($menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a menu item link element
|
||||
*
|
||||
* @param Menu $menu
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function createLink(Menu $menu)
|
||||
{
|
||||
if ($menu->getIcon() && strpos($menu->getIcon(), '.') === false) {
|
||||
return sprintf(
|
||||
'<a href="%s"%s><i aria-hidden="true" class="icon-%s"></i>%s</a>',
|
||||
$menu->getUrl() ? : '#',
|
||||
$this->getAttributes(),
|
||||
$menu->getIcon(),
|
||||
$this->getView()->escape($menu->getTitle())
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<a href="%s"%s>%s%s<span></span></a>',
|
||||
$menu->getUrl() ? : '#',
|
||||
$this->getAttributes(),
|
||||
$menu->getIcon() ? '<img aria-hidden="true" src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
|
||||
$this->getView()->escape($menu->getTitle())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <a> element specific attributes if present
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getAttributes()
|
||||
{
|
||||
$attributes = '';
|
||||
$view = $this->getView();
|
||||
foreach ($this->attributes as $attribute => $value) {
|
||||
$attributes .= ' ' . $view->escape($attribute) . '="' . $view->escape($value) . '"';
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
|
@ -1,64 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Menu;
|
||||
|
||||
use Icinga\Web\Menu;
|
||||
|
||||
class ProblemMenuItemRenderer extends MenuItemRenderer
|
||||
{
|
||||
/**
|
||||
* Set of summarized problems from submenus
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $summary = array();
|
||||
|
||||
/**
|
||||
* Renders the html content of a single menu item and summarizes submenu problems
|
||||
*
|
||||
* @param Menu $menu
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(Menu $menu)
|
||||
{
|
||||
if ($menu->getParent() !== null && $menu->hasSubMenus()) {
|
||||
/** @var $submenu Menu */
|
||||
foreach ($menu->getSubMenus() as $submenu) {
|
||||
$renderer = $submenu->getRenderer();
|
||||
if (method_exists($renderer, 'getSummary')) {
|
||||
if ($renderer->getSummary() !== null) {
|
||||
$this->summary[] = $renderer->getSummary();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->getBadge() . $this->createLink($menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the problem badge
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getBadge()
|
||||
{
|
||||
if (count($this->summary) > 0) {
|
||||
$problems = 0;
|
||||
$titles = array();
|
||||
|
||||
foreach ($this->summary as $summary) {
|
||||
$problems += $summary['problems'];
|
||||
$titles[] = $summary['title'];
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<div title="%s" class="badge-container"><span class="badge badge-critical">%s</span></div>',
|
||||
implode(', ', $titles),
|
||||
$problems
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
94
library/Icinga/Web/Menu/SummaryMenuItemRenderer.php
Normal file
94
library/Icinga/Web/Menu/SummaryMenuItemRenderer.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Menu;
|
||||
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Data\ConfigObject;
|
||||
|
||||
/**
|
||||
* Summary badge adding up all badges in the sub-menus that have the same state
|
||||
*/
|
||||
class SummaryMenuItemRenderer extends BadgeMenuItemRenderer
|
||||
{
|
||||
/**
|
||||
* Set of summarized problems from submenus
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $titles = array();
|
||||
|
||||
/**
|
||||
* The amount of problems
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $count = 0;
|
||||
|
||||
/**
|
||||
* The state that should be summarized
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The amount of problems
|
||||
*/
|
||||
public function __construct(ConfigObject $configuration)
|
||||
{
|
||||
$this->state = $configuration->get('state', self::STATE_CRITICAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the html content of a single menu item and summarized sub-menus
|
||||
*
|
||||
* @param Menu $menu
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(Menu $menu)
|
||||
{
|
||||
/** @var $submenu Menu */
|
||||
foreach ($menu->getSubMenus() as $submenu) {
|
||||
$renderer = $submenu->getRenderer();
|
||||
if ($renderer instanceof BadgeMenuItemRenderer) {
|
||||
if ($renderer->getState() === $this->state) {
|
||||
$this->titles[] = $renderer->getTitle();
|
||||
$this->count += $renderer->getCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->renderBadge() . $this->createLink($menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of items to display in the badge
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the color of the badge
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tooltip title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return implode(', ', $this->titles);
|
||||
}
|
||||
}
|
@ -119,6 +119,11 @@ class Request extends Zend_Controller_Request_Http
|
||||
return $id . '-' . $this->uniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether cookies are enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCookieSupport()
|
||||
{
|
||||
$cookie = new Cookie($this);
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
namespace Icinga\Web\Widget\Dashboard;
|
||||
|
||||
use Zend_Form_Element_Button;
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Url;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Exception\IcingaException;
|
||||
@ -43,6 +41,13 @@ class Dashlet extends UserWidget
|
||||
*/
|
||||
private $disabled = false;
|
||||
|
||||
/**
|
||||
* The progress label being used
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $progressLabel;
|
||||
|
||||
/**
|
||||
* The template string used for rendering this widget
|
||||
*
|
||||
@ -52,6 +57,7 @@ class Dashlet extends UserWidget
|
||||
|
||||
<div class="container" data-icinga-url="{URL}">
|
||||
<h1><a href="{FULL_URL}" aria-label="{TOOLTIP}" title="{TOOLTIP}" data-base-target="col1">{TITLE}</a></h1>
|
||||
<p class="progress-label">{PROGRESS_LABEL}<span>.</span><span>.</span><span>.</span></p>
|
||||
<noscript>
|
||||
<iframe
|
||||
src="{IFRAME_URL}"
|
||||
@ -147,6 +153,33 @@ EOD;
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the progress label to use
|
||||
*
|
||||
* @param string $label
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProgressLabel($label)
|
||||
{
|
||||
$this->progressLabel = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the progress label to use
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProgressLabe()
|
||||
{
|
||||
if ($this->progressLabel === null) {
|
||||
return $this->view()->translate('Loading');
|
||||
}
|
||||
|
||||
return $this->progressLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this dashlet's structure as array
|
||||
*
|
||||
@ -185,7 +218,8 @@ EOD;
|
||||
'{FULL_URL}',
|
||||
'{TOOLTIP}',
|
||||
'{TITLE}',
|
||||
'{TITLE_PREFIX}'
|
||||
'{TITLE_PREFIX}',
|
||||
'{PROGRESS_LABEL}'
|
||||
);
|
||||
|
||||
$replaceTokens = array(
|
||||
@ -194,7 +228,8 @@ EOD;
|
||||
$url->getUrlWithout(array('view', 'limit')),
|
||||
sprintf($view->translate('Show %s', 'dashboard.dashlet.tooltip'), $view->escape($this->getTitle())),
|
||||
$view->escape($this->getTitle()),
|
||||
$view->translate('Dashlet') . ': '
|
||||
$view->translate('Dashlet') . ': ',
|
||||
$this->getProgressLabe()
|
||||
);
|
||||
|
||||
return str_replace($searchTokens, $replaceTokens, $this->template);
|
||||
|
@ -191,6 +191,21 @@ class Pane extends UserWidget
|
||||
return implode("\n", $dashlets) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create, add and return a new dashlet
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $url
|
||||
*
|
||||
* @return Dashlet
|
||||
*/
|
||||
public function createDashlet($title, $url = null)
|
||||
{
|
||||
$dashlet = new Dashlet($title, $url, $this);
|
||||
$this->addDashlet($dashlet);
|
||||
return $dashlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a dashlet to this pane, optionally creating it if $dashlet is a string
|
||||
*
|
||||
@ -206,7 +221,7 @@ class Pane extends UserWidget
|
||||
if ($dashlet instanceof Dashlet) {
|
||||
$this->dashlets[$dashlet->getTitle()] = $dashlet;
|
||||
} elseif (is_string($dashlet) && $url !== null) {
|
||||
$this->dashlets[$dashlet] = new Dashlet($dashlet, $url, $this);
|
||||
$this->createDashlet($dashlet, $url);
|
||||
} else {
|
||||
throw new ConfigurationError('Invalid dashlet added: %s', $dashlet);
|
||||
}
|
||||
|
@ -69,10 +69,10 @@ class SearchDashboard extends Dashboard
|
||||
usort($searchUrls, array($this, 'compareSearchUrls'));
|
||||
|
||||
foreach (array_reverse($searchUrls) as $searchUrl) {
|
||||
$pane->addDashlet(
|
||||
$pane->createDashlet(
|
||||
$searchUrl->title . ': ' . $searchString,
|
||||
Url::fromPath($searchUrl->url, array('q' => $searchString))
|
||||
);
|
||||
)->setProgressLabel($this->view()->translate('Searching'));
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -38,6 +38,11 @@ class Wizard
|
||||
*/
|
||||
const BTN_PREV = 'btn_prev';
|
||||
|
||||
/**
|
||||
* The name and id of the element for showing the user an activity indicator when advancing the wizard
|
||||
*/
|
||||
const PROGRESS_ELEMENT = 'wizard_progress';
|
||||
|
||||
/**
|
||||
* This wizard's parent
|
||||
*
|
||||
@ -606,7 +611,7 @@ class Wizard
|
||||
'type' => 'submit',
|
||||
'value' => $pages[1]->getName(),
|
||||
'label' => t('Next'),
|
||||
'decorators' => array('ViewHelper')
|
||||
'decorators' => array('ViewHelper', 'Spinner')
|
||||
)
|
||||
);
|
||||
} elseif ($index < count($pages) - 1) {
|
||||
@ -655,8 +660,21 @@ class Wizard
|
||||
);
|
||||
}
|
||||
|
||||
$page->setAttrib('data-progress-element', static::PROGRESS_ELEMENT);
|
||||
$page->addElement(
|
||||
'note',
|
||||
static::PROGRESS_ELEMENT,
|
||||
array(
|
||||
'order' => 99, // Ensures that it's shown on the right even if a sub-class adds another button
|
||||
'decorators' => array(
|
||||
'ViewHelper',
|
||||
array('Spinner', array('id' => static::PROGRESS_ELEMENT))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$page->addDisplayGroup(
|
||||
array(static::BTN_PREV, static::BTN_NEXT),
|
||||
array(static::BTN_PREV, static::BTN_NEXT, static::PROGRESS_ELEMENT),
|
||||
'buttons',
|
||||
array(
|
||||
'decorators' => array(
|
||||
|
@ -24,24 +24,6 @@ class Monitoring_ListController extends Controller
|
||||
$this->createTabs();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT USE. THIS IS A HACK. This is removed once we fix the eventhistory action w/ filters.
|
||||
*/
|
||||
protected function applyFilter($query)
|
||||
{
|
||||
$params = clone $this->params;
|
||||
$params->shift('format');
|
||||
$params->shift('limit');
|
||||
$params->shift('page');
|
||||
$params->shift('view');
|
||||
if ($sort = $params->shift('sort')) {
|
||||
$query->order($sort, $params->shift('dir'));
|
||||
}
|
||||
$query->applyFilter(Filter::fromQuerystring((string) $params));
|
||||
$this->handleFormatRequest($query);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the backend to use (used for testing)
|
||||
*
|
||||
@ -91,8 +73,8 @@ class Monitoring_ListController extends Controller
|
||||
'host_current_check_attempt',
|
||||
'host_max_check_attempts'
|
||||
), $this->addColumns()));
|
||||
$this->filterQuery($query);
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
$this->filterQuery($query);
|
||||
$this->view->hosts = $query;
|
||||
$stats = $this->backend->select()->from('hoststatussummary', array(
|
||||
'hosts_total',
|
||||
@ -177,8 +159,8 @@ class Monitoring_ListController extends Controller
|
||||
'max_check_attempts' => 'service_max_check_attempts'
|
||||
), $this->addColumns());
|
||||
$query = $this->backend->select()->from('servicestatus', $columns);
|
||||
$this->filterQuery($query);
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
$this->filterQuery($query);
|
||||
$this->view->services = $query;
|
||||
|
||||
$this->setupLimitControl();
|
||||
@ -242,9 +224,8 @@ class Monitoring_ListController extends Controller
|
||||
'host_display_name',
|
||||
'service_display_name'
|
||||
));
|
||||
$this->filterQuery($query);
|
||||
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
$this->filterQuery($query);
|
||||
|
||||
$this->view->downtimes = $query;
|
||||
|
||||
@ -291,8 +272,8 @@ class Monitoring_ListController extends Controller
|
||||
'host_display_name',
|
||||
'service_display_name'
|
||||
));
|
||||
$this->filterQuery($query);
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
$this->filterQuery($query);
|
||||
$this->view->notifications = $query;
|
||||
|
||||
$this->setupLimitControl();
|
||||
@ -314,8 +295,8 @@ class Monitoring_ListController extends Controller
|
||||
'contact_notify_service_timeperiod',
|
||||
'contact_notify_host_timeperiod'
|
||||
));
|
||||
$this->filterQuery($query);
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
$this->filterQuery($query);
|
||||
$this->view->contacts = $query;
|
||||
|
||||
$this->setupLimitControl();
|
||||
@ -386,8 +367,8 @@ class Monitoring_ListController extends Controller
|
||||
'contact_email',
|
||||
'contact_pager'
|
||||
));
|
||||
$this->filterQuery($query);
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
$this->filterQuery($query);
|
||||
|
||||
$this->setupSortControl(array(
|
||||
'contactgroup_name' => $this->translate('Contactgroup Name'),
|
||||
@ -430,10 +411,8 @@ class Monitoring_ListController extends Controller
|
||||
'host_display_name',
|
||||
'service_display_name'
|
||||
));
|
||||
$this->filterQuery($query);
|
||||
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
|
||||
$this->filterQuery($query);
|
||||
$this->view->comments = $query;
|
||||
|
||||
$this->setupLimitControl();
|
||||
@ -485,10 +464,8 @@ class Monitoring_ListController extends Controller
|
||||
'services_warning_last_state_change_unhandled' => 'services_warning_unhandled_last_state_change',
|
||||
'services_warning_unhandled'
|
||||
));
|
||||
$this->filterQuery($query);
|
||||
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
|
||||
$this->filterQuery($query);
|
||||
$this->view->servicegroups = $query;
|
||||
|
||||
$this->setupLimitControl();
|
||||
@ -531,10 +508,8 @@ class Monitoring_ListController extends Controller
|
||||
'services_warning_handled',
|
||||
'services_warning_unhandled'
|
||||
));
|
||||
$this->filterQuery($query);
|
||||
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
|
||||
$this->filterQuery($query);
|
||||
$this->view->hostgroups = $query;
|
||||
|
||||
$this->setupLimitControl();
|
||||
@ -589,8 +564,8 @@ class Monitoring_ListController extends Controller
|
||||
'service_output',
|
||||
'service_handled'
|
||||
));
|
||||
$this->filterQuery($query);
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
$this->filterQuery($query);
|
||||
$this->setupSortControl(array(
|
||||
'host_name' => $this->translate('Hostname'),
|
||||
'service_description' => $this->translate('Service description')
|
||||
|
@ -68,7 +68,7 @@ class DeleteCommentCommandForm extends CommandForm
|
||||
'ignore' => true,
|
||||
'escape' => false,
|
||||
'type' => 'submit',
|
||||
'class' => 'link-like',
|
||||
'class' => 'link-like spinner',
|
||||
'label' => $this->getView()->icon('trash'),
|
||||
'title' => $this->translate('Delete this comment'),
|
||||
'decorators' => array('ViewHelper')
|
||||
|
@ -68,7 +68,7 @@ class DeleteDowntimeCommandForm extends CommandForm
|
||||
'ignore' => true,
|
||||
'escape' => false,
|
||||
'type' => 'submit',
|
||||
'class' => 'link-like',
|
||||
'class' => 'link-like spinner',
|
||||
'label' => $this->getView()->icon('trash'),
|
||||
'title' => $this->translate('Delete this downtime'),
|
||||
'decorators' => array('ViewHelper')
|
||||
|
@ -67,7 +67,7 @@
|
||||
) : $this->translate('This comment does not expire.'); ?>
|
||||
</td>
|
||||
<?php if (isset($delCommentForm)): // Form is unset if the current user lacks the respective permission ?>
|
||||
<td style="width: 2em" data-base-target="self">
|
||||
<td style="width: 2em" data-base-target="_self">
|
||||
<?php
|
||||
$delCommentForm = clone $delCommentForm;
|
||||
$delCommentForm->populate(
|
||||
|
@ -126,7 +126,7 @@ if (! $this->compact): ?>
|
||||
</small>
|
||||
</td>
|
||||
<?php if (isset($delDowntimeForm)): // Form is unset if the current user lacks the respective permission ?>
|
||||
<td style="width: 2em" data-base-target="self">
|
||||
<td style="width: 2em" data-base-target="_self">
|
||||
<?php
|
||||
$delDowntimeForm = clone $delDowntimeForm;
|
||||
$delDowntimeForm->populate(
|
||||
|
@ -89,17 +89,34 @@ $this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/serv
|
||||
* Problems Section
|
||||
*/
|
||||
$section = $this->menuSection($this->translate('Problems'), array(
|
||||
'renderer' => 'Icinga\Module\Monitoring\Web\Menu\ProblemMenuItemRenderer',
|
||||
'renderer' => array(
|
||||
'SummaryMenuItemRenderer',
|
||||
'state' => 'critical'
|
||||
),
|
||||
'icon' => 'block',
|
||||
'priority' => 20
|
||||
));
|
||||
$section->add($this->translate('Unhandled Hosts'), array(
|
||||
'renderer' => 'Icinga\Module\Monitoring\Web\Menu\UnhandledHostMenuItemRenderer',
|
||||
'renderer' => array(
|
||||
'Icinga\Module\Monitoring\Web\Menu\MonitoringBadgeMenuItemRenderer',
|
||||
'columns' => array(
|
||||
'hosts_down_unhandled' => $this->translate('%d unhandled hosts down')
|
||||
),
|
||||
'state' => 'critical',
|
||||
'dataView' => 'statussummary'
|
||||
),
|
||||
'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0',
|
||||
'priority' => 30
|
||||
));
|
||||
$section->add($this->translate('Unhandled Services'), array(
|
||||
'renderer' => 'Icinga\Module\Monitoring\Web\Menu\UnhandledServiceMenuItemRenderer',
|
||||
'renderer' => array(
|
||||
'Icinga\Module\Monitoring\Web\Menu\MonitoringBadgeMenuItemRenderer',
|
||||
'columns' => array(
|
||||
'services_critical_unhandled' => $this->translate('%d unhandled services critical')
|
||||
),
|
||||
'state' => 'critical',
|
||||
'dataView' => 'statussummary'
|
||||
),
|
||||
'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity',
|
||||
'priority' => 40
|
||||
));
|
||||
@ -204,7 +221,7 @@ $section = $this->menuSection($this->translate('System'));
|
||||
$section->add($this->translate('Monitoring Health'), array(
|
||||
'url' => 'monitoring/process/info',
|
||||
'priority' => 720,
|
||||
'renderer' => 'Icinga\Module\Monitoring\Web\Menu\BackendAvailabilityMenuItemRenderer'
|
||||
'renderer' => 'Icinga\Module\Monitoring\Web\Menu\BackendAvailabilityMenuItemRenderer'
|
||||
));
|
||||
|
||||
/*
|
||||
|
@ -479,8 +479,12 @@ abstract class IdoQuery extends DbQuery
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addFilter(Filter $filter)
|
||||
{
|
||||
$filter = clone $filter;
|
||||
$this->requireFilterColumns($filter);
|
||||
return parent::addFilter($filter);
|
||||
}
|
||||
|
@ -120,9 +120,10 @@ class MonitoringWizard extends Wizard implements SetupWizard
|
||||
'submit',
|
||||
'backend_validation',
|
||||
array(
|
||||
'ignore' => true,
|
||||
'label' => t('Validate Configuration'),
|
||||
'decorators' => array('ViewHelper')
|
||||
'ignore' => true,
|
||||
'label' => t('Validate Configuration'),
|
||||
'data-progress-label' => t('Validation In Progress'),
|
||||
'decorators' => array('ViewHelper')
|
||||
)
|
||||
);
|
||||
$page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation'));
|
||||
|
@ -4,10 +4,10 @@
|
||||
namespace Icinga\Module\Monitoring\Web\Menu;
|
||||
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Web\Menu\MenuItemRenderer;
|
||||
use Icinga\Web\Menu\BadgeMenuItemRenderer;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
|
||||
class BackendAvailabilityMenuItemRenderer extends MenuItemRenderer
|
||||
class BackendAvailabilityMenuItemRenderer extends BadgeMenuItemRenderer
|
||||
{
|
||||
/**
|
||||
* Get whether or not the monitoring backend is currently running
|
||||
@ -27,47 +27,39 @@ class BackendAvailabilityMenuItemRenderer extends MenuItemRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(Menu $menu)
|
||||
{
|
||||
return $this->getBadge() . $this->createLink($menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the problem badge HTML
|
||||
* The css class of the badge
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getBadge()
|
||||
public function getState()
|
||||
{
|
||||
if (! $this->isCurrentlyRunning()) {
|
||||
return sprintf(
|
||||
'<div title="%s" class="badge-container"><span class="badge badge-critical">%d</span></div>',
|
||||
sprintf(
|
||||
mt('monitoring', 'Monitoring backend %s is not running'), MonitoringBackend::instance()->getName()
|
||||
),
|
||||
1
|
||||
);
|
||||
}
|
||||
return '';
|
||||
return self::STATE_CRITICAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the problem data for the summary
|
||||
* The amount of items to display in the badge
|
||||
*
|
||||
* @return array|null
|
||||
* @return int
|
||||
*/
|
||||
public function getSummary()
|
||||
public function getCount()
|
||||
{
|
||||
if (! $this->isCurrentlyRunning()) {
|
||||
return array(
|
||||
'problems' => 1,
|
||||
'title' => sprintf(
|
||||
mt('monitoring', 'Monitoring backend %s is not running'), MonitoringBackend::instance()->getName()
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
return null;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tooltip title
|
||||
*
|
||||
* @return string
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return sprintf(
|
||||
mt('monitoring', 'Monitoring backend %s is not running'),
|
||||
MonitoringBackend::instance()->getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Menu;
|
||||
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filterable;
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Web\Menu\BadgeMenuItemRenderer;
|
||||
|
||||
/**
|
||||
* Render generic dataView columns as badges in MenuItems
|
||||
*
|
||||
* Renders numeric data view column values into menu item badges, fully configurable
|
||||
* and with a caching mechanism to prevent needless requests to the same data view
|
||||
*/
|
||||
class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer
|
||||
{
|
||||
/**
|
||||
* Caches the responses for all executed summaries
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $summaries = array();
|
||||
|
||||
/**
|
||||
* Accumulates all needed columns for a view to allow fetching the needed columns in
|
||||
* one single query
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $dataViews = array();
|
||||
|
||||
/**
|
||||
* The data view displayed by this menu item
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dataView;
|
||||
|
||||
/**
|
||||
* The columns and titles displayed in the badge
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* The titles that will be used to render this menu item tooltip
|
||||
*
|
||||
* @var String[]
|
||||
*/
|
||||
protected $titles;
|
||||
|
||||
/**
|
||||
* The class of the badge element
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Create a new instance of ColumnMenuItemRenderer
|
||||
*
|
||||
* It is possible to configure the class of the rendered badge as option 'class', the column
|
||||
* to fetch using the option 'column' and the dataView from which the columns will be
|
||||
* fetched using the option 'dataView'.
|
||||
*
|
||||
* @param $configuration ConfigObject The configuration to use
|
||||
*/
|
||||
public function __construct(ConfigObject $configuration)
|
||||
{
|
||||
parent::__construct($configuration);
|
||||
|
||||
$this->columns = $configuration->get('columns');
|
||||
$this->state = $configuration->get('state');
|
||||
$this->dataView = $configuration->get('dataView');
|
||||
|
||||
// clear the outdated summary cache, since new columns are being added. Optimally all menu item are constructed
|
||||
// before any rendering is going on to avoid trashing too man old requests
|
||||
if (isset(self::$summaries[$this->dataView])) {
|
||||
unset(self::$summaries[$this->dataView]);
|
||||
}
|
||||
|
||||
// add the new columns to this view
|
||||
if (! isset(self::$dataViews[$this->dataView])) {
|
||||
self::$dataViews[$this->dataView] = array();
|
||||
}
|
||||
foreach ($this->columns as $column => $title) {
|
||||
if (! array_search($column, self::$dataViews[$this->dataView])) {
|
||||
self::$dataViews[$this->dataView][] = $column;
|
||||
}
|
||||
$this->titles[$column] = $title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a restriction on the given data view
|
||||
*
|
||||
* @param string $restriction The name of restriction
|
||||
* @param Filterable $filterable The filterable to restrict
|
||||
*
|
||||
* @return Filterable The filterable
|
||||
*/
|
||||
protected static function applyRestriction($restriction, Filterable $filterable)
|
||||
{
|
||||
$restrictions = Filter::matchAny();
|
||||
foreach (Auth::getInstance()->getRestrictions($restriction) as $filter) {
|
||||
$restrictions->addFilter(Filter::fromQueryString($filter));
|
||||
}
|
||||
$filterable->applyFilter($restrictions);
|
||||
return $filterable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the response from the database or access cache
|
||||
*
|
||||
* @param $view
|
||||
*
|
||||
* @return null
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
*/
|
||||
protected static function summary($view)
|
||||
{
|
||||
if (! isset(self::$summaries[$view])) {
|
||||
$summary = MonitoringBackend::instance()->select()->from(
|
||||
$view,
|
||||
self::$dataViews[$view]
|
||||
);
|
||||
static::applyRestriction('monitoring/filter/objects', $summary);
|
||||
self::$summaries[$view] = $summary->fetchRow();
|
||||
}
|
||||
return isset(self::$summaries[$view]) ? self::$summaries[$view] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the color of the badge
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of items to display in the badge
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
$sum = self::summary($this->dataView);
|
||||
$count = 0;
|
||||
|
||||
foreach ($this->columns as $col => $title) {
|
||||
if (isset($sum->$col)) {
|
||||
$count += $sum->$col;
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tooltip title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
$titles = array();
|
||||
$sum = $this->summary($this->dataView);
|
||||
foreach ($this->columns as $column => $value) {
|
||||
if (isset($sum->$column) && $sum->$column > 0) {
|
||||
$titles[] = sprintf($this->titles[$column], $sum->$column);
|
||||
}
|
||||
}
|
||||
return implode(', ', $titles);
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Menu;
|
||||
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filterable;
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Web\Menu\MenuItemRenderer;
|
||||
|
||||
class MonitoringMenuItemRenderer extends MenuItemRenderer
|
||||
{
|
||||
protected static $summary;
|
||||
|
||||
protected $columns = array();
|
||||
|
||||
/**
|
||||
* Apply a restriction on the given data view
|
||||
*
|
||||
* @param string $restriction The name of restriction
|
||||
* @param Filterable $filterable The filterable to restrict
|
||||
*
|
||||
* @return Filterable The filterable
|
||||
*/
|
||||
protected static function applyRestriction($restriction, Filterable $filterable)
|
||||
{
|
||||
$restrictions = Filter::matchAny();
|
||||
foreach (Auth::getInstance()->getRestrictions($restriction) as $filter) {
|
||||
$restrictions->addFilter(Filter::fromQueryString($filter));
|
||||
}
|
||||
$filterable->applyFilter($restrictions);
|
||||
return $filterable;
|
||||
}
|
||||
|
||||
protected static function summary($column = null)
|
||||
{
|
||||
if (self::$summary === null) {
|
||||
$summary = MonitoringBackend::instance()->select()->from(
|
||||
'statussummary',
|
||||
array(
|
||||
'hosts_down_unhandled',
|
||||
'services_critical_unhandled'
|
||||
)
|
||||
);
|
||||
static::applyRestriction('monitoring/filter/objects', $summary);
|
||||
self::$summary = $summary->fetchRow();
|
||||
}
|
||||
|
||||
if ($column === null) {
|
||||
return self::$summary;
|
||||
} elseif (isset(self::$summary->$column)) {
|
||||
return self::$summary->$column;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getBadgeTitle()
|
||||
{
|
||||
$translations = array(
|
||||
'hosts_down_unhandled' => mt('monitoring', '%d unhandled hosts down'),
|
||||
'services_critical_unhandled' => mt('monitoring', '%d unhandled services critical')
|
||||
);
|
||||
|
||||
$titles = array();
|
||||
$sum = $this->summary();
|
||||
|
||||
foreach ($this->columns as $col) {
|
||||
if (isset($sum->$col) && $sum->$col > 0) {
|
||||
$titles[] = sprintf($translations[$col], $sum->$col);
|
||||
}
|
||||
}
|
||||
|
||||
return implode(', ', $titles);
|
||||
}
|
||||
|
||||
protected function countItems()
|
||||
{
|
||||
$sum = self::summary();
|
||||
$count = 0;
|
||||
|
||||
foreach ($this->columns as $col) {
|
||||
if (isset($sum->$col)) {
|
||||
$count += $sum->$col;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function render(Menu $menu)
|
||||
{
|
||||
return $this->getBadge() . $this->createLink($menu);
|
||||
}
|
||||
|
||||
protected function getBadge()
|
||||
{
|
||||
if ($count = $this->countItems()) {
|
||||
return sprintf(
|
||||
'<div title="%s" class="badge-container"><span class="badge badge-critical">%s</span></div>',
|
||||
$this->getBadgeTitle(),
|
||||
$count
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Menu;
|
||||
|
||||
class ProblemMenuItemRenderer extends MonitoringMenuItemRenderer
|
||||
{
|
||||
protected $columns = array(
|
||||
'hosts_down_unhandled',
|
||||
'services_critical_unhandled'
|
||||
);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Menu;
|
||||
|
||||
class UnhandledHostMenuItemRenderer extends MonitoringMenuItemRenderer
|
||||
{
|
||||
protected $columns = array(
|
||||
'hosts_down_unhandled',
|
||||
);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Menu;
|
||||
|
||||
class UnhandledServiceMenuItemRenderer extends MonitoringMenuItemRenderer
|
||||
{
|
||||
protected $columns = array(
|
||||
'services_critical_unhandled'
|
||||
);
|
||||
}
|
@ -186,10 +186,6 @@ table.avp {
|
||||
}
|
||||
}
|
||||
|
||||
.autosubmit-warning {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.object-features {
|
||||
label {
|
||||
font-weight: normal;
|
||||
|
@ -2,84 +2,83 @@
|
||||
|
||||
(function(Icinga) {
|
||||
|
||||
var Monitoring = function(module) {
|
||||
/**
|
||||
* The Icinga.Module instance
|
||||
*/
|
||||
this.module = module;
|
||||
var Monitoring = function(module) {
|
||||
/**
|
||||
* The Icinga.Module instance
|
||||
*/
|
||||
this.module = module;
|
||||
|
||||
/**
|
||||
* The observer used to handle the timeline's infinite loading
|
||||
*/
|
||||
this.scrollCheckTimer = null;
|
||||
|
||||
/**
|
||||
* Whether to skip the timeline's scroll-check
|
||||
*/
|
||||
this.skipScrollCheck = false;
|
||||
|
||||
this.initialize();
|
||||
};
|
||||
|
||||
Monitoring.prototype = {
|
||||
|
||||
initialize: function()
|
||||
{
|
||||
this.module.on('rendered', this.enableScrollCheck);
|
||||
this.module.icinga.logger.debug('Monitoring module loaded');
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable the timeline's scroll-check
|
||||
*/
|
||||
enableScrollCheck: function()
|
||||
{
|
||||
/**
|
||||
* Re-enable the scroll-check in case the timeline has just been extended
|
||||
*/
|
||||
if (this.skipScrollCheck) {
|
||||
this.skipScrollCheck = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the timer to handle the timeline's infinite loading
|
||||
*/
|
||||
var $timeline = $('div.timeline');
|
||||
if ($timeline.length && !$timeline.closest('.dashboard').length) {
|
||||
if (this.scrollCheckTimer === null) {
|
||||
this.scrollCheckTimer = this.module.icinga.timer.register(
|
||||
this.checkTimelinePosition,
|
||||
this,
|
||||
800
|
||||
);
|
||||
this.module.icinga.logger.debug('Enabled timeline scroll-check');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether the user scrolled to the end of the timeline
|
||||
*/
|
||||
checkTimelinePosition: function()
|
||||
{
|
||||
if (!$('div.timeline').length) {
|
||||
this.module.icinga.timer.unregister(this.scrollCheckTimer);
|
||||
/**
|
||||
* The observer used to handle the timeline's infinite loading
|
||||
*/
|
||||
this.scrollCheckTimer = null;
|
||||
this.module.icinga.logger.debug('Disabled timeline scroll-check');
|
||||
} else if (!this.skipScrollCheck && this.module.icinga.utils.isVisible('#end')) {
|
||||
this.skipScrollCheck = true;
|
||||
this.module.icinga.loader.loadUrl(
|
||||
$('#end').remove().attr('href'),
|
||||
$('div.timeline'),
|
||||
undefined,
|
||||
undefined,
|
||||
'append'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
/**
|
||||
* Whether to skip the timeline's scroll-check
|
||||
*/
|
||||
this.skipScrollCheck = false;
|
||||
|
||||
Icinga.availableModules.monitoring = Monitoring;
|
||||
this.initialize();
|
||||
};
|
||||
|
||||
Monitoring.prototype = {
|
||||
|
||||
initialize: function()
|
||||
{
|
||||
this.module.on('rendered', this.enableScrollCheck);
|
||||
this.module.icinga.logger.debug('Monitoring module loaded');
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable the timeline's scroll-check
|
||||
*/
|
||||
enableScrollCheck: function()
|
||||
{
|
||||
/**
|
||||
* Re-enable the scroll-check in case the timeline has just been extended
|
||||
*/
|
||||
if (this.skipScrollCheck) {
|
||||
this.skipScrollCheck = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the timer to handle the timeline's infinite loading
|
||||
*/
|
||||
var $timeline = $('div.timeline');
|
||||
if ($timeline.length && !$timeline.closest('.dashboard').length) {
|
||||
if (this.scrollCheckTimer === null) {
|
||||
this.scrollCheckTimer = this.module.icinga.timer.register(
|
||||
this.checkTimelinePosition,
|
||||
this,
|
||||
800
|
||||
);
|
||||
this.module.icinga.logger.debug('Enabled timeline scroll-check');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether the user scrolled to the end of the timeline
|
||||
*/
|
||||
checkTimelinePosition: function()
|
||||
{
|
||||
if (!$('div.timeline').length) {
|
||||
this.module.icinga.timer.unregister(this.scrollCheckTimer);
|
||||
this.scrollCheckTimer = null;
|
||||
this.module.icinga.logger.debug('Disabled timeline scroll-check');
|
||||
} else if (!this.skipScrollCheck && this.module.icinga.utils.isVisible('#end')) {
|
||||
this.skipScrollCheck = true;
|
||||
this.module.icinga.loader.loadUrl(
|
||||
$('#end').remove().attr('href'),
|
||||
$('div.timeline'),
|
||||
undefined,
|
||||
undefined,
|
||||
'append'
|
||||
).addToHistory = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Icinga.availableModules.monitoring = Monitoring;
|
||||
|
||||
}(Icinga));
|
||||
|
@ -3,11 +3,18 @@
|
||||
use Icinga\Web\Wizard;
|
||||
|
||||
?>
|
||||
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>">
|
||||
<form
|
||||
id="<?= $form->getName(); ?>"
|
||||
name="<?= $form->getName(); ?>"
|
||||
enctype="<?= $form->getEncType(); ?>"
|
||||
method="<?= $form->getMethod(); ?>"
|
||||
action="<?= $form->getAction(); ?>"
|
||||
data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
|
||||
>
|
||||
<h2><?= $this->translate('Modules', 'setup.page.title'); ?></h2>
|
||||
<p><?= $this->translate('The following modules were found in your Icinga Web 2 installation. To enable and configure a module, just tick it and click "Next".'); ?></p>
|
||||
<?php foreach ($form->getElements() as $element): ?>
|
||||
<?php if (! in_array($element->getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, $form->getTokenElementName(), $form->getUidElementName()))): ?>
|
||||
<?php if (! in_array($element->getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, Wizard::PROGRESS_ELEMENT, $form->getTokenElementName(), $form->getUidElementName()))): ?>
|
||||
<div class="module">
|
||||
<h3><label for="<?= $element->getId(); ?>"><strong><?= $element->getLabel(); ?></strong></label></h3>
|
||||
<label for="<?= $element->getId(); ?>"><?= $element->getDescription(); ?></label>
|
||||
@ -20,5 +27,6 @@ use Icinga\Web\Wizard;
|
||||
<div class="buttons">
|
||||
<?= $form->getElement(Wizard::BTN_PREV); ?>
|
||||
<?= $form->getElement(Wizard::BTN_NEXT); ?>
|
||||
<?= $form->getElement(Wizard::PROGRESS_ELEMENT); ?>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -9,7 +9,14 @@ use Icinga\Web\Wizard;
|
||||
<h1><?= ucwords($moduleName) . ' ' . $this->translate('Module'); ?></h1>
|
||||
<?= $wizard->getRequirements(); ?>
|
||||
<?php endforeach ?>
|
||||
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>">
|
||||
<form
|
||||
id="<?= $form->getName(); ?>"
|
||||
name="<?= $form->getName(); ?>"
|
||||
enctype="<?= $form->getEncType(); ?>"
|
||||
method="<?= $form->getMethod(); ?>"
|
||||
action="<?= $form->getAction(); ?>"
|
||||
data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
|
||||
>
|
||||
<?= $form->getElement($form->getTokenElementName()); ?>
|
||||
<?= $form->getElement($form->getUidElementName()); ?>
|
||||
<div class="buttons">
|
||||
@ -21,6 +28,7 @@ use Icinga\Web\Wizard;
|
||||
}
|
||||
echo $btn;
|
||||
?>
|
||||
<?= $form->getElement(Wizard::PROGRESS_ELEMENT); ?>
|
||||
<div class="requirements-refresh">
|
||||
<?php $title = $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?>
|
||||
<?= $this->qlink(
|
||||
|
@ -20,11 +20,20 @@ use Icinga\Web\Wizard;
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>" class="summary">
|
||||
<form
|
||||
id="<?= $form->getName(); ?>"
|
||||
name="<?= $form->getName(); ?>"
|
||||
enctype="<?= $form->getEncType(); ?>"
|
||||
method="<?= $form->getMethod(); ?>"
|
||||
action="<?= $form->getAction(); ?>"
|
||||
data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
|
||||
class="summary"
|
||||
>
|
||||
<?= $form->getElement($form->getTokenElementName()); ?>
|
||||
<?= $form->getElement($form->getUidElementName()); ?>
|
||||
<div class="buttons">
|
||||
<?= $form->getElement(Wizard::BTN_PREV); ?>
|
||||
<?= $form->getElement(Wizard::BTN_NEXT)->setAttrib('class', 'finish'); ?>
|
||||
<?= $form->getElement(Wizard::PROGRESS_ELEMENT); ?>
|
||||
</div>
|
||||
</form>
|
@ -53,6 +53,10 @@ class GeneralConfigStep extends Step
|
||||
|
||||
$generalHtml = ''
|
||||
. '<ul>'
|
||||
. '<li>' . ($this->data['generalConfig']['global_show_stacktraces']
|
||||
? t('An exception\'s stacktrace is shown to every user by default.')
|
||||
: t('An exception\'s stacktrace is hidden from every user by default.')
|
||||
) . '</li>'
|
||||
. '<li>' . sprintf(
|
||||
$this->data['generalConfig']['global_config_backend'] === 'ini' ? sprintf(
|
||||
t('Preferences will be stored per user account in INI files at: %s'),
|
||||
|
@ -369,9 +369,10 @@ class WebWizard extends Wizard implements SetupWizard
|
||||
'submit',
|
||||
'backend_validation',
|
||||
array(
|
||||
'ignore' => true,
|
||||
'label' => t('Validate Configuration'),
|
||||
'decorators' => array('ViewHelper')
|
||||
'ignore' => true,
|
||||
'label' => t('Validate Configuration'),
|
||||
'data-progress-label' => t('Validation In Progress'),
|
||||
'decorators' => array('ViewHelper')
|
||||
)
|
||||
);
|
||||
$page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation'));
|
||||
|
@ -79,4 +79,269 @@
|
||||
-webkit-transform: rotate(359deg);
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes move-vertical {
|
||||
0% {
|
||||
-moz-transform: translate(0, 100%);
|
||||
-o-transform: translate(0, 100%);
|
||||
-webkit-transform: translate(0, 100%);
|
||||
transform: translate(0, 100%);
|
||||
}
|
||||
|
||||
17% {
|
||||
-moz-transform: translate(0, 66%);
|
||||
-o-transform: translate(0, 66%);
|
||||
-webkit-transform: translate(0, 66%);
|
||||
transform: translate(0, 66%);
|
||||
}
|
||||
|
||||
33% {
|
||||
-moz-transform: translate(0, 33%);
|
||||
-o-transform: translate(0, 33%);
|
||||
-webkit-transform: translate(0, 33%);
|
||||
transform: translate(0, 33%);
|
||||
}
|
||||
|
||||
50% {
|
||||
-moz-transform: translate(0, 0);
|
||||
-o-transform: translate(0, 0);
|
||||
-webkit-transform: translate(0, 0);
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
67% {
|
||||
-moz-transform: translate(0, -33%);
|
||||
-o-transform: translate(0, -33%);
|
||||
-webkit-transform: translate(0, -33%);
|
||||
transform: translate(0, -33%);
|
||||
}
|
||||
|
||||
83% {
|
||||
-moz-transform: translate(0, -66%);
|
||||
-o-transform: translate(0, -66%);
|
||||
-webkit-transform: translate(0, -66%);
|
||||
transform: translate(0, -66%);
|
||||
}
|
||||
|
||||
100% {
|
||||
-moz-transform: translate(0, -100%);
|
||||
-o-transform: translate(0, -100%);
|
||||
-webkit-transform: translate(0, -100%);
|
||||
transform: translate(0, -100%);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes move-vertical {
|
||||
0% {
|
||||
-moz-transform: translate(0, 100%);
|
||||
-o-transform: translate(0, 100%);
|
||||
-webkit-transform: translate(0, 100%);
|
||||
transform: translate(0, 100%);
|
||||
}
|
||||
|
||||
17% {
|
||||
-moz-transform: translate(0, 66%);
|
||||
-o-transform: translate(0, 66%);
|
||||
-webkit-transform: translate(0, 66%);
|
||||
transform: translate(0, 66%);
|
||||
}
|
||||
|
||||
33% {
|
||||
-moz-transform: translate(0, 33%);
|
||||
-o-transform: translate(0, 33%);
|
||||
-webkit-transform: translate(0, 33%);
|
||||
transform: translate(0, 33%);
|
||||
}
|
||||
|
||||
50% {
|
||||
-moz-transform: translate(0, 0);
|
||||
-o-transform: translate(0, 0);
|
||||
-webkit-transform: translate(0, 0);
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
67% {
|
||||
-moz-transform: translate(0, -33%);
|
||||
-o-transform: translate(0, -33%);
|
||||
-webkit-transform: translate(0, -33%);
|
||||
transform: translate(0, -33%);
|
||||
}
|
||||
|
||||
83% {
|
||||
-moz-transform: translate(0, -66%);
|
||||
-o-transform: translate(0, -66%);
|
||||
-webkit-transform: translate(0, -66%);
|
||||
transform: translate(0, -66%);
|
||||
}
|
||||
|
||||
100% {
|
||||
-moz-transform: translate(0, -100%);
|
||||
-o-transform: translate(0, -100%);
|
||||
-webkit-transform: translate(0, -100%);
|
||||
transform: translate(0, -100%);
|
||||
}
|
||||
}
|
||||
@-o-keyframes move-vertical {
|
||||
0% {
|
||||
-moz-transform: translate(0, 100%);
|
||||
-o-transform: translate(0, 100%);
|
||||
-webkit-transform: translate(0, 100%);
|
||||
transform: translate(0, 100%);
|
||||
}
|
||||
|
||||
17% {
|
||||
-moz-transform: translate(0, 66%);
|
||||
-o-transform: translate(0, 66%);
|
||||
-webkit-transform: translate(0, 66%);
|
||||
transform: translate(0, 66%);
|
||||
}
|
||||
|
||||
33% {
|
||||
-moz-transform: translate(0, 33%);
|
||||
-o-transform: translate(0, 33%);
|
||||
-webkit-transform: translate(0, 33%);
|
||||
transform: translate(0, 33%);
|
||||
}
|
||||
|
||||
50% {
|
||||
-moz-transform: translate(0, 0);
|
||||
-o-transform: translate(0, 0);
|
||||
-webkit-transform: translate(0, 0);
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
67% {
|
||||
-moz-transform: translate(0, -33%);
|
||||
-o-transform: translate(0, -33%);
|
||||
-webkit-transform: translate(0, -33%);
|
||||
transform: translate(0, -33%);
|
||||
}
|
||||
|
||||
83% {
|
||||
-moz-transform: translate(0, -66%);
|
||||
-o-transform: translate(0, -66%);
|
||||
-webkit-transform: translate(0, -66%);
|
||||
transform: translate(0, -66%);
|
||||
}
|
||||
|
||||
100% {
|
||||
-moz-transform: translate(0, -100%);
|
||||
-o-transform: translate(0, -100%);
|
||||
-webkit-transform: translate(0, -100%);
|
||||
transform: translate(0, -100%);
|
||||
}
|
||||
}
|
||||
@-ms-keyframes move-vertical {
|
||||
0% {
|
||||
-moz-transform: translate(0, 100%);
|
||||
-o-transform: translate(0, 100%);
|
||||
-webkit-transform: translate(0, 100%);
|
||||
transform: translate(0, 100%);
|
||||
}
|
||||
|
||||
17% {
|
||||
-moz-transform: translate(0, 66%);
|
||||
-o-transform: translate(0, 66%);
|
||||
-webkit-transform: translate(0, 66%);
|
||||
transform: translate(0, 66%);
|
||||
}
|
||||
|
||||
33% {
|
||||
-moz-transform: translate(0, 33%);
|
||||
-o-transform: translate(0, 33%);
|
||||
-webkit-transform: translate(0, 33%);
|
||||
transform: translate(0, 33%);
|
||||
}
|
||||
|
||||
50% {
|
||||
-moz-transform: translate(0, 0);
|
||||
-o-transform: translate(0, 0);
|
||||
-webkit-transform: translate(0, 0);
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
67% {
|
||||
-moz-transform: translate(0, -33%);
|
||||
-o-transform: translate(0, -33%);
|
||||
-webkit-transform: translate(0, -33%);
|
||||
transform: translate(0, -33%);
|
||||
}
|
||||
|
||||
83% {
|
||||
-moz-transform: translate(0, -66%);
|
||||
-o-transform: translate(0, -66%);
|
||||
-webkit-transform: translate(0, -66%);
|
||||
transform: translate(0, -66%);
|
||||
}
|
||||
|
||||
100% {
|
||||
-moz-transform: translate(0, -100%);
|
||||
-o-transform: translate(0, -100%);
|
||||
-webkit-transform: translate(0, -100%);
|
||||
transform: translate(0, -100%);
|
||||
}
|
||||
}
|
||||
@keyframes move-vertical {
|
||||
0% {
|
||||
-moz-transform: translate(0, 100%);
|
||||
-o-transform: translate(0, 100%);
|
||||
-webkit-transform: translate(0, 100%);
|
||||
transform: translate(0, 100%);
|
||||
}
|
||||
|
||||
17% {
|
||||
-moz-transform: translate(0, 66%);
|
||||
-o-transform: translate(0, 66%);
|
||||
-webkit-transform: translate(0, 66%);
|
||||
transform: translate(0, 66%);
|
||||
}
|
||||
|
||||
33% {
|
||||
-moz-transform: translate(0, 33%);
|
||||
-o-transform: translate(0, 33%);
|
||||
-webkit-transform: translate(0, 33%);
|
||||
transform: translate(0, 33%);
|
||||
}
|
||||
|
||||
50% {
|
||||
-moz-transform: translate(0, 0);
|
||||
-o-transform: translate(0, 0);
|
||||
-webkit-transform: translate(0, 0);
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
67% {
|
||||
-moz-transform: translate(0, -33%);
|
||||
-o-transform: translate(0, -33%);
|
||||
-webkit-transform: translate(0, -33%);
|
||||
transform: translate(0, -33%);
|
||||
}
|
||||
|
||||
83% {
|
||||
-moz-transform: translate(0, -66%);
|
||||
-o-transform: translate(0, -66%);
|
||||
-webkit-transform: translate(0, -66%);
|
||||
transform: translate(0, -66%);
|
||||
}
|
||||
|
||||
100% {
|
||||
-moz-transform: translate(0, -100%);
|
||||
-o-transform: translate(0, -100%);
|
||||
-webkit-transform: translate(0, -100%);
|
||||
transform: translate(0, -100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
@ -107,6 +107,38 @@ form.inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.spinner {
|
||||
display: inline-block;
|
||||
margin-top: 0.2em;
|
||||
margin-left: 0.25em;
|
||||
|
||||
i {
|
||||
visibility: hidden;
|
||||
|
||||
&.active {
|
||||
visibility: visible;
|
||||
|
||||
&:before {
|
||||
.animate(spin 2s infinite linear);
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
margin: 0; // Disables wobbling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.animated.active {
|
||||
&.move-up i:before {
|
||||
.animate(move-vertical 500ms infinite linear);
|
||||
}
|
||||
|
||||
&.move-down i:before {
|
||||
.animate(move-vertical 500ms infinite linear reverse);
|
||||
}
|
||||
}
|
||||
|
||||
button, .button-like {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
@ -252,12 +284,18 @@ form div.element {
|
||||
|
||||
form label {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-top: 0.25em;
|
||||
margin-right: 1em;
|
||||
font-size: 0.9em;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
form div.element > * {
|
||||
form div.element i.help:before {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
label ~ * {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@ -295,11 +333,11 @@ select.grant-permissions {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
label ~ input, label ~ select {
|
||||
margin-left: 1.35em;
|
||||
label ~ input, label ~ select, label ~ textarea {
|
||||
margin-left: 1.3em;
|
||||
}
|
||||
|
||||
label + i ~ input, label + i ~ select {
|
||||
label + i ~ input, label + i ~ select, label + i ~ textarea {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@ -307,6 +345,21 @@ button.noscript-apply {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
i.autosubmit-warning {
|
||||
display: inline-block;
|
||||
margin-left: 0.2em;
|
||||
margin-top: 0.25em;
|
||||
|
||||
&:before {
|
||||
margin: 0; // Disables wobbling
|
||||
}
|
||||
|
||||
&.spinning:before {
|
||||
content: '\e874'; // icon-spin6
|
||||
.animate(spin 2s infinite linear);
|
||||
}
|
||||
}
|
||||
|
||||
html.no-js i.autosubmit-warning {
|
||||
.sr-only;
|
||||
}
|
||||
|
@ -338,3 +338,15 @@ li li .badge {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.progress-label span {
|
||||
font-size: 1.5em;
|
||||
.animate(blink 1.4s infinite both);
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: .2s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: .4s;
|
||||
}
|
||||
}
|
||||
|
@ -212,6 +212,7 @@
|
||||
var method = $form.attr('method');
|
||||
var encoding = $form.attr('enctype');
|
||||
var $button = $('input[type=submit]:focus', $form).add('button[type=submit]:focus', $form);
|
||||
var progressTimer;
|
||||
var $target;
|
||||
var data;
|
||||
|
||||
@ -227,11 +228,11 @@
|
||||
icinga.logger.debug('events/submitForm: Button is event.currentTarget');
|
||||
}
|
||||
|
||||
if ($el && ($el.is('input[type=submit]') || $el.is('button[type=submit]'))) {
|
||||
if ($el && ($el.is('input[type=submit]') || $el.is('button[type=submit]')) && $el.is(':focus')) {
|
||||
$button = $el;
|
||||
} else {
|
||||
icinga.logger.debug(
|
||||
'events/submitForm: Can not determine submit button, using the first one in form'
|
||||
'events/submitForm: Can not determine submit button, using the last one in form'
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -246,8 +247,12 @@
|
||||
encoding = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
if (typeof autosubmit === 'undefined') {
|
||||
autosubmit = false;
|
||||
}
|
||||
|
||||
if ($button.length === 0) {
|
||||
$button = $('input[type=submit]', $form).add('button[type=submit]', $form).first();
|
||||
$button = $('button[type=submit]', $form).add('input[type=submit]', $form).last();
|
||||
}
|
||||
|
||||
if ($button.length) {
|
||||
@ -271,7 +276,7 @@
|
||||
if (method === 'GET') {
|
||||
var dataObj = $form.serializeObject();
|
||||
|
||||
if (typeof autosubmit === 'undefined' || ! autosubmit) {
|
||||
if (! autosubmit) {
|
||||
if ($button.length && $button.attr('name') !== 'undefined') {
|
||||
dataObj[$button.attr('name')] = $button.attr('value');
|
||||
}
|
||||
@ -289,7 +294,7 @@
|
||||
$form.find(':input:not(:disabled)').prop('disabled', true);
|
||||
}, 0);
|
||||
|
||||
if (! typeof autosubmit === 'undefined' && autosubmit) {
|
||||
if (autosubmit) {
|
||||
if ($button.length) {
|
||||
// We're autosubmitting the form so the button has not been clicked, however,
|
||||
// to be really safe, we're disabling the button explicitly, just in case..
|
||||
@ -310,7 +315,7 @@
|
||||
data = $form.serializeArray();
|
||||
}
|
||||
|
||||
if (typeof autosubmit === 'undefined' || ! autosubmit) {
|
||||
if (! autosubmit) {
|
||||
if ($button.length && $button.attr('name') !== 'undefined') {
|
||||
if (encoding === 'multipart/form-data') {
|
||||
data.append($button.attr('name'), $button.attr('value'));
|
||||
@ -328,7 +333,55 @@
|
||||
// Note that disabled form inputs will not be enabled via JavaScript again
|
||||
$form.find(':input:not(#search):not(:disabled)').prop('disabled', true);
|
||||
|
||||
icinga.loader.loadUrl(url, $target, data, method);
|
||||
// Show a spinner depending on how the form is being submitted
|
||||
if (autosubmit && typeof $el !== 'undefined' && $el.next().hasClass('autosubmit-warning')) {
|
||||
$el.next().addClass('spinning');
|
||||
} else if ($button.length && $button.is('button') && $button.hasClass('animated')) {
|
||||
$button.addClass('active');
|
||||
} else if ($button.length && $button.attr('data-progress-label')) {
|
||||
var isInput = $button.is('input');
|
||||
if (isInput) {
|
||||
$button.prop('value', $button.attr('data-progress-label') + '...');
|
||||
} else {
|
||||
$button.html($button.attr('data-progress-label') + '...');
|
||||
}
|
||||
|
||||
// Use a fixed width to prevent the button from wobbling
|
||||
$button.css('width', $button.css('width'));
|
||||
|
||||
progressTimer = icinga.timer.register(function () {
|
||||
var label = isInput ? $button.prop('value') : $button.html();
|
||||
var dots = label.substr(-3);
|
||||
|
||||
// Using empty spaces here to prevent centered labels from wobbling
|
||||
if (dots === '...') {
|
||||
label = label.slice(0, -2) + ' ';
|
||||
} else if (dots === '.. ') {
|
||||
label = label.slice(0, -1) + '.';
|
||||
} else if (dots === '. ') {
|
||||
label = label.slice(0, -2) + '. ';
|
||||
}
|
||||
|
||||
if (isInput) {
|
||||
$button.prop('value', label);
|
||||
} else {
|
||||
$button.html(label);
|
||||
}
|
||||
}, null, 100);
|
||||
} else if ($button.length && $button.next().hasClass('spinner')) {
|
||||
$('i', $button.next()).addClass('active');
|
||||
} else if ($form.attr('data-progress-element')) {
|
||||
var $progressElement = $('#' + $form.attr('data-progress-element'));
|
||||
if ($progressElement.length) {
|
||||
if ($progressElement.hasClass('spinner')) {
|
||||
$('i', $progressElement).addClass('active');
|
||||
} else {
|
||||
$('i.autosubmit-warning', $progressElement).addClass('spinning');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
icinga.loader.loadUrl(url, $target, data, method).progressTimer = progressTimer;
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
@ -509,6 +562,9 @@
|
||||
self.icinga.ui.layout1col();
|
||||
} else {
|
||||
$target = $('#' + targetId);
|
||||
if (! $target.length) {
|
||||
self.icinga.logger.warn('Link target "#' + targetId + '" does not exist in DOM.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -42,13 +42,15 @@
|
||||
/**
|
||||
* Load the given URL to the given target
|
||||
*
|
||||
* @param {string} url URL to be loaded
|
||||
* @param {object} target Target jQuery element
|
||||
* @param {object} data Optional parameters, usually for POST requests
|
||||
* @param {string} method HTTP method, default is 'GET'
|
||||
* @param {string} action How to handle the response ('replace' or 'append'), default is 'replace'
|
||||
* @param {string} url URL to be loaded
|
||||
* @param {object} target Target jQuery element
|
||||
* @param {object} data Optional parameters, usually for POST requests
|
||||
* @param {string} method HTTP method, default is 'GET'
|
||||
* @param {string} action How to handle the response ('replace' or 'append'), default is 'replace'
|
||||
* @param {boolean} autorefresh Whether the cause is a autorefresh or not
|
||||
* @param {object} progressTimer A timer to be stopped when the request is done
|
||||
*/
|
||||
loadUrl: function (url, $target, data, method, action, autorefresh) {
|
||||
loadUrl: function (url, $target, data, method, action, autorefresh, progressTimer) {
|
||||
var id = null;
|
||||
|
||||
// Default method is GET
|
||||
@ -131,6 +133,7 @@
|
||||
req.autorefresh = autorefresh;
|
||||
req.action = action;
|
||||
req.addToHistory = true;
|
||||
req.progressTimer = progressTimer;
|
||||
|
||||
if (id) {
|
||||
this.requests[id] = req;
|
||||
@ -537,6 +540,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof req.progressTimer !== 'undefined') {
|
||||
this.icinga.timer.unregister(req.progressTimer);
|
||||
}
|
||||
|
||||
// .html() removes outer div we added above
|
||||
this.renderContentToContainer($resp.html(), req.$target, req.action, req.autorefresh);
|
||||
if (oldNotifications) {
|
||||
@ -638,6 +645,10 @@
|
||||
req.$target.data('icingaUrl', url);
|
||||
}
|
||||
|
||||
if (typeof req.progressTimer !== 'undefined') {
|
||||
this.icinga.timer.unregister(req.progressTimer);
|
||||
}
|
||||
|
||||
if (req.status > 0) {
|
||||
this.icinga.logger.error(
|
||||
req.status,
|
||||
|
Loading…
x
Reference in New Issue
Block a user