commit
8006090108
|
@ -6,6 +6,7 @@ namespace Icinga\Controllers;
|
|||
use Icinga\Application\Icinga;
|
||||
use Icinga\Forms\Authentication\LoginForm;
|
||||
use Icinga\Web\Controller;
|
||||
use Icinga\Web\Helper\CookieHelper;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
|
@ -37,13 +38,14 @@ class AuthenticationController extends Controller
|
|||
$this->redirectNow($form->getRedirectUrl());
|
||||
}
|
||||
if (! $requiresSetup) {
|
||||
if (! $this->getRequest()->hasCookieSupport()) {
|
||||
$cookies = new CookieHelper($this->getRequest());
|
||||
if (! $cookies->isSupported()) {
|
||||
$this
|
||||
->getResponse()
|
||||
->setBody("Cookies must be enabled to run this application.\n")
|
||||
->setHttpResponseCode(403)
|
||||
->sendResponse();
|
||||
exit();
|
||||
exit;
|
||||
}
|
||||
$form->handleRequest();
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class ErrorController extends ActionController
|
|||
Logger::error('Stacktrace: %s', $exception->getTraceAsString());
|
||||
|
||||
if (! ($isAuthenticated = $this->Auth()->isAuthenticated())) {
|
||||
$this->innerLayout = 'error';
|
||||
$this->innerLayout = 'guest-error';
|
||||
}
|
||||
|
||||
switch ($error->type) {
|
||||
|
@ -89,6 +89,7 @@ class ErrorController extends ActionController
|
|||
if ($this->getInvokeArg('displayExceptions')) {
|
||||
$this->view->stackTrace = $exception->getTraceAsString();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ use Icinga\Web\Controller;
|
|||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Web\FileCache;
|
||||
use Icinga\Web\LessCompiler;
|
||||
|
||||
/**
|
||||
* Delivery static content to clients
|
||||
|
@ -87,94 +86,4 @@ class StaticController extends Controller
|
|||
|
||||
readfile($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a javascript file from the application's or the module's public folder
|
||||
*/
|
||||
public function javascriptAction()
|
||||
{
|
||||
$module = $this->_getParam('module_name');
|
||||
$file = $this->_getParam('file');
|
||||
|
||||
if ($module == 'app') {
|
||||
$basedir = Icinga::app()->getApplicationDir('../public/js/icinga/components/');
|
||||
$filePath = $basedir . $file;
|
||||
} else {
|
||||
if (! Icinga::app()->getModuleManager()->hasEnabled($module)) {
|
||||
Logger::error(
|
||||
'Non-existing frontend component "' . $module . '/' . $file
|
||||
. '" was requested. The module "' . $module . '" does not exist or is not active.'
|
||||
);
|
||||
echo "/** Module not enabled **/";
|
||||
return;
|
||||
}
|
||||
$basedir = Icinga::app()->getModuleManager()->getModule($module)->getBaseDir();
|
||||
$filePath = $basedir . '/public/js/' . $file;
|
||||
}
|
||||
|
||||
if (! file_exists($filePath)) {
|
||||
Logger::error(
|
||||
'Non-existing frontend component "' . $module . '/' . $file
|
||||
. '" was requested, which would resolve to the the path: ' . $filePath
|
||||
);
|
||||
echo '/** Module has no js files **/';
|
||||
return;
|
||||
}
|
||||
$response = $this->getResponse();
|
||||
$response->setHeader('Content-Type', 'text/javascript');
|
||||
$this->setCacheHeader();
|
||||
|
||||
$response->setHeader(
|
||||
'Last-Modified',
|
||||
gmdate('D, d M Y H:i:s', filemtime($filePath)) . ' GMT'
|
||||
);
|
||||
|
||||
readfile($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache header for the response
|
||||
*
|
||||
* @param int $maxAge The maximum age to set
|
||||
*/
|
||||
private function setCacheHeader($maxAge = 3600)
|
||||
{
|
||||
$maxAge = (int) $maxAge;
|
||||
$this
|
||||
->getResponse()
|
||||
->setHeader('Cache-Control', sprintf('max-age=%d', $maxAge), true)
|
||||
->setHeader('Pragma', 'cache', true)
|
||||
->setHeader(
|
||||
'Expires',
|
||||
gmdate('D, d M Y H:i:s', time() + $maxAge) . ' GMT',
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send application's and modules' CSS
|
||||
*/
|
||||
public function stylesheetAction()
|
||||
{
|
||||
$lessCompiler = new LessCompiler();
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
|
||||
$publicDir = realpath(dirname($_SERVER['SCRIPT_FILENAME']));
|
||||
|
||||
$lessCompiler->addItem($publicDir . '/css/vendor');
|
||||
$lessCompiler->addItem($publicDir . '/css/icinga');
|
||||
|
||||
foreach ($moduleManager->getLoadedModules() as $moduleName) {
|
||||
$cssDir = $moduleName->getCssDir();
|
||||
|
||||
if (is_dir($cssDir)) {
|
||||
$lessCompiler->addItem($cssDir);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getResponse()->setHeader('Content-Type', 'text/css');
|
||||
$this->setCacheHeader(3600);
|
||||
|
||||
$lessCompiler->printStack();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,14 @@ use Icinga\Data\ResourceFactory;
|
|||
use Icinga\Web\Form;
|
||||
|
||||
/**
|
||||
* Form class to modify the general application configuration
|
||||
* Configuration form for general application options
|
||||
*
|
||||
* This form is not used directly but as subform to the {@link GeneralConfigForm}.
|
||||
*/
|
||||
class ApplicationConfigForm extends Form
|
||||
{
|
||||
/**
|
||||
* Initialize this form
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
|
@ -21,7 +23,9 @@ class ApplicationConfigForm extends Form
|
|||
}
|
||||
|
||||
/**
|
||||
* @see Form::createElements()
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
|
@ -68,6 +72,7 @@ class ApplicationConfigForm extends Form
|
|||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (isset($formData['global_config_backend']) && $formData['global_config_backend'] === 'db') {
|
||||
$backends = array();
|
||||
foreach (ResourceFactory::getResourceConfigs()->toArray() as $name => $resource) {
|
||||
|
|
|
@ -6,10 +6,15 @@ namespace Icinga\Forms\Config\General;
|
|||
use Icinga\Application\Logger;
|
||||
use Icinga\Web\Form;
|
||||
|
||||
/**
|
||||
* Configuration form for logging options
|
||||
*
|
||||
* This form is not used directly but as subform for the {@link GeneralConfigForm}.
|
||||
*/
|
||||
class LoggingConfigForm extends Form
|
||||
{
|
||||
/**
|
||||
* Initialize this form
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
|
@ -17,8 +22,9 @@ class LoggingConfigForm extends Form
|
|||
}
|
||||
|
||||
/**
|
||||
* (non-PHPDoc)
|
||||
* @see Form::createElements() For the method documentation.
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Forms\Config\General;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Web\Form;
|
||||
|
||||
/**
|
||||
* Configuration form for theming options
|
||||
*
|
||||
* This form is not used directly but as subform for the {@link GeneralConfigForm}.
|
||||
*/
|
||||
class ThemingConfigForm extends Form
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->setName('form_config_general_theming');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
$this->addElement(
|
||||
'select',
|
||||
'themes_default',
|
||||
array(
|
||||
'description' => $this->translate('The default theme', 'Form element description'),
|
||||
'label' => $this->translate('Default Theme', 'Form element label'),
|
||||
'multiOptions' => Icinga::app()->getThemes()
|
||||
)
|
||||
);
|
||||
|
||||
$this->addElement(
|
||||
'checkbox',
|
||||
'themes_disabled',
|
||||
array(
|
||||
'description' => $this->translate(
|
||||
'Check this box for disallowing users to change the theme. If a default theme is set, it will be'
|
||||
. ' used nonetheless',
|
||||
'Form element description'
|
||||
),
|
||||
'label' => $this->translate('Disable Themes', 'Form element label')
|
||||
)
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues($suppressArrayNotation);
|
||||
if ($values['themes_default'] === 'Icinga') {
|
||||
$values['themes_default'] = null;
|
||||
}
|
||||
if (! $values['themes_disabled']) {
|
||||
$values['themes_disabled'] = null;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
}
|
|
@ -5,15 +5,16 @@ namespace Icinga\Forms\Config;
|
|||
|
||||
use Icinga\Forms\Config\General\ApplicationConfigForm;
|
||||
use Icinga\Forms\Config\General\LoggingConfigForm;
|
||||
use Icinga\Forms\Config\General\ThemingConfigForm;
|
||||
use Icinga\Forms\ConfigForm;
|
||||
|
||||
/**
|
||||
* Form class for application-wide and logging specific settings
|
||||
* Configuration form for application-wide options
|
||||
*/
|
||||
class GeneralConfigForm extends ConfigForm
|
||||
{
|
||||
/**
|
||||
* Initialize this configuration form
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
|
@ -22,13 +23,15 @@ class GeneralConfigForm extends ConfigForm
|
|||
}
|
||||
|
||||
/**
|
||||
* @see Form::createElements()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
$appConfigForm = new ApplicationConfigForm();
|
||||
$loggingConfigForm = new LoggingConfigForm();
|
||||
$this->addElements($appConfigForm->createElements($formData)->getElements());
|
||||
$this->addElements($loggingConfigForm->createElements($formData)->getElements());
|
||||
$themingConfigForm = new ThemingConfigForm();
|
||||
$this->addSubForm($appConfigForm->create($formData));
|
||||
$this->addSubForm($loggingConfigForm->create($formData));
|
||||
$this->addSubForm($themingConfigForm->create($formData));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -321,21 +321,6 @@ class UserBackendConfigForm extends ConfigForm
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all form element values
|
||||
*
|
||||
* @param bool $suppressArrayNotation Ignored
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues();
|
||||
$values = array_merge($values, $values['backend_form']);
|
||||
unset($values['backend_form']);
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given values are valid
|
||||
*
|
||||
|
|
|
@ -198,19 +198,4 @@ class UserGroupBackendForm extends ConfigForm
|
|||
$this->populate($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all form element values
|
||||
*
|
||||
* @param bool $suppressArrayNotation Ignored
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues();
|
||||
$values = array_merge($values, $values['backend_form']);
|
||||
unset($values['backend_form']);
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ namespace Icinga\Forms;
|
|||
|
||||
use Exception;
|
||||
use Zend_Form_Decorator_Abstract;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Notification;
|
||||
use Icinga\Application\Config;
|
||||
|
||||
/**
|
||||
* Form base-class providing standard functionality for configuration forms
|
||||
|
@ -21,6 +21,23 @@ class ConfigForm extends Form
|
|||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Values from subforms are directly added to the returned values array instead of being grouped by the subforms'
|
||||
* names.
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues($suppressArrayNotation);
|
||||
foreach (array_keys($this->_subForms) as $name) {
|
||||
// Zend returns values from subforms grouped by their names, but we want them flat
|
||||
$values = array_merge($values, $values[$name]);
|
||||
unset($values[$name]);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration to use when populating the form or when saving the user's input
|
||||
*
|
||||
|
|
|
@ -761,17 +761,6 @@ class NavigationConfigForm extends ConfigForm
|
|||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues();
|
||||
$values = array_merge($values, $values['item_form']);
|
||||
unset($values['item_form']);
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -6,12 +6,14 @@ namespace Icinga\Forms;
|
|||
use Exception;
|
||||
use DateTimeZone;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\User\Preferences;
|
||||
use Icinga\User\Preferences\PreferencesStore;
|
||||
use Icinga\Util\TimezoneDetect;
|
||||
use Icinga\Util\Translator;
|
||||
use Icinga\Web\Cookie;
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Notification;
|
||||
use Icinga\Web\Session;
|
||||
|
@ -103,6 +105,14 @@ class PreferenceForm extends Form
|
|||
|
||||
Session::getSession()->user->setPreferences($this->preferences);
|
||||
|
||||
if (($theme = $this->getElement('theme')) !== null
|
||||
&& ($theme = $theme->getValue()) !== $this->getRequest()->getCookie('theme')
|
||||
) {
|
||||
$this->getResponse()
|
||||
->setCookie(new Cookie('theme', $theme))
|
||||
->setReloadCss(true);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->store && $this->getElement('btn_submit_preferences')->isChecked()) {
|
||||
$this->save();
|
||||
|
@ -142,6 +152,20 @@ class PreferenceForm extends Form
|
|||
*/
|
||||
public function createElements(array $formData)
|
||||
{
|
||||
if (! (bool) Config::app()->get('themes', 'disabled', false)) {
|
||||
$themes = Icinga::app()->getThemes();
|
||||
if (count($themes) > 1) {
|
||||
$this->addElement(
|
||||
'select',
|
||||
'theme',
|
||||
array(
|
||||
'label' => $this->translate('Theme', 'Form element label'),
|
||||
'multiOptions' => $themes
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$languages = array();
|
||||
$languages['autodetect'] = sprintf($this->translate('Browser (%s)', 'preferences.form'), $this->getLocale());
|
||||
foreach (Translator::getAvailableLocaleCodes() as $language) {
|
||||
|
|
|
@ -42,33 +42,7 @@ class RoleForm extends ConfigForm
|
|||
'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/application/navigation' => 'config/application/navigation',
|
||||
'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'
|
||||
*/
|
||||
'config/*' => $this->translate('Allow config access') . ' (config/*)'
|
||||
);
|
||||
|
||||
$helper = new Zend_Form_Element('bogus');
|
||||
|
|
|
@ -18,22 +18,18 @@ if ($this->layout()->autorefreshInterval) {
|
|||
|
||||
?>
|
||||
<div id="header">
|
||||
<div id="logo">
|
||||
<?php if (Auth::getInstance()->isAuthenticated()): ?>
|
||||
<div id="header-logo-container">
|
||||
<?= $this->qlink(
|
||||
'',
|
||||
'dashboard',
|
||||
Auth::getInstance()->isAuthenticated() ? 'dashboard' : '',
|
||||
null,
|
||||
array(
|
||||
'icon' => 'img/logo_icinga-inv.png',
|
||||
'data-base-target' => '_main',
|
||||
'aria-hidden' => 'true',
|
||||
'data-base-target' => '_main',
|
||||
'id' => 'header-logo',
|
||||
'tabindex' => -1
|
||||
)
|
||||
); ?>
|
||||
<?php else: ?>
|
||||
<?= $this->icon('img/logo_icinga-inv.png'); ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (! $this->layout()->isIframe): ?>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<div class="logo">
|
||||
<div class="image">
|
||||
<img aria-hidden="true" src="<?= $this->baseUrl('img/logo_icinga_big.png'); ?>" alt="<?= $this->translate('The Icinga logo'); ?>" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="below-logo">
|
||||
<?= $this->render('inline.phtml') ?>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
<div id="guest-error">
|
||||
<div class="centered-ghost">
|
||||
<div class="centered-content">
|
||||
<div id="icinga-logo" aria-hidden="true"></div>
|
||||
<div id="guest-error-message">
|
||||
<?= $this->render('inline.phtml') ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,2 @@
|
|||
<?= $this->layout()->moduleStart ?>
|
||||
<?= $this->layout()->content ?>
|
||||
<?= $this->layout()->benchmark ?>
|
||||
<?= $this->layout()->moduleEnd ?>
|
||||
|
|
|
@ -41,15 +41,14 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
|
|||
}());
|
||||
</script>
|
||||
<?php endif ?>
|
||||
<link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="screen" type="text/css" />
|
||||
<link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="all" type="text/css" />
|
||||
<!-- Respond.js IE8 support of media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script>
|
||||
<![endif]-->
|
||||
<link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" />
|
||||
|
||||
</head>
|
||||
<body id="body">
|
||||
<body id="body" class="loading">
|
||||
<pre id="responsive-debug"></pre>
|
||||
<div id="layout" class="default-layout<?php if ($showFullscreen): ?> fullscreen-layout<?php endif ?>">
|
||||
<?= $this->render($innerLayoutScript); ?>
|
||||
|
|
|
@ -15,7 +15,7 @@ if ($moduleName !== 'default') {
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
<?= StyleSheet::compileForPdf() ?>
|
||||
<?= StyleSheet::forPdf() ?>
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
<div id="login">
|
||||
<div class="logo">
|
||||
<div class="image">
|
||||
<img aria-hidden="true" class="fade-in one" src="<?= $this->baseUrl('img/logo_icinga_big.png'); ?>" alt="<?= $this->translate('The Icinga logo'); ?>" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="form" data-base-target="layout">
|
||||
<div id="login" class="centered-ghost">
|
||||
<div class="centered-content" data-base-target="layout">
|
||||
<div id="icinga-logo" aria-hidden="true"></div>
|
||||
<?php if ($requiresSetup): ?>
|
||||
<p class="config-note"><?= sprintf(
|
||||
$this->translate(
|
||||
|
@ -12,15 +8,15 @@
|
|||
. 'authentication method. Please define a authentication method by following the instructions in the'
|
||||
. ' %1$sdocumentation%3$s or by using our %2$sweb-based setup-wizard%3$s.'
|
||||
),
|
||||
'<a href="http://docs.icinga.org/" title="' . $this->translate('Icinga Web 2 Documentation') . '">', // TODO: More exact documentation link..
|
||||
'<a href="http://docs.icinga.org/" title="' . $this->translate('Icinga Web 2 Documentation') . '">', // TODO: More exact documentation link
|
||||
'<a href="' . $this->href('setup') . '" title="' . $this->translate('Icinga Web 2 Setup-Wizard') . '">',
|
||||
'</a>'
|
||||
); ?></p>
|
||||
) ?></p>
|
||||
<?php endif ?>
|
||||
<?= $this->form ?>
|
||||
<div class="footer">
|
||||
Icinga Web 2 © 2013-<?= date('Y'); ?><br><br>
|
||||
<?= $this->qlink($this->translate('The Icinga Project'), 'https://www.icinga.org'); ?>
|
||||
<div id="login-footer">
|
||||
<p>Icinga Web 2 © 2013-<?= date('Y') ?></p>
|
||||
<?= $this->qlink($this->translate('The Icinga Project'), 'https://www.icinga.org') ?>
|
||||
<?= $this->qlink(
|
||||
null,
|
||||
'http://www.twitter.com/icinga',
|
||||
|
@ -30,7 +26,7 @@
|
|||
'icon' => 'twitter',
|
||||
'title' => $this->translate('Icinga on Twitter')
|
||||
)
|
||||
); ?>
|
||||
) ?>
|
||||
<?= $this->qlink(
|
||||
null,
|
||||
'http://www.facebook.com/icinga',
|
||||
|
@ -49,7 +45,7 @@
|
|||
'icon' => 'gplus-squared',
|
||||
'title' => $this->translate('Icinga on Google+')
|
||||
)
|
||||
); ?>
|
||||
</div>
|
||||
) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?php if (! $this->compact && !$hideControls): ?>
|
||||
<?php if (! $this->compact && ! $hideControls): ?>
|
||||
<div class="controls">
|
||||
<?= $tabs->showOnlyCloseButton(); ?>
|
||||
<?= $tabs->showOnlyCloseButton() ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="content">
|
||||
<p><strong><?= nl2br($this->escape($message)); ?></strong></p>
|
||||
<p class="error-message"><?= nl2br($this->escape($message)) ?></p>
|
||||
<?php if (isset($stackTrace)): ?>
|
||||
<hr />
|
||||
<pre><?= $this->escape($stackTrace) ?></pre>
|
||||
|
|
|
@ -4,7 +4,7 @@ use Icinga\Data\Extensible;
|
|||
use Icinga\Data\Reducible;
|
||||
|
||||
if (! $this->compact): ?>
|
||||
<div class="controls separated dont-print">
|
||||
<div class="controls separated">
|
||||
<?= $tabs; ?>
|
||||
<div class="grid">
|
||||
<?= $this->sortBox ?>
|
||||
|
|
|
@ -23,7 +23,7 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan
|
|||
}
|
||||
|
||||
?>
|
||||
<div class="controls separated dont-print">
|
||||
<div class="controls separated">
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $tabs; ?>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -4,7 +4,7 @@ use Icinga\Data\Extensible;
|
|||
use Icinga\Data\Reducible;
|
||||
|
||||
if (! $this->compact): ?>
|
||||
<div class="controls separated dont-print">
|
||||
<div class="controls separated">
|
||||
<?= $tabs ?>
|
||||
<div class="grid">
|
||||
<?= $this->sortBox ?>
|
||||
|
|
|
@ -22,7 +22,7 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
|
|||
}
|
||||
|
||||
?>
|
||||
<div class="controls separated dont-print">
|
||||
<div class="controls separated">
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $tabs; ?>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -20,7 +20,7 @@ use Icinga\Web\Response;
|
|||
class EmbeddedWeb extends ApplicationBootstrap
|
||||
{
|
||||
/**
|
||||
* Request object
|
||||
* Request
|
||||
*
|
||||
* @var Request
|
||||
*/
|
||||
|
@ -57,6 +57,7 @@ class EmbeddedWeb extends ApplicationBootstrap
|
|||
* Embedded bootstrap parts
|
||||
*
|
||||
* @see ApplicationBootstrap::bootstrap
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function bootstrap()
|
||||
|
@ -65,6 +66,8 @@ class EmbeddedWeb extends ApplicationBootstrap
|
|||
->setupZendAutoloader()
|
||||
->setupErrorHandling()
|
||||
->loadConfig()
|
||||
->setupLogging()
|
||||
->setupLogger()
|
||||
->setupRequest()
|
||||
->setupResponse()
|
||||
->setupTimezone()
|
||||
|
|
|
@ -13,6 +13,7 @@ use Zend_Paginator;
|
|||
use Zend_View_Helper_PaginationControl;
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\User;
|
||||
use Icinga\Util\DirectoryIterator;
|
||||
use Icinga\Util\TimezoneDetect;
|
||||
use Icinga\Util\Translator;
|
||||
use Icinga\Web\Controller\Dispatcher;
|
||||
|
@ -20,6 +21,7 @@ use Icinga\Web\Navigation\Navigation;
|
|||
use Icinga\Web\Notification;
|
||||
use Icinga\Web\Session;
|
||||
use Icinga\Web\Session\Session as BaseSession;
|
||||
use Icinga\Web\StyleSheet;
|
||||
use Icinga\Web\View;
|
||||
|
||||
/**
|
||||
|
@ -98,6 +100,33 @@ class Web extends EmbeddedWeb
|
|||
->setupPagination();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get themes provided by Web 2 and all enabled modules
|
||||
*
|
||||
* @return string[] Array of theme names as keys and values
|
||||
*/
|
||||
public function getThemes()
|
||||
{
|
||||
$themes = array(StyleSheet::DEFAULT_THEME);
|
||||
$applicationThemePath = $this->getBaseDir('public/css/themes');
|
||||
if (DirectoryIterator::isReadable($applicationThemePath)) {
|
||||
foreach (new DirectoryIterator($applicationThemePath, 'less') as $name => $theme) {
|
||||
$themes[] = substr($name, 0, -5);
|
||||
}
|
||||
}
|
||||
$mm = $this->getModuleManager();
|
||||
foreach ($mm->listEnabledModules() as $moduleName) {
|
||||
$moduleThemePath = $mm->getModule($moduleName)->getCssDir() . '/themes';
|
||||
if (! DirectoryIterator::isReadable($moduleThemePath)) {
|
||||
continue;
|
||||
}
|
||||
foreach (new DirectoryIterator($moduleThemePath, 'less') as $name => $theme) {
|
||||
$themes[] = $moduleName . '/' . substr($name, 0, -5);
|
||||
}
|
||||
}
|
||||
return array_combine($themes, $themes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare routing
|
||||
*
|
||||
|
|
|
@ -61,7 +61,7 @@ if (in_array($path, $special)) {
|
|||
Stylesheet::send();
|
||||
exit;
|
||||
case 'css/icinga.min.css':
|
||||
Stylesheet::sendMinified();
|
||||
Stylesheet::send(true);
|
||||
exit;
|
||||
|
||||
case 'js/icinga.dev.js':
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\File;
|
||||
|
||||
use FilterIterator;
|
||||
use Iterator;
|
||||
|
||||
/**
|
||||
* Iterator over files having a specific file extension
|
||||
*
|
||||
* Usage example:
|
||||
* <code>
|
||||
* <?php
|
||||
*
|
||||
* namespace Icinga\Example;
|
||||
*
|
||||
* use RecursiveDirectoryIterator;
|
||||
* use RecursiveIteratorIterator;
|
||||
* use Icinga\File\FileExtensionFilterIterator;
|
||||
*
|
||||
* $markdownFiles = new FileExtensionFilterIterator(
|
||||
* new RecursiveIteratorIterator(
|
||||
* new RecursiveDirectoryIterator(__DIR__),
|
||||
* RecursiveIteratorIterator::SELF_FIRST
|
||||
* ),
|
||||
* 'md'
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
class FileExtensionFilterIterator extends FilterIterator
|
||||
{
|
||||
/**
|
||||
* The extension to filter for
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* Create a new FileExtensionFilterIterator
|
||||
*
|
||||
* @param Iterator $iterator Apply filter to this iterator
|
||||
* @param string $extension The file extension to filter for. The file extension may not contain the leading dot
|
||||
*/
|
||||
public function __construct(Iterator $iterator, $extension)
|
||||
{
|
||||
$this->extension = '.' . ltrim(strtolower((string) $extension), '.');
|
||||
parent::__construct($iterator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept files which match the file extension to filter for
|
||||
*
|
||||
* @return bool Whether the current element of the iterator is acceptable
|
||||
* through this filter
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
$current = $this->current();
|
||||
/** @var $current \SplFileInfo */
|
||||
if (! $current->isFile()) {
|
||||
return false;
|
||||
}
|
||||
// SplFileInfo::getExtension() is only available since PHP 5 >= 5.3.6
|
||||
$filename = $current->getFilename();
|
||||
$sfx = substr($filename, -strlen($this->extension));
|
||||
return $sfx === false ? false : strtolower($sfx) === $this->extension;
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\File;
|
||||
|
||||
use FilterIterator;
|
||||
|
||||
/**
|
||||
* Iterator over non-empty files
|
||||
*
|
||||
* Usage example:
|
||||
* <code>
|
||||
* <?php
|
||||
*
|
||||
* namespace Icinga\Example;
|
||||
*
|
||||
* use RecursiveDirectoryIterator;
|
||||
* use RecursiveIteratorIterator;
|
||||
* use Icinga\File\NonEmptyFilterIterator;
|
||||
*
|
||||
* $nonEmptyFiles = new NonEmptyFileIterator(
|
||||
* new RecursiveIteratorIterator(
|
||||
* new RecursiveDirectoryIterator(__DIR__),
|
||||
* RecursiveIteratorIterator::SELF_FIRST
|
||||
* )
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
class NonEmptyFileIterator extends FilterIterator
|
||||
{
|
||||
/**
|
||||
* Accept non-empty files
|
||||
*
|
||||
* @return bool Whether the current element of the iterator is acceptable
|
||||
* through this filter
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
$current = $this->current();
|
||||
/** @var $current \SplFileInfo */
|
||||
if (! $current->isFile()
|
||||
|| $current->getSize() === 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Util;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
|
||||
/**
|
||||
* Iterator for traversing a directory
|
||||
*/
|
||||
class DirectoryIterator implements Iterator
|
||||
{
|
||||
/**
|
||||
* Current directory item
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
private $current;
|
||||
|
||||
/**
|
||||
* The file extension to filter for
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* Directory handle
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $handle;
|
||||
|
||||
/**
|
||||
* Current key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* The path of the directory to traverse
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Whether to skip empty files
|
||||
*
|
||||
* Defaults to true.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $skipEmpty = true;
|
||||
|
||||
/**
|
||||
* Whether to skip hidden files
|
||||
*
|
||||
* Defaults to true.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $skipHidden = true;
|
||||
|
||||
/**
|
||||
* Create a new directory iterator from path
|
||||
*
|
||||
* The given path will not be validated whether it is readable. Use {@link isReadable()} before creating a new
|
||||
* directory iterator instance.
|
||||
*
|
||||
* @param string $path The path of the directory to traverse
|
||||
* @param string $extension The file extension to filter for. A leading dot is optional
|
||||
*/
|
||||
public function __construct($path, $extension = null)
|
||||
{
|
||||
if (empty($path)) {
|
||||
throw new InvalidArgumentException('The path can\'t be empty');
|
||||
}
|
||||
$this->path = $path;
|
||||
if (! empty($extension)) {
|
||||
$this->extension = '.' . ltrim($extension, '.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given path is a directory and is readable
|
||||
*
|
||||
* @param string $path The path of the directory
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isReadable($path)
|
||||
{
|
||||
return is_dir($path) && is_readable($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
do {
|
||||
$file = readdir($this->handle);
|
||||
if ($file === false) {
|
||||
$key = false;
|
||||
break;
|
||||
} else {
|
||||
$skip = false;
|
||||
do {
|
||||
if ($this->skipHidden && $file[0] === '.') {
|
||||
$skip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$path = $this->path . '/' . $file;
|
||||
|
||||
if (is_dir($path)) {
|
||||
$skip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->skipEmpty && ! filesize($path)) {
|
||||
$skip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->extension && ! String::endsWith($file, $this->extension)) {
|
||||
$skip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$key = $file;
|
||||
$file = $path;
|
||||
} while (0);
|
||||
}
|
||||
} while ($skip);
|
||||
|
||||
$this->current = $file;
|
||||
/** @noinspection PhpUndefinedVariableInspection */
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->current !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
if ($this->handle === null) {
|
||||
$this->handle = opendir($this->path);
|
||||
} else {
|
||||
rewinddir($this->handle);
|
||||
}
|
||||
$this->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close directory handle if created
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->handle !== null) {
|
||||
closedir($this->handle);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -101,17 +101,17 @@ class String
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if a string ends with a different string
|
||||
* Test whether the given string ends with the given suffix
|
||||
*
|
||||
* @param $haystack string The string to search for matches
|
||||
* @param $needle string The string to match at the start of the haystack
|
||||
* @param string $string The string to test
|
||||
* @param string $suffix The suffix the string must end with
|
||||
*
|
||||
* @return bool Whether or not needle is at the beginning of haystack
|
||||
* @return bool
|
||||
*/
|
||||
public static function endsWith($haystack, $needle)
|
||||
public static function endsWith($string, $suffix)
|
||||
{
|
||||
return $needle === '' ||
|
||||
(($temp = strlen($haystack) - strlen($needle)) >= 0 && false !== strpos($haystack, $needle, $temp));
|
||||
$stringSuffix = substr($string, -strlen($suffix));
|
||||
return $stringSuffix !== false ? $stringSuffix === $suffix : false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,77 +3,260 @@
|
|||
|
||||
namespace Icinga\Web;
|
||||
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Icinga;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Helper Class Cookie
|
||||
* A HTTP cookie
|
||||
*/
|
||||
class Cookie
|
||||
{
|
||||
/**
|
||||
* The name of the control cookie
|
||||
* Domain of the cookie
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CHECK_COOKIE = '_chc';
|
||||
protected $domain;
|
||||
|
||||
/**
|
||||
* The request
|
||||
* The timestamp at which the cookie expires
|
||||
*
|
||||
* @var Request
|
||||
* @var int
|
||||
*/
|
||||
protected $request;
|
||||
protected $expire;
|
||||
|
||||
/**
|
||||
* Whether to protect the cookie against client side script code attempts to read the cookie
|
||||
*
|
||||
* Defaults to true.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $httpOnly = true;
|
||||
|
||||
/**
|
||||
* Name of the cookie
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The path on the web server where the cookie is available
|
||||
*
|
||||
* Defaults to the base URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Whether to send the cookie only over a secure connection
|
||||
*
|
||||
* Defaults to auto-detection so that if the current request was sent over a secure connection the secure flag will
|
||||
* be set to true.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $secure;
|
||||
|
||||
/**
|
||||
* Value of the cookie
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Create a new cookie
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
public function __construct($name, $value = null)
|
||||
{
|
||||
$this->request = $request;
|
||||
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Cookie name can\'t contain these characters: =,; \t\r\n\013\014 (%s)',
|
||||
$name
|
||||
));
|
||||
}
|
||||
if (empty($name)) {
|
||||
throw new InvalidArgumentException('The cookie name can\'t be empty');
|
||||
}
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether cookies are supported or not
|
||||
* Get the domain of the cookie
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the domain of the cookie
|
||||
*
|
||||
* @param string $domain
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDomain($domain)
|
||||
{
|
||||
$this->domain = $domain;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp at which the cookie expires
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getExpire()
|
||||
{
|
||||
return $this->expire;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timestamp at which the cookie expires
|
||||
*
|
||||
* @param int $expire
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setExpire($expire)
|
||||
{
|
||||
$this->expire = $expire;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to protect the cookie against client side script code attempts to read the cookie
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSupported()
|
||||
public function isHttpOnly()
|
||||
{
|
||||
if (! empty($_COOKIE)) {
|
||||
$this->cleanupCheck();
|
||||
return true;
|
||||
}
|
||||
|
||||
$url = $this->request->getUrl();
|
||||
|
||||
if ($url->hasParam('_checkCookie') && empty($_COOKIE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $url->hasParam('_checkCookie')) {
|
||||
$this->provideCheck();
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare check to detect cookie support
|
||||
* Set whether to protect the cookie against client side script code attempts to read the cookie
|
||||
*
|
||||
* @param bool $httpOnly
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function provideCheck()
|
||||
public function setHttpOnly($httpOnly)
|
||||
{
|
||||
setcookie(self::CHECK_COOKIE, '1');
|
||||
|
||||
$requestUri = $this->request->getUrl()->addParams(array('_checkCookie' => 1));
|
||||
$this->request->getResponse()->redirectAndExit($requestUri);
|
||||
$this->httpOnly = $httpOnly;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the cookie support check
|
||||
* Get the name of the cookie
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function cleanupCheck()
|
||||
public function getName()
|
||||
{
|
||||
if ($this->request->getUrl()->hasParam('_checkCookie') && isset($_COOKIE[self::CHECK_COOKIE])) {
|
||||
$requestUri =$this->request->getUrl()->without('_checkCookie');
|
||||
$this->request->getResponse()->redirectAndExit($requestUri);
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path on the web server where the cookie is available
|
||||
*
|
||||
* If the path has not been set either via {@link setPath()} or via config, the base URL will be returned.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
if ($this->path === null) {
|
||||
$path = Config::app()->get('cookie', 'path');
|
||||
if ($path === null) {
|
||||
// The following call could be used as default for ConfigObject::get(), but we prevent unnecessary
|
||||
// function calls here, if the path is set in the config
|
||||
$path = Icinga::app()->getRequest()->getBaseUrl();
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path on the web server where the cookie is available
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPath($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to send the cookie only over a secure connection
|
||||
*
|
||||
* If the secure flag has not been set either via {@link setSecure()} or via config and if the current request was
|
||||
* sent over a secure connection, true will be returned.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSecure()
|
||||
{
|
||||
if ($this->secure === null) {
|
||||
$secure = Config::app()->get('cookie', 'secure');
|
||||
if ($secure === null) {
|
||||
// The following call could be used as default for ConfigObject::get(), but we prevent unnecessary
|
||||
// function calls here, if the secure flag is set in the config
|
||||
$secure = Icinga::app()->getRequest()->isSecure();
|
||||
}
|
||||
return $secure;
|
||||
}
|
||||
return $this->secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to send the cookie only over a secure connection
|
||||
*
|
||||
* @param bool $secure
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSecure($secure)
|
||||
{
|
||||
$this->secure = $secure;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the cookie
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the cookie
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
|
||||
/**
|
||||
* Maintain a set of cookies
|
||||
*/
|
||||
class CookieSet implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Cookies in this set indexed by the cookie names
|
||||
*
|
||||
* @var Cookie[]
|
||||
*/
|
||||
protected $cookies = array();
|
||||
|
||||
/**
|
||||
* Get an iterator for traversing the cookies in this set
|
||||
*
|
||||
* @return ArrayIterator An iterator for traversing the cookies in this set
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a cookie to the set
|
||||
*
|
||||
* If a cookie with the same name already exists, the cookie will be overridden.
|
||||
*
|
||||
* @param Cookie $cookie The cookie to add
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function add(Cookie $cookie)
|
||||
{
|
||||
$this->cookies[$cookie->getName()] = $cookie;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie with the given name from the set
|
||||
*
|
||||
* @param string $name The name of the cookie
|
||||
*
|
||||
* @return Cookie|null The cookie with the given name or null if the cookie does not exist
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
return isset($this->cookies[$name]) ? $this->cookies[$name] : null;
|
||||
}
|
||||
}
|
|
@ -190,9 +190,6 @@ class FileCache
|
|||
/**
|
||||
* Whether the given ETag matchesspecific file(s) on disk
|
||||
*
|
||||
* If no ETag is given we'll try to fetch the one from the current
|
||||
* HTTP request. Respects HTTP Cache-Control: no-cache, if set.
|
||||
*
|
||||
* @param string|array $files file(s) to check
|
||||
* @param string $match ETag to match against
|
||||
*
|
||||
|
@ -208,9 +205,6 @@ class FileCache
|
|||
if (! $match) {
|
||||
return false;
|
||||
}
|
||||
if (isset($_SERVER['HTTP_CACHE_CONTROL']) && $_SERVER['HTTP_CACHE_CONTROL'] === 'no-cache') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$etag = self::etagForFiles($files);
|
||||
return $match === $etag ? $etag : false;
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Helper;
|
||||
|
||||
use Icinga\Web\Request;
|
||||
|
||||
/**
|
||||
* Helper Class Cookie
|
||||
*/
|
||||
class CookieHelper
|
||||
{
|
||||
/**
|
||||
* The name of the control cookie
|
||||
*/
|
||||
const CHECK_COOKIE = '_chc';
|
||||
|
||||
/**
|
||||
* The request
|
||||
*
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Create a new cookie
|
||||
*
|
||||
* @param Request $request
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether cookies are supported or not
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSupported()
|
||||
{
|
||||
if (! empty($_COOKIE)) {
|
||||
$this->cleanupCheck();
|
||||
return true;
|
||||
}
|
||||
|
||||
$url = $this->request->getUrl();
|
||||
|
||||
if ($url->hasParam('_checkCookie') && empty($_COOKIE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $url->hasParam('_checkCookie')) {
|
||||
$this->provideCheck();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare check to detect cookie support
|
||||
*/
|
||||
public function provideCheck()
|
||||
{
|
||||
setcookie(self::CHECK_COOKIE, '1');
|
||||
|
||||
$requestUri = $this->request->getUrl()->addParams(array('_checkCookie' => 1));
|
||||
$this->request->getResponse()->redirectAndExit($requestUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the cookie support check
|
||||
*/
|
||||
public function cleanupCheck()
|
||||
{
|
||||
if ($this->request->getUrl()->hasParam('_checkCookie') && isset($_COOKIE[self::CHECK_COOKIE])) {
|
||||
$requestUri =$this->request->getUrl()->without('_checkCookie');
|
||||
$this->request->getResponse()->redirectAndExit($requestUri);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,15 +47,22 @@ class JavaScript
|
|||
|
||||
public static function sendMinified()
|
||||
{
|
||||
return self::send(true);
|
||||
self::send(true);
|
||||
}
|
||||
|
||||
public static function sendForIe8()
|
||||
{
|
||||
self::$vendorFiles = self::$ie8VendorFiles;
|
||||
return self::send();
|
||||
self::send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the client side script code to the client
|
||||
*
|
||||
* Does not cache the client side script code if the HTTP header Cache-Control or Pragma is set to no-cache.
|
||||
*
|
||||
* @param bool $minified Whether to compress the client side script code
|
||||
*/
|
||||
public static function send($minified = false)
|
||||
{
|
||||
header('Content-Type: application/javascript');
|
||||
|
@ -87,7 +94,10 @@ class JavaScript
|
|||
}
|
||||
$files = array_merge($vendorFiles, $jsFiles);
|
||||
|
||||
if ($etag = FileCache::etagMatchesFiles($files)) {
|
||||
$request = Icinga::app()->getRequest();
|
||||
$noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache';
|
||||
|
||||
if (! $noCache && FileCache::etagMatchesFiles($files)) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
return;
|
||||
} else {
|
||||
|
@ -99,7 +109,7 @@ class JavaScript
|
|||
|
||||
$cacheFile = 'icinga-' . $etag . $min . '.js';
|
||||
$cache = FileCache::instance();
|
||||
if ($cache->has($cacheFile)) {
|
||||
if (! $noCache && $cache->has($cacheFile)) {
|
||||
$cache->send($cacheFile);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3,167 +3,170 @@
|
|||
|
||||
namespace Icinga\Web;
|
||||
|
||||
use Exception;
|
||||
use RecursiveDirectoryIterator;
|
||||
use Icinga\Application\Logger;
|
||||
use RecursiveArrayIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
use RecursiveRegexIterator;
|
||||
use Icinga\Application\Icinga;
|
||||
use lessc;
|
||||
|
||||
/**
|
||||
* Less compiler prints files or directories to stdout
|
||||
* Compile LESS into CSS
|
||||
*
|
||||
* Comments will be removed always. lessc is messing them up.
|
||||
*/
|
||||
class LessCompiler
|
||||
{
|
||||
/**
|
||||
* Collection of items: File or directories
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $items = array();
|
||||
|
||||
/**
|
||||
* lessphp compiler
|
||||
*
|
||||
* @var \lessc
|
||||
* @var lessc
|
||||
*/
|
||||
private $lessc;
|
||||
|
||||
private $source;
|
||||
protected $lessc;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
* Array of LESS files
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $lessFiles = array();
|
||||
|
||||
/**
|
||||
* Array of module LESS files indexed by module names
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
protected $moduleLessFiles = array();
|
||||
|
||||
/**
|
||||
* LESS source
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* Path of the LESS theme
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $theme;
|
||||
|
||||
/**
|
||||
* Create a new LESS compiler
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
require_once 'lessphp/lessc.inc.php';
|
||||
$this->lessc = new lessc();
|
||||
// Discourage usage of import because we're caching based on an explicit list of LESS files to compile
|
||||
$this->lessc->importDisabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the extendend import functionality
|
||||
* Add a Web 2 LESS file
|
||||
*
|
||||
* @param string $lessFile Path to the LESS file
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disableExtendedImport()
|
||||
public function addLessFile($lessFile)
|
||||
{
|
||||
$this->lessc->importDisabled = true;
|
||||
$this->lessFiles[] = $lessFile;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a module LESS file
|
||||
*
|
||||
* @param string $moduleName Name of the module
|
||||
* @param string $lessFile Path to the LESS file
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addModuleLessFile($moduleName, $lessFile)
|
||||
{
|
||||
if (! isset($this->moduleLessFiles[$moduleName])) {
|
||||
$this->moduleLessFiles[$moduleName] = array();
|
||||
}
|
||||
$this->moduleLessFiles[$moduleName][] = $lessFile;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of LESS files added to the compiler
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLessFiles()
|
||||
{
|
||||
$lessFiles = iterator_to_array(new RecursiveIteratorIterator(new RecursiveArrayIterator(
|
||||
$this->lessFiles + $this->moduleLessFiles
|
||||
)));
|
||||
if ($this->theme !== null) {
|
||||
$lessFiles[] = $this->theme;
|
||||
}
|
||||
return $lessFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path to the LESS theme
|
||||
*
|
||||
* @param string $theme Path to the LESS theme
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTheme($theme)
|
||||
{
|
||||
if (is_file($theme) && is_readable($theme)) {
|
||||
$this->theme = $theme;
|
||||
} else {
|
||||
Logger::error('Can\t load theme %s. Make sure that the theme exists and is readable', $theme);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instruct the compiler to minify CSS
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function compress()
|
||||
{
|
||||
$this->lessc->setPreserveComments(false);
|
||||
$this->lessc->setFormatter('compressed');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add usable style item to stack
|
||||
* Render to CSS
|
||||
*
|
||||
* @param string $item File or directory
|
||||
* @return string
|
||||
*/
|
||||
public function addItem($item)
|
||||
public function render()
|
||||
{
|
||||
$this->items[] = $item;
|
||||
foreach ($this->lessFiles as $lessFile) {
|
||||
$this->source .= file_get_contents($lessFile);
|
||||
}
|
||||
|
||||
public function addLoadedModules()
|
||||
{
|
||||
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) {
|
||||
$this->addModule($name, $module);
|
||||
$moduleCss = '';
|
||||
foreach ($this->moduleLessFiles as $moduleName => $moduleLessFiles) {
|
||||
$moduleCss .= '.icinga-module.module-' . $moduleName . ' {';
|
||||
foreach ($moduleLessFiles as $moduleLessFile) {
|
||||
$moduleCss .= file_get_contents($moduleLessFile);
|
||||
}
|
||||
return $this;
|
||||
$moduleCss .= '}';
|
||||
}
|
||||
|
||||
public function addFile($filename)
|
||||
{
|
||||
$this->source .= "\n/* CSS: $filename */\n"
|
||||
. file_get_contents($filename)
|
||||
. "\n\n";
|
||||
return $this;
|
||||
$moduleCss = preg_replace(
|
||||
'/(\.icinga-module\.module-[^\s]+) (#layout\.[^\s]+)/m',
|
||||
'\2 \1',
|
||||
$moduleCss
|
||||
);
|
||||
|
||||
$this->source .= $moduleCss;
|
||||
|
||||
if ($this->theme !== null) {
|
||||
$this->source .= file_get_contents($this->theme);
|
||||
}
|
||||
|
||||
public function compile()
|
||||
{
|
||||
return $this->lessc->compile($this->source);
|
||||
}
|
||||
|
||||
public function addModule($name, $module)
|
||||
{
|
||||
if ($module->hasCss()) {
|
||||
$contents = array();
|
||||
foreach ($module->getCssFiles() as $path) {
|
||||
if (file_exists($path)) {
|
||||
$contents[] = "/* CSS: modules/$name/$path */\n" . file_get_contents($path);
|
||||
}
|
||||
}
|
||||
|
||||
$this->source .= ''
|
||||
. '.icinga-module.module-'
|
||||
. $name
|
||||
. " {\n"
|
||||
. join("\n\n", $contents)
|
||||
. "}\n\n";
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and print a single file
|
||||
*
|
||||
* @param string $file
|
||||
*/
|
||||
public function printFile($file)
|
||||
{
|
||||
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
echo PHP_EOL. '/* CSS: ' . $file . ' */' . PHP_EOL;
|
||||
|
||||
if ($ext === 'css') {
|
||||
readfile($file);
|
||||
} elseif ($ext === 'less') {
|
||||
try {
|
||||
echo $this->lessc->compileFile($file);
|
||||
} catch (Exception $e) {
|
||||
echo '/* ' . PHP_EOL . ' ===' . PHP_EOL;
|
||||
echo ' Error in file ' . $file . PHP_EOL;
|
||||
echo ' ' . $e->getMessage() . PHP_EOL . PHP_EOL;
|
||||
echo ' ' . 'This file was dropped cause of errors.' . PHP_EOL;
|
||||
echo ' ===' . PHP_EOL . '*/' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and print a path content (recursive)
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function printPathRecursive($path)
|
||||
{
|
||||
$directoryInterator = new RecursiveDirectoryIterator($path);
|
||||
$iterator = new RecursiveIteratorIterator($directoryInterator);
|
||||
$filteredIterator = new RegexIterator($iterator, '/\.(css|less)$/', RecursiveRegexIterator::GET_MATCH);
|
||||
foreach ($filteredIterator as $file => $extension) {
|
||||
$this->printFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and print the whole item stack
|
||||
*/
|
||||
public function printStack()
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
if (is_dir($item)) {
|
||||
$this->printPathRecursive($item);
|
||||
} elseif (is_file($item)) {
|
||||
$this->printFile($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,15 +118,4 @@ 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);
|
||||
return $cookie->isSupported();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,18 @@ use Zend_Controller_Response_Http;
|
|||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\Response\JsonResponse;
|
||||
|
||||
/**
|
||||
* A HTTP response
|
||||
*/
|
||||
class Response extends Zend_Controller_Response_Http
|
||||
{
|
||||
/**
|
||||
* Set of cookies which are to be sent to the client
|
||||
*
|
||||
* @var CookieSet
|
||||
*/
|
||||
protected $cookies;
|
||||
|
||||
/**
|
||||
* Redirect URL
|
||||
*
|
||||
|
@ -23,6 +33,13 @@ class Response extends Zend_Controller_Response_Http
|
|||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Whether to instruct client side script code to reload CSS
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $reloadCss;
|
||||
|
||||
/**
|
||||
* Whether to send the rerender layout header on XHR
|
||||
*
|
||||
|
@ -30,6 +47,44 @@ class Response extends Zend_Controller_Response_Http
|
|||
*/
|
||||
protected $rerenderLayout = false;
|
||||
|
||||
/**
|
||||
* Get the set of cookies which are to be sent to the client
|
||||
*
|
||||
* @return CookieSet
|
||||
*/
|
||||
public function getCookies()
|
||||
{
|
||||
if ($this->cookies === null) {
|
||||
$this->cookies = new CookieSet();
|
||||
}
|
||||
return $this->cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie with the given name from the set of cookies which are to be sent to the client
|
||||
*
|
||||
* @param string $name The name of the cookie
|
||||
*
|
||||
* @return Cookie|null The cookie with the given name or null if the cookie does not exist
|
||||
*/
|
||||
public function getCookie($name)
|
||||
{
|
||||
return $this->getCookies()->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given cookie for sending it to the client
|
||||
*
|
||||
* @param Cookie $cookie The cookie to send to the client
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCookie(Cookie $cookie)
|
||||
{
|
||||
$this->getCookies()->add($cookie);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redirect URL
|
||||
*
|
||||
|
@ -74,6 +129,29 @@ class Response extends Zend_Controller_Response_Http
|
|||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to instruct client side script code to reload CSS
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReloadCss()
|
||||
{
|
||||
return $this->reloadCss;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to instruct client side script code to reload CSS
|
||||
*
|
||||
* @param bool $reloadCss
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setReloadCss($reloadCss)
|
||||
{
|
||||
$this->reloadCss = $reloadCss;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to send the rerender layout header on XHR
|
||||
*
|
||||
|
@ -123,6 +201,9 @@ class Response extends Zend_Controller_Response_Http
|
|||
if ($this->getRerenderLayout()) {
|
||||
$this->setHeader('X-Icinga-Container', 'layout', true);
|
||||
}
|
||||
if ($this->isReloadCss()) {
|
||||
$this->setHeader('X-Icinga-Reload-Css', 'now', true);
|
||||
}
|
||||
} else {
|
||||
if ($redirectUrl !== null) {
|
||||
$this->setRedirect($redirectUrl->getAbsoluteUrl());
|
||||
|
@ -148,12 +229,34 @@ class Response extends Zend_Controller_Response_Http
|
|||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the cookies to the client
|
||||
*/
|
||||
public function sendCookies()
|
||||
{
|
||||
foreach ($this->getCookies() as $cookie) {
|
||||
/** @var Cookie $cookie */
|
||||
setcookie(
|
||||
$cookie->getName(),
|
||||
$cookie->getValue(),
|
||||
$cookie->getExpire(),
|
||||
$cookie->getPath(),
|
||||
$cookie->getDomain(),
|
||||
$cookie->isSecure(),
|
||||
$cookie->isHttpOnly()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sendHeaders()
|
||||
{
|
||||
$this->prepare();
|
||||
if (! $this->getRequest()->isApiRequest()) {
|
||||
$this->sendCookies();
|
||||
}
|
||||
return parent::sendHeaders();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,28 @@
|
|||
|
||||
namespace Icinga\Web;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\FileCache;
|
||||
use Icinga\Web\LessCompiler;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Exception\IcingaException;
|
||||
|
||||
/**
|
||||
* Send CSS for Web 2 and all loaded modules to the client
|
||||
*/
|
||||
class StyleSheet
|
||||
{
|
||||
/**
|
||||
* The name of the default theme
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEFAULT_THEME = 'Icinga';
|
||||
|
||||
/**
|
||||
* Array of core LESS files Web 2 sends to the client
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $lessFiles = array(
|
||||
'../application/fonts/fontello-ifont/css/ifont-embedded.css',
|
||||
'css/vendor/normalize.css',
|
||||
|
@ -21,11 +37,9 @@ class StyleSheet
|
|||
'css/icinga/nav.less',
|
||||
'css/icinga/main.less',
|
||||
'css/icinga/animation.less',
|
||||
'css/icinga/layout-colors.less',
|
||||
'css/icinga/layout.less',
|
||||
'css/icinga/layout-structure.less',
|
||||
'css/icinga/menu.less',
|
||||
'css/icinga/header-elements.less',
|
||||
'css/icinga/footer-elements.less',
|
||||
// 'css/icinga/main-content.less',
|
||||
'css/icinga/tabs.less',
|
||||
'css/icinga/forms.less',
|
||||
|
@ -37,92 +51,180 @@ class StyleSheet
|
|||
'css/icinga/dev.less',
|
||||
// 'css/icinga/logo.less',
|
||||
'css/icinga/spinner.less',
|
||||
'css/icinga/compat.less'
|
||||
'css/icinga/compat.less',
|
||||
'css/icinga/print.less'
|
||||
);
|
||||
|
||||
public static function compileForPdf()
|
||||
{
|
||||
self::checkPhp();
|
||||
$less = new LessCompiler();
|
||||
$basedir = Icinga::app()->getBootstrapDirectory();
|
||||
foreach (self::$lessFiles as $file) {
|
||||
$less->addFile($basedir . '/' . $file);
|
||||
}
|
||||
$less->addLoadedModules();
|
||||
$less->addFile($basedir . '/css/pdf/pdfprint.less');
|
||||
return $less->compile();
|
||||
}
|
||||
/**
|
||||
* Application instance
|
||||
*
|
||||
* @var \Icinga\Application\EmbeddedWeb
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
public static function sendMinified()
|
||||
{
|
||||
self::send(true);
|
||||
}
|
||||
/**
|
||||
* Less compiler
|
||||
*
|
||||
* @var LessCompiler
|
||||
*/
|
||||
protected $lessCompiler;
|
||||
|
||||
protected static function fixModuleLayoutCss($css)
|
||||
{
|
||||
return preg_replace(
|
||||
'/(\.icinga-module\.module-[^\s]+) (#layout\.[^\s]+)/m',
|
||||
'\2 \1',
|
||||
$css
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Path to the public directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pubPath;
|
||||
|
||||
protected static function checkPhp()
|
||||
/**
|
||||
* Create the StyleSheet
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// PHP had a rather conservative PCRE backtrack limit unless 5.3.7
|
||||
if (version_compare(PHP_VERSION, '5.3.7') <= 0) {
|
||||
ini_set('pcre.backtrack_limit', 1000000);
|
||||
}
|
||||
$app = Icinga::app();
|
||||
$this->app = $app;
|
||||
$this->lessCompiler = new LessCompiler();
|
||||
$this->pubPath = $app->getBootstrapDirectory();
|
||||
$this->collect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect Web 2 and module LESS files and add them to the LESS compiler
|
||||
*/
|
||||
protected function collect()
|
||||
{
|
||||
foreach (self::$lessFiles as $lessFile) {
|
||||
$this->lessCompiler->addLessFile($this->pubPath . '/' . $lessFile);
|
||||
}
|
||||
|
||||
$mm = $this->app->getModuleManager();
|
||||
|
||||
foreach ($mm->getLoadedModules() as $moduleName => $module) {
|
||||
if ($module->hasCss()) {
|
||||
foreach ($module->getCssFiles() as $lessFilePath) {
|
||||
$this->lessCompiler->addModuleLessFile($moduleName, $lessFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$themingConfig = $this->app->getConfig()->getSection('themes');
|
||||
$defaultTheme = $themingConfig->get('default', self::DEFAULT_THEME);
|
||||
$theme = null;
|
||||
|
||||
if ((bool) $themingConfig->get('disabled', false)) {
|
||||
if ($defaultTheme !== self::DEFAULT_THEME) {
|
||||
$theme = $defaultTheme;
|
||||
}
|
||||
} else {
|
||||
if (($userTheme = $this->app->getRequest()->getCookie('theme', $defaultTheme))
|
||||
&& $userTheme !== $defaultTheme
|
||||
) {
|
||||
$theme = $userTheme;
|
||||
}
|
||||
}
|
||||
|
||||
if ($theme) {
|
||||
if (($pos = strpos($theme, '/')) !== false) {
|
||||
$moduleName = substr($theme, 0, $pos);
|
||||
$theme = substr($theme, $pos + 1);
|
||||
if ($mm->hasLoaded($moduleName)) {
|
||||
$module = $mm->getModule($moduleName);
|
||||
$this->lessCompiler->setTheme($module->getCssDir() . '/themes/' . $theme . '.less');
|
||||
}
|
||||
} else {
|
||||
$this->lessCompiler->setTheme($this->pubPath . '/css/themes/' . $theme . '.less');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stylesheet for PDF export
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public static function forPdf()
|
||||
{
|
||||
$styleSheet = new self();
|
||||
$styleSheet->lessCompiler->addLessFile($styleSheet->pubPath . '/css/pdf/pdfprint.less');
|
||||
// TODO(el): Caching
|
||||
return $styleSheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the stylesheet
|
||||
*
|
||||
* @param bool $minified Whether to compress the stylesheet
|
||||
*
|
||||
* @return string CSS
|
||||
*/
|
||||
public function render($minified = false)
|
||||
{
|
||||
if ($minified) {
|
||||
$this->lessCompiler->compress();
|
||||
}
|
||||
return $this->lessCompiler->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the stylesheet to the client
|
||||
*
|
||||
* Does not cache the stylesheet if the HTTP header Cache-Control or Pragma is set to no-cache.
|
||||
*
|
||||
* @param bool $minified Whether to compress the stylesheet
|
||||
*/
|
||||
public static function send($minified = false)
|
||||
{
|
||||
self::checkPhp();
|
||||
$app = Icinga::app();
|
||||
$basedir = $app->getBootstrapDirectory();
|
||||
foreach (self::$lessFiles as $file) {
|
||||
$lessFiles[] = $basedir . '/' . $file;
|
||||
}
|
||||
$files = $lessFiles;
|
||||
foreach ($app->getModuleManager()->getLoadedModules() as $name => $module) {
|
||||
if ($module->hasCss()) {
|
||||
foreach ($module->getCssFiles() as $path) {
|
||||
if (file_exists($path)) {
|
||||
$files[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$styleSheet = new self();
|
||||
|
||||
if ($etag = FileCache::etagMatchesFiles($files)) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
$request = $styleSheet->app->getRequest();
|
||||
$response = $styleSheet->app->getResponse();
|
||||
|
||||
$noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache';
|
||||
|
||||
if (! $noCache && FileCache::etagMatchesFiles($styleSheet->lessCompiler->getLessFiles())) {
|
||||
$response
|
||||
->setHttpResponseCode(304)
|
||||
->sendHeaders();
|
||||
return;
|
||||
} else {
|
||||
$etag = FileCache::etagForFiles($files);
|
||||
}
|
||||
header('Cache-Control: public');
|
||||
header('ETag: "' . $etag . '"');
|
||||
header('Content-Type: text/css');
|
||||
|
||||
$min = $minified ? '.min' : '';
|
||||
$cacheFile = 'icinga-' . $etag . $min . '.css';
|
||||
$etag = FileCache::etagForFiles($styleSheet->lessCompiler->getLessFiles());
|
||||
|
||||
$response
|
||||
->setHeader('Cache-Control', 'public', true)
|
||||
->setHeader('ETag', $etag, true)
|
||||
->setHeader('Content-Type', 'text/css', true);
|
||||
|
||||
$cacheFile = 'icinga-' . $etag . ($minified ? '.min' : '') . '.css';
|
||||
$cache = FileCache::instance();
|
||||
if ($cache->has($cacheFile)) {
|
||||
$cache->send($cacheFile);
|
||||
return;
|
||||
|
||||
if (! $noCache && $cache->has($cacheFile)) {
|
||||
$response->setBody($cache->get($cacheFile));
|
||||
} else {
|
||||
$css = $styleSheet->render($minified);
|
||||
$response->setBody($css);
|
||||
$cache->store($cacheFile, $css);
|
||||
}
|
||||
|
||||
$less = new LessCompiler();
|
||||
$less->disableExtendedImport();
|
||||
foreach ($lessFiles as $file) {
|
||||
$less->addFile($file);
|
||||
$response->sendResponse();
|
||||
}
|
||||
$less->addLoadedModules();
|
||||
if ($minified) {
|
||||
$less->compress();
|
||||
|
||||
/**
|
||||
* Render the stylesheet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
try {
|
||||
return $this->render();
|
||||
} catch (Exception $e) {
|
||||
Logger::error($e);
|
||||
return IcingaException::describe($e);
|
||||
}
|
||||
$out = self::fixModuleLayoutCss($less->compile());
|
||||
$cache->store($cacheFile, $out);
|
||||
echo $out;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -703,7 +703,7 @@ class FilterEditor extends AbstractWidget
|
|||
|
||||
public function renderSearch()
|
||||
{
|
||||
$html = ' <form method="post" class="search inline dontprint" action="'
|
||||
$html = ' <form method="post" class="search inline" action="'
|
||||
. $this->preservedUrl()
|
||||
. '"><input type="text" name="q" style="width: 8em" class="search" value="" placeholder="'
|
||||
. t('Search...')
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Doc;
|
||||
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Icinga\File\NonEmptyFileIterator;
|
||||
use Icinga\File\FileExtensionFilterIterator;
|
||||
|
||||
/**
|
||||
* Iterator over non-empty Markdown files ordered by the case insensitive "natural order" of file names
|
||||
*/
|
||||
class DocIterator implements Countable, IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Ordered files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fileInfo;
|
||||
|
||||
/**
|
||||
* Create a new DocIterator
|
||||
*
|
||||
* @param string $path Path to the documentation
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
$it = new FileExtensionFilterIterator(
|
||||
new NonEmptyFileIterator(
|
||||
new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($path),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
)
|
||||
),
|
||||
'md'
|
||||
);
|
||||
// Unfortunately we have no chance to sort the iterator
|
||||
$fileInfo = iterator_to_array($it);
|
||||
natcasesort($fileInfo);
|
||||
$this->fileInfo = $fileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->fileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->fileInfo);
|
||||
}
|
||||
}
|
|
@ -4,11 +4,11 @@
|
|||
namespace Icinga\Module\Doc;
|
||||
|
||||
use CachingIterator;
|
||||
use LogicException;
|
||||
use SplFileObject;
|
||||
use SplStack;
|
||||
use Icinga\Data\Tree\SimpleTree;
|
||||
use Icinga\Exception\NotReadableError;
|
||||
use Icinga\Module\Doc\Exception\DocEmptyException;
|
||||
use Icinga\Util\DirectoryIterator;
|
||||
use Icinga\Module\Doc\Exception\DocException;
|
||||
|
||||
/**
|
||||
|
@ -40,7 +40,7 @@ class DocParser
|
|||
/**
|
||||
* Iterator over documentation files
|
||||
*
|
||||
* @var DocIterator
|
||||
* @var DirectoryIterator
|
||||
*/
|
||||
protected $docIterator;
|
||||
|
||||
|
@ -51,34 +51,17 @@ class DocParser
|
|||
*
|
||||
* @throws DocException If the documentation directory does not exist
|
||||
* @throws NotReadableError If the documentation directory is not readable
|
||||
* @throws DocEmptyException If the documentation directory is empty
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
if (! is_dir($path)) {
|
||||
if (! DirectoryIterator::isReadable($path)) {
|
||||
throw new DocException(
|
||||
sprintf(mt('doc', 'Documentation directory \'%s\' does not exist'), $path)
|
||||
);
|
||||
}
|
||||
if (! is_readable($path)) {
|
||||
throw new DocException(
|
||||
sprintf(mt('doc', 'Documentation directory \'%s\' is not readable'), $path)
|
||||
);
|
||||
}
|
||||
$docIterator = new DocIterator($path);
|
||||
if ($docIterator->count() === 0) {
|
||||
throw new DocEmptyException(
|
||||
sprintf(
|
||||
mt(
|
||||
'doc',
|
||||
'Documentation directory \'%s\' does not contain any non-empty Markdown file (\'.md\' suffix)'
|
||||
),
|
||||
mt('doc', 'Documentation directory \'%s\' is not readable'),
|
||||
$path
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->path = $path;
|
||||
$this->docIterator = $docIterator;
|
||||
$this->docIterator = new DirectoryIterator($path, 'md');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,9 +128,8 @@ class DocParser
|
|||
public function getDocTree()
|
||||
{
|
||||
$tree = new SimpleTree();
|
||||
foreach ($this->docIterator as $fileInfo) {
|
||||
/** @var $fileInfo \SplFileInfo */
|
||||
$file = $fileInfo->openFile();
|
||||
foreach ($this->docIterator as $filename) {
|
||||
$file = new SplFileObject($filename);
|
||||
$lastLine = null;
|
||||
$stack = new SplStack();
|
||||
$cachingIterator = new CachingIterator($file, CachingIterator::TOSTRING_USE_CURRENT);
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Doc\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown if a documentation directory is empty
|
||||
*/
|
||||
class DocEmptyException extends DocException
|
||||
{
|
||||
}
|
|
@ -70,6 +70,9 @@ class DocTocRenderer extends DocRenderer
|
|||
*/
|
||||
public function render()
|
||||
{
|
||||
if (empty($this->content)) {
|
||||
return '<p>' . mt('doc', 'Documentation is empty.') . '</p>';
|
||||
}
|
||||
$view = $this->getView();
|
||||
$zendUrlHelper = $view->getHelper('Url');
|
||||
foreach ($this as $section) {
|
||||
|
|
|
@ -258,19 +258,4 @@ class TransportConfigForm extends ConfigForm
|
|||
$this->populate($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all form element values
|
||||
*
|
||||
* @param bool $suppressArrayNotation Ignored
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues();
|
||||
$values = array_merge($values, $values['transport_form']);
|
||||
unset($values['transport_form']);
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php if (! $this->compact): ?>
|
||||
<div class="controls">
|
||||
<?= $this->tabs; ?>
|
||||
<div style="float: right;" class="dontprint">
|
||||
<div style="float: right;" class="dont-print">
|
||||
<?= $intervalBox; ?>
|
||||
</div>
|
||||
<?= $this->limiter; ?>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php if (! $this->compact): ?>
|
||||
<div class="controls separated dont-print">
|
||||
<div class="controls separated">
|
||||
<?= $tabs ?>
|
||||
<?= $this->render('list/components/selectioninfo.phtml') ?>
|
||||
<div class="grid">
|
||||
|
|
|
@ -7,7 +7,7 @@ if (! $stats instanceof stdClass) {
|
|||
$stats = $stats->fetchRow();
|
||||
}
|
||||
?>
|
||||
<div class="hosts-summary">
|
||||
<div class="hosts-summary dont-print">
|
||||
<span class="hosts-link"><?= $this->qlink(
|
||||
sprintf($this->translatePlural('%u Host', '%u Hosts', $stats->hosts_total), $stats->hosts_total),
|
||||
// @TODO(el): Fix that
|
||||
|
|
|
@ -7,7 +7,7 @@ if (! $stats instanceof stdClass) {
|
|||
$stats = $stats->fetchRow();
|
||||
}
|
||||
?>
|
||||
<div class="services-summary">
|
||||
<div class="services-summary dont-print">
|
||||
<span class="services-link"><?= $this->qlink(
|
||||
sprintf($this->translatePlural(
|
||||
'%u Service', '%u Services', $stats->services_total),
|
||||
|
|
|
@ -3,7 +3,7 @@ use Icinga\Module\Monitoring\Object\Host;
|
|||
use Icinga\Module\Monitoring\Object\Service;
|
||||
|
||||
if (! $this->compact): ?>
|
||||
<div class="controls separated dont-print">
|
||||
<div class="controls separated">
|
||||
<?= $tabs ?>
|
||||
<?= $this->render('list/components/selectioninfo.phtml') ?>
|
||||
<div class="grid">
|
||||
|
|
|
@ -20,13 +20,13 @@ if (! $this->compact): ?>
|
|||
<?php return; endif ?>
|
||||
<table class="table-row-selectable common-table" data-base-target="_next">
|
||||
<thead>
|
||||
<tr>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><?= $this->translate('Host Group') ?></th>
|
||||
<th><?= $this->translate('Host States') ?></th>
|
||||
<th></th>
|
||||
<th><?= $this->translate('Service States') ?></th>
|
||||
</tr>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($hostgroups->peekAhead($this->compact) as $hostgroup): ?>
|
||||
|
|
|
@ -18,9 +18,11 @@ if (! $this->compact): ?>
|
|||
<?php return; endif ?>
|
||||
<table class="table-row-selectable common-table" data-base-target="_next">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><?= $this->translate('Service Group') ?></th>
|
||||
<th><?= $this->translate('Service States') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($servicegroups->peekAhead($this->compact) as $serviceGroup): ?>
|
||||
|
|
|
@ -186,12 +186,14 @@
|
|||
width: 100%;
|
||||
|
||||
tr[href] {
|
||||
.transition(background 0.2s ease);
|
||||
|
||||
&.active {
|
||||
background-color: @gray-lighter;
|
||||
background-color: @tr-active-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @gray-lightest;
|
||||
background-color: @tr-hover-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,21 +116,6 @@ class AuthBackendPage extends Form
|
|||
$this->addSubForm($backendForm, 'backend_form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all form element values
|
||||
*
|
||||
* @param bool $suppressArrayNotation Ignored
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues();
|
||||
$values = array_merge($values, $values['backend_form']);
|
||||
unset($values['backend_form']);
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given form data and check whether it's possible to authenticate using the configured backend
|
||||
*
|
||||
|
|
|
@ -129,19 +129,4 @@ class UserGroupBackendPage extends Form
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all form element values
|
||||
*
|
||||
* @param bool $suppressArrayNotation Ignored
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues($suppressArrayNotation = false)
|
||||
{
|
||||
$values = parent::getValues();
|
||||
$values = array_merge($values, $values['backend_form']);
|
||||
unset($values['backend_form']);
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
// Text color on <a>
|
||||
@link-color: @text-color;
|
||||
|
||||
@tr-active-color: #E5F9FF;
|
||||
@tr-hover-color: #F5FDFF;
|
||||
|
||||
// Font families
|
||||
@font-family: Calibri, Helvetica, sans-serif;
|
||||
@font-family-fixed: "Liberation Mono", "Lucida Console", Courier, monospace;
|
||||
|
@ -138,19 +141,11 @@ td, th {
|
|||
}
|
||||
}
|
||||
|
||||
#layout {
|
||||
background-color: @body-bg-color;
|
||||
color: @text-color;
|
||||
font-family: @font-family;
|
||||
}
|
||||
|
||||
#main > .container, #menu, #header {
|
||||
font-size: @font-size;
|
||||
line-height: @line-height;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.dont-print {
|
||||
display: none;
|
||||
}
|
||||
// Styles for when the page is loading. JS will remove this class once the document is ready
|
||||
.loading * {
|
||||
// Disable all transition on page load
|
||||
-webkit-transition: none !important;
|
||||
-moz-transition: none !important;
|
||||
-o-transition: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
div#footer {
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/** Notifications **/
|
||||
|
||||
#notifications {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#notifications > li {
|
||||
list-style-type: none;
|
||||
display: block;
|
||||
border-top: 1px solid #999;
|
||||
color: white;
|
||||
line-height: 2.5em;
|
||||
padding-left: 3em;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 1em center;
|
||||
}
|
||||
|
||||
#notifications > li:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#notifications > li.info {
|
||||
background-color: @colorFormNotificationInfo;
|
||||
}
|
||||
|
||||
#notifications > li.warning {
|
||||
background-color: @colorWarningHandled;
|
||||
}
|
||||
#notifications > li.error {
|
||||
background-color: @colorCritical;
|
||||
background-image: url(../img/icons/error_white.png);
|
||||
}
|
||||
|
||||
#notifications > li.success {
|
||||
background-color: #fe6;
|
||||
background-image: url(../img/icons/success.png);
|
||||
color: #333;
|
||||
}
|
||||
/** END of Notifications **/
|
|
@ -1,18 +0,0 @@
|
|||
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
#logo {
|
||||
height: 4.0em;
|
||||
width: 13em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#logo a {
|
||||
display: block;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
#logo img {
|
||||
width: 100px;
|
||||
margin-left: 1.5em;
|
||||
margin-top: 0.8em;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
/* Layout colors */
|
||||
|
||||
#sidebar {
|
||||
background-color: @gray-lighter;
|
||||
}
|
||||
|
||||
#header {
|
||||
background-color: @icinga-blue;
|
||||
color: #ddd;
|
||||
color: #d0d0d0;
|
||||
}
|
||||
|
||||
#header input {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
#main {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#col1.impact, #col2.impact, #col3.impact {
|
||||
background-color: #ddd;
|
||||
transition: background-color 2s 1s linear;
|
||||
-moz-transition: background-color 2s 1s linear;
|
||||
-o-transition: background-color 2s 1s linear;
|
||||
-webkit-transition: background-color 2s 1s linear;
|
||||
.controls {
|
||||
background-color: #ddd;
|
||||
transition: background-color 2s 1s linear;
|
||||
-moz-transition: background-color 2s 1s linear;
|
||||
-o-transition: background-color 2s 1s linear;
|
||||
-webkit-transition: background-color 2s 1s linear;
|
||||
}
|
||||
}
|
||||
|
|
@ -96,7 +96,6 @@ html {
|
|||
.controls > ul.tabs {
|
||||
margin-top: 0;
|
||||
height: 1.5em;
|
||||
background-color: @icinga-blue;
|
||||
font-size: 0.75em;
|
||||
padding: 0.2em 0 0;
|
||||
}
|
||||
|
@ -138,7 +137,6 @@ html {
|
|||
*/
|
||||
.container .controls {
|
||||
top: 0;
|
||||
background-color: white;
|
||||
padding: 1em 1em 0;
|
||||
z-index: 100;
|
||||
|
||||
|
@ -267,42 +265,9 @@ html {
|
|||
margin-right: 1%;
|
||||
}
|
||||
|
||||
#logo img {
|
||||
/* TODO: Quickfix, this needs improvement */
|
||||
width: 0 !important;
|
||||
top: -100px;
|
||||
position: absolute;
|
||||
}
|
||||
#main {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#login {
|
||||
.below-logo label {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
.footer {
|
||||
margin-left: 0;
|
||||
}
|
||||
h1 {
|
||||
margin-left: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
form {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
form input {
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
form input[type=submit] {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
#footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#guest-error {
|
||||
background-color: @icinga-blue;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#guest-error #icinga-logo {
|
||||
.fadein();
|
||||
}
|
||||
|
||||
#guest-error-message {
|
||||
.fadein();
|
||||
color: @body-bg-color;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
#header,
|
||||
#main > .container,
|
||||
#menu {
|
||||
font-size: @font-size;
|
||||
line-height: @line-height;
|
||||
}
|
||||
|
||||
#header {
|
||||
background-color: @icinga-blue;
|
||||
}
|
||||
|
||||
#header-logo-container {
|
||||
height: 4em;
|
||||
margin-left: 1.5em;
|
||||
margin-top: 0.2em;
|
||||
margin-bottom: 0.2em;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#header-logo {
|
||||
background-image: url('../img/logo_icinga-inv.png');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#icinga-logo {
|
||||
background-image: url('../img/logo_icinga_big.png');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain; // Does not work in IE < 10
|
||||
height: 177px;
|
||||
margin-bottom: 2em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#layout {
|
||||
background-color: @body-bg-color;
|
||||
color: @text-color;
|
||||
font-family: @font-family;
|
||||
}
|
||||
|
||||
#login {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
background-color: @gray-lighter;
|
||||
}
|
||||
|
||||
.controls {
|
||||
background-color: @body-bg-color;
|
||||
}
|
||||
|
||||
// Notification styles
|
||||
|
||||
#notifications {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#notifications > li {
|
||||
list-style-type: none;
|
||||
display: block;
|
||||
border-top: 1px solid #999;
|
||||
color: white;
|
||||
line-height: 2.5em;
|
||||
padding-left: 3em;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 1em center;
|
||||
}
|
||||
|
||||
#notifications > li:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#notifications > li.info {
|
||||
background-color: @colorFormNotificationInfo;
|
||||
}
|
||||
|
||||
#notifications > li.warning {
|
||||
background-color: @colorWarningHandled;
|
||||
}
|
||||
#notifications > li.error {
|
||||
background-color: @colorCritical;
|
||||
background-image: url(../img/icons/error_white.png);
|
||||
}
|
||||
|
||||
#notifications > li.success {
|
||||
background-color: #fe6;
|
||||
background-image: url(../img/icons/success.png);
|
||||
color: #333;
|
||||
}
|
|
@ -1,189 +1,91 @@
|
|||
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
// Login page styles
|
||||
|
||||
#login {
|
||||
#icinga-logo {
|
||||
.fadein();
|
||||
}
|
||||
|
||||
background-color: @icinga-blue;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.form-controls {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
padding-top: 5%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
.control-label {
|
||||
color: @body-bg-color;
|
||||
font-size: 1em;
|
||||
line-height: @line-height;
|
||||
}
|
||||
|
||||
.errors {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 0.5em;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
padding: 0.5em;
|
||||
}
|
||||
background-color: @color-critical;
|
||||
color: @body-bg-color;
|
||||
font-size: @font-size-small;
|
||||
margin: 2em 0 0 0;
|
||||
|
||||
.image img {
|
||||
width: 355px;
|
||||
}
|
||||
|
||||
.form {
|
||||
position: absolute;
|
||||
font-size: 0.9em;
|
||||
top: 35%;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.form h1 {
|
||||
text-align: center;
|
||||
font-size: 1.5em;
|
||||
margin-left: 2.3em;
|
||||
border: none;
|
||||
color: @text-color-inverted;
|
||||
font-variant: unset;
|
||||
}
|
||||
|
||||
.form div.element {
|
||||
margin: 0;
|
||||
|
||||
ul.errors {
|
||||
> li {
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.form label {
|
||||
color: @text-color-inverted;
|
||||
font-weight: normal;
|
||||
.control-group {
|
||||
margin: 0 auto; // Center horizontally
|
||||
width: 24em;
|
||||
}
|
||||
|
||||
.control-label-group {
|
||||
display: block;
|
||||
line-height: 2.5em;
|
||||
width: 15em;
|
||||
margin-right: 2.5em;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 18em;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
form input {
|
||||
color: @text-color-inverted;
|
||||
width: 18em;
|
||||
padding: 0.5em;
|
||||
background: rgba(255,255,255,0.2);
|
||||
margin-left: 0;
|
||||
padding: 6px 10px;
|
||||
input[type=password],
|
||||
input[type=text] {
|
||||
background-color: @gray-light; // IE8 fallback
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
border: none;
|
||||
border-bottom: solid 3px @body-bg-color;
|
||||
-webkit-transition: border 0.3s;
|
||||
-moz-transition: border 0.3s;
|
||||
-o-transition: border 0.3s;
|
||||
transition: border 0.3s;
|
||||
color: @body-bg-color;
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form input:focus {
|
||||
border-bottom: solid 3px @body-bg-color;
|
||||
background: rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
ul.errors {
|
||||
.form-controls {
|
||||
margin-bottom: 2em;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
color: @text-color-inverted;
|
||||
background: none;
|
||||
border: none;
|
||||
margin-top: 3em;
|
||||
margin-right: 5px;
|
||||
border: solid 3px @body-bg-color;
|
||||
-webkit-transition: border 0.3s;
|
||||
-moz-transition: border 0.3s;
|
||||
-o-transition: border 0.3s;
|
||||
.button(@icinga-blue, @body-bg-color);
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type=submit]:hover, a.button:hover, input[type=submit]:focus {
|
||||
background: @body-bg-color;
|
||||
color: @icinga-blue;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: @text-color-inverted;
|
||||
margin-top: 5em;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: @text-color-inverted;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
p.config-note {
|
||||
width: 50%;
|
||||
padding: 1em;
|
||||
margin: 0 auto 2.5em;
|
||||
text-align: center;
|
||||
.config-note {
|
||||
color: white;
|
||||
background-color: @color-critical;
|
||||
margin: 0 auto 2em auto; // Center horizontally w/ bottom margin
|
||||
max-width: 50%;
|
||||
min-width: 24em;
|
||||
padding: 1em;
|
||||
|
||||
a {
|
||||
color: @text-color-inverted;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
p.info-box {
|
||||
width: 50%;
|
||||
height: 2.2em;
|
||||
margin: 2em auto 2.5em;
|
||||
|
||||
i.icon-info {
|
||||
float: left;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
em {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* make keyframes that tell the start state and the end state of our object */
|
||||
@-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
|
||||
@-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
|
||||
@keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
|
||||
#login-footer {
|
||||
color: @body-bg-color;
|
||||
font-size: @font-size-small;
|
||||
|
||||
.fade-in {
|
||||
opacity:0; /* make things invisible upon start */
|
||||
-webkit-animation:fadeIn ease-in 1; /* call our keyframe named fadeIn, use animattion ease-in and repeat it only 1 time */
|
||||
-moz-animation:fadeIn ease-in 1;
|
||||
animation:fadeIn ease-in 1;
|
||||
a {
|
||||
font-weight: @font-weight-bold;
|
||||
|
||||
-webkit-animation-fill-mode:forwards; /* this makes sure that after animation is done we remain at the last keyframe value (opacity: 1)*/
|
||||
-moz-animation-fill-mode:forwards;
|
||||
animation-fill-mode:forwards;
|
||||
|
||||
-webkit-animation-duration:1s;
|
||||
-moz-animation-duration:1s;
|
||||
animation-duration:1s;
|
||||
// Social links
|
||||
&:hover > i {
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.fade-in.one {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
-moz-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
// Width for the name column--th--of name-value-table
|
||||
@name-value-table-name-width: 14em;
|
||||
|
||||
.action-link {
|
||||
color: @icinga-blue;
|
||||
.action-link(@color: @icinga-blue) {
|
||||
color: @color;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-weight: @font-weight-bold;
|
||||
}
|
||||
|
||||
.large-icon {
|
||||
|
@ -58,9 +62,9 @@ a:hover > .icon-cancel {
|
|||
|
||||
// Link styles
|
||||
|
||||
.button-link {
|
||||
.action-link();
|
||||
.button();
|
||||
.button-link(@background-color: @body-bg-color, @color: @icinga-blue) {
|
||||
.action-link(@color);
|
||||
.button(@background-color, @color);
|
||||
display: inline-block;
|
||||
height: 35px;
|
||||
line-height: 20px;
|
||||
|
@ -164,13 +168,16 @@ a:hover > .icon-cancel {
|
|||
}
|
||||
|
||||
tr[href] {
|
||||
.transition(background 0.2s ease);
|
||||
|
||||
&.active {
|
||||
background-color: @gray-lighter;
|
||||
background-color: @tr-active-color;
|
||||
border-left-color: @icinga-blue;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @gray-lightest;
|
||||
background-color: @tr-hover-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@ -227,3 +234,35 @@ a:hover > .icon-cancel {
|
|||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles for centering content of unknown width and height both horizontally and vertically
|
||||
*
|
||||
* Example markup:
|
||||
* <div class="centered-ghost">
|
||||
* <div class="centered-content">
|
||||
* <p>I'm centered.</p>
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
|
||||
.centered-content {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.centered-ghost {
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
letter-spacing: -1em; // Remove gap between content and ghost
|
||||
}
|
||||
|
||||
.centered-ghost > * {
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
.centered-ghost:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
|
@ -12,22 +12,24 @@
|
|||
box-shadow: @arguments;
|
||||
}
|
||||
|
||||
.button() {
|
||||
.button(@background-color: @body-bg-color, @color: @icinga-blue) {
|
||||
.rounded-corners(3px);
|
||||
background-color: @body-bg-color;
|
||||
border: 2px solid @icinga-blue;
|
||||
color: @icinga-blue;
|
||||
|
||||
background-color: @background-color;
|
||||
border: 2px solid @color;
|
||||
color: @color;
|
||||
cursor: pointer;
|
||||
padding: @vertical-padding @horizontal-padding;
|
||||
|
||||
// Transition mixin does not work w/ comma-separated transistions
|
||||
-webkit-transition: background 0.3s ease, color 0.3s ease;
|
||||
-moz-transition: background 0.3s ease, color 0.3s ease;
|
||||
-o-transition: background 0.3s ease, color 0.3s ease;
|
||||
transition: background 0.3s ease, color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: @icinga-blue;
|
||||
color: @text-color-inverted;
|
||||
background-color: @color;
|
||||
color: @background-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
@ -79,3 +81,49 @@
|
|||
.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
// Fadein animation
|
||||
|
||||
/* Chrome, WebKit */
|
||||
@-webkit-keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* FF < 16 */
|
||||
@-moz-keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* IE */
|
||||
@-ms-keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Opera < 12.1 */
|
||||
@-o-keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.fadein() {
|
||||
opacity: 0;
|
||||
|
||||
-webkit-animation: fadein 2s ease-in; /* Chrome, WebKit */
|
||||
-moz-animation: fadein 2s ease-in; /* FF < 16 */
|
||||
-o-animation: fadein 2s ease-in; /* Opera < 12.1 */
|
||||
animation: fadein 2s ease-in;
|
||||
|
||||
// Make sure that after animation is done we remain at the last keyframe value (opacity: 1)
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
-moz-animation-fill-mode: forwards;
|
||||
-o-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
// Print styles
|
||||
|
||||
@media print {
|
||||
#fileupload-frame-target,
|
||||
#header,
|
||||
#responsive-debug,
|
||||
#sidebar,
|
||||
.controls > .tabs,
|
||||
.controls .filter,
|
||||
.controls .limiter-control,
|
||||
.controls .pagination-control,
|
||||
.controls .selection-info,
|
||||
.controls .sort-control,
|
||||
.dontprint, // Compat only, use dont-print instead
|
||||
.dont-print {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#layout,
|
||||
#main,
|
||||
.controls {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.container {
|
||||
float: none !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,11 @@
|
|||
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
.controls form, .controls .pagination, .controls .widgetLimiter, .controls > .tabs, .dontprint {
|
||||
.controls form,
|
||||
.controls .pagination,
|
||||
.controls .widgetLimiter,
|
||||
.controls > .tabs,
|
||||
.dontprint, // Compat only, use dont-print instead
|
||||
.dont-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
@ -93,4 +98,3 @@ h1 form {
|
|||
body {
|
||||
margin: 1cm 1cm 1.5cm 1cm;
|
||||
}
|
||||
|
||||
|
|
|
@ -161,6 +161,7 @@
|
|||
},
|
||||
|
||||
onLoad: function (event) {
|
||||
$('body').removeClass('loading');
|
||||
//$('.container').trigger('rendered');
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue