Fix CSP violations (#5060)

This commit is contained in:
Johannes Meyer 2023-08-28 17:15:39 +02:00 committed by GitHub
commit b201b030b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 514 additions and 176 deletions

View File

@ -111,7 +111,7 @@ jobs:
- name: Setup dependencies - name: Setup dependencies
run: | run: |
composer require -n --no-progress mockery/mockery ipl/i18n:@dev composer require -n --no-progress mockery/mockery ipl/i18n:@dev ipl/web:@dev
git clone --depth 1 --branch snapshot/nightly https://github.com/Icinga/icinga-php-thirdparty.git vendor/icinga-php-thirdparty git clone --depth 1 --branch snapshot/nightly https://github.com/Icinga/icinga-php-thirdparty.git vendor/icinga-php-thirdparty
- name: PHPUnit - name: PHPUnit

View File

@ -262,7 +262,7 @@ class RoleForm extends RepositoryForm
PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
)))), )))),
'description' => $spec['description'], 'description' => $spec['description'],
'style' => $isUnrestricted ? 'text-decoration:line-through;' : '', 'class' => $isUnrestricted ? 'unrestricted-role' : '',
'readonly' => $isUnrestricted ?: null 'readonly' => $isUnrestricted ?: null
] ]
) )

View File

@ -24,7 +24,15 @@ $iframeClass = $isIframe ? ' iframe' : '';
$innerLayoutScript = $this->layout()->innerLayout . '.phtml'; $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
?><!DOCTYPE html> ?><!DOCTYPE html>
<html class="no-js<?= $iframeClass ?>" lang="<?= $lang ?>"> <html
class="no-js<?= $iframeClass ?>" lang="<?= $lang ?>"
data-icinga-window-name="<?= $this->protectId('Icinga') ?>"
data-icinga-timezone="<?= $timezone ?>"
data-icinga-base-url="<?= $this->baseUrl(); ?>"
<?php if ($isIframe): ?>
data-icinga-is-iframe
<?php endif ?>
>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="google" value="notranslate"> <meta name="google" value="notranslate">
@ -39,12 +47,6 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
<base target="_parent"> <base target="_parent">
<?php else: ?> <?php else: ?>
<base href="<?= $this->baseUrl(); ?>/"> <base href="<?= $this->baseUrl(); ?>/">
<script type="text/javascript">
(function() {
var html = document.getElementsByTagName('html')[0];
html.className = html.className.replace(/no-js/, 'js');
}());
</script>
<?php endif ?> <?php endif ?>
<link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="all" type="text/css" /> <link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="all" type="text/css" />
<link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" /> <link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" />
@ -55,25 +57,6 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
<div id="layout" class="default-layout<?php if ($showFullscreen): ?> fullscreen-layout<?php endif ?>"> <div id="layout" class="default-layout<?php if ($showFullscreen): ?> fullscreen-layout<?php endif ?>">
<?= $this->render($innerLayoutScript); ?> <?= $this->render($innerLayoutScript); ?>
</div> </div>
<script type="text/javascript">
(function() {
if (document.defaultView && document.defaultView.getComputedStyle) {
var matched;
var html = document.getElementsByTagName('html')[0];
var element = document.getElementById('layout');
var name = document.defaultView
.getComputedStyle(html)['font-family']
.replace(/['",]/g, '');
if (null !== (matched = name.match(/^([a-z]+)-layout$/))) {
element.className = element.className.replace('default-layout', name);
if ('object' === typeof window.console) {
window.console.log('Icinga Web 2: setting initial layout to ' + name);
}
}
}
}());
</script>
<div id="collapsible-control-ghost" class="collapsible-control"> <div id="collapsible-control-ghost" class="collapsible-control">
<button type="button"> <button type="button">
<!-- TODO: Accessibility attributes are missing since usage of the Icon class --> <!-- TODO: Accessibility attributes are missing since usage of the Icon class -->
@ -95,13 +78,6 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
</div> </div>
</div> </div>
<script type="text/javascript" src="<?= $this->href($jsfile) ?>"></script> <script type="text/javascript" src="<?= $this->href($jsfile) ?>"></script>
<script type="text/javascript"> <script type="text/javascript" src="<?= $this->href('js/bootstrap.js') ?>"></script>
window.name = '<?= $this->protectId('Icinga') ?>';
var icinga = new Icinga({
baseUrl: '<?= $this->baseUrl(); ?>',
locale: '<?= $lang; ?>',
timezone: '<?= $timezone ?>'
});
</script>
</body> </body>
</html> </html>

View File

@ -1,3 +1,8 @@
<?php
use Icinga\Util\Csp;
?>
<!-- <!--
This view provides a workaround to logout from an external authentication provider, in case external This view provides a workaround to logout from an external authentication provider, in case external
authentication was configured (the default is to handle authentications internally in Icingaweb2). authentication was configured (the default is to handle authentications internally in Icingaweb2).
@ -9,44 +14,24 @@
--> -->
<div class="content"> <div class="content">
<div id="icinga-logo" aria-hidden="true"></div> <div id="icinga-logo" aria-hidden="true"></div>
<div class="alert alert-warning" id="logout-status"> <div class="alert alert-warning" id="logout-in-progress">
<b><?= $this->translate('Logging out...'); ?></b> <b><?= $this->translate('Logging out...'); ?></b>
<br> <p>
<?= $this->translate( <?= $this->translate(
'If this message does not disappear, it might be necessary to quit the' 'If this message does not disappear, it might be necessary to quit the'
. ' current session manually by clearing the cache, or by closing the current' . ' current session manually by clearing the cache, or by closing the current'
. ' browser session.' . ' browser session.'
); ?> ); ?>
</p>
</div> </div>
<div id="logout-successful" class="alert alert-success" hidden><?= $this->translate('Logout successful'); ?></div>
<div class="container"> <div class="container">
<a href="<?= $this->href('dashboard'); ?>"><?= $this->translate('Login'); ?></a> <a href="<?= $this->href('dashboard'); ?>"><?= $this->translate('Login'); ?></a>
</div> </div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript" src="<?= $this->href('js/logout.js'); ?>"></script>
/* <style type="text/css" nonce="<?= Csp::getStyleNonce(); ?>">
* When JavaScript is available, trigger an XmlHTTPRequest with the non-existing user 'logout' and abort it
* before it is able to finish. This will cause the browser to show a new authentication prompt in the next
* request.
*/
document.addEventListener('DOMContentLoaded', function () {
var msg = document.getElementById('logout-status');
try {
if (navigator.userAgent.toLowerCase().indexOf('msie') !== -1) {
document.execCommand('ClearAuthenticationCache');
} else {
var xhttp = new XMLHttpRequest();
xhttp.open('GET', 'arbitrary url', true, 'logout', 'logout');
xhttp.send('');
xhttp.abort();
}
} catch (e) {
}
msg.innerHTML = '<?= $this->translate('Logout successful!'); ?>';
msg.className = 'alert alert-success';
});
</script>
<style type="text/css">
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background-color: #0095bf; background-color: #0095bf;
@ -66,7 +51,7 @@
width: 100%; width: 100%;
} }
#logout-status { #logout-in-progress {
margin: 2em 0 1em; margin: 2em 0 1em;
font-size: 2em; font-size: 2em;
font-weight: bold; font-weight: bold;

View File

@ -7,20 +7,20 @@
<table class="avp action" data-base-target="_next"> <table class="avp action" data-base-target="_next">
<thead> <thead>
<tr> <tr>
<th style="width: 18em;"> <th>
<strong><?= t('Dashlet Name') ?></strong> <strong><?= t('Dashlet Name') ?></strong>
</th> </th>
<th> <th>
<strong><?= t('Url') ?></strong> <strong><?= t('Url') ?></strong>
</th> </th>
<th style="width: 1.4em;">&nbsp;</th> <th class="icon-col">&nbsp;</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($this->dashboard->getPanes() as $pane): ?> <?php foreach ($this->dashboard->getPanes() as $pane): ?>
<?php if ($pane->getDisabled()) continue; ?> <?php if ($pane->getDisabled()) continue; ?>
<tr> <tr>
<th colspan="2" style="text-align: left; padding: 0.5em;"> <th colspan="2">
<?php if ($pane->isUserWidget()): ?> <?php if ($pane->isUserWidget()): ?>
<?= $this->qlink( <?= $this->qlink(
$pane->getName(), $pane->getName(),
@ -63,7 +63,7 @@
array('title' => sprintf($this->translate('Edit dashlet %s'), $dashlet->getTitle())) array('title' => sprintf($this->translate('Edit dashlet %s'), $dashlet->getTitle()))
); ?> ); ?>
</td> </td>
<td style="table-layout: fixed; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> <td>
<?= $this->qlink( <?= $this->qlink(
$dashlet->getUrl()->getRelativeUrl(), $dashlet->getUrl()->getRelativeUrl(),
$dashlet->getUrl()->getRelativeUrl(), $dashlet->getUrl()->getRelativeUrl(),

View File

@ -13,8 +13,8 @@
<tbody> <tbody>
<?php foreach ($this->logData as $value): ?> <?php foreach ($this->logData as $value): ?>
<?php $datetime = new Datetime($value->datetime) ?> <?php $datetime = new Datetime($value->datetime) ?>
<tr class="state"> <tr>
<td style="width: 6em; text-align: center"> <td>
<?= $this->escape($datetime->format('d.m. H:i')) ?><br /> <?= $this->escape($datetime->format('d.m. H:i')) ?><br />
<?= $this->escape($value->loglevel) ?> <?= $this->escape($value->loglevel) ?>
</td> </td>

View File

@ -17,9 +17,9 @@ if (! $this->compact): ?>
<table class="table-row-selectable common-table"> <table class="table-row-selectable common-table">
<thead> <thead>
<th><?= $this->translate('Shared Navigation'); ?></th> <th><?= $this->translate('Shared Navigation'); ?></th>
<th style="width: 10em"><?= $this->translate('Type'); ?></th> <th><?= $this->translate('Type'); ?></th>
<th style="width: 10em"><?= $this->translate('Owner'); ?></th> <th><?= $this->translate('Owner'); ?></th>
<th style="width: 5em"><?= $this->translate('Unshare'); ?></th> <th><?= $this->translate('Unshare'); ?></th>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($items as $item): ?> <?php foreach ($items as $item): ?>
@ -53,7 +53,7 @@ if (! $this->compact): ?>
) )
); ?></td> ); ?></td>
<?php else: ?> <?php else: ?>
<td data-base-target="_self"><?= $removeForm <td data-base-target="_self" class="remove-nav-item"><?= $removeForm
->setDefault('name', $item->name) ->setDefault('name', $item->name)
->setAction(Url::fromPath( ->setAction(Url::fromPath(
'navigation/unshare', 'navigation/unshare',

View File

@ -75,6 +75,7 @@ class Test extends Cli
// Conflicts with `Tests\Icinga\Module\...\Lib`. But it seems it's not needed anyway... // Conflicts with `Tests\Icinga\Module\...\Lib`. But it seems it's not needed anyway...
//$this->getLoader()->registerNamespace('Tests', $this->getBaseDir('test/php/library')); //$this->getLoader()->registerNamespace('Tests', $this->getBaseDir('test/php/library'));
$this->getLoader()->registerNamespace('Tests\\Icinga\\Lib', $this->getBaseDir('test/php/Lib'));
return $this; return $this;
} }

View File

@ -112,7 +112,7 @@ class BarGraph extends Styleable implements Drawable
$rect->setAttribute('data-icinga-graph-index', $index); $rect->setAttribute('data-icinga-graph-index', $index);
} }
$rect->setAttribute('data-icinga-graph-type', 'bar'); $rect->setAttribute('data-icinga-graph-type', 'bar');
$rect->setAdditionalStyle('clip-path: url(#clip);'); $rect->setAdditionalStyle(['clip-path' => 'url(#clip)']);
return $rect; return $rect;
} }

View File

@ -165,8 +165,8 @@ class LineGraph extends Styleable implements Drawable
$path->setFill($this->fill); $path->setFill($this->fill);
} }
$path->setAdditionalStyle('clip-path: url(#clip);'); $path->setAdditionalStyle(['clip-path' => 'url(#clip)']);
$path->setId($this->id); $path->setId($this->id ?? uniqid('line-graph-'));
$group = $path->toSvg($ctx); $group = $path->toSvg($ctx);
foreach ($this->dataset as $x => $point) { foreach ($this->dataset as $x => $point) {
@ -180,7 +180,7 @@ class LineGraph extends Styleable implements Drawable
if (isset($this->tooltips[$x])) { if (isset($this->tooltips[$x])) {
$invisible = new Circle($point[0], $point[1], 20); $invisible = new Circle($point[0], $point[1], 20);
$invisible->setFill($this->strokeColor); $invisible->setFill($this->strokeColor);
$invisible->setAdditionalStyle('opacity: 0.0;'); $invisible->setAdditionalStyle(['opacity' => '0.0']);
$data = array( $data = array(
'label' => isset($this->graphs[$this->order]['label']) ? 'label' => isset($this->graphs[$this->order]['label']) ?
strtolower($this->graphs[$this->order]['label']) : '', strtolower($this->graphs[$this->order]['label']) : '',

View File

@ -4,6 +4,7 @@
namespace Icinga\Chart\Primitive; namespace Icinga\Chart\Primitive;
use DOMDocument;
use DOMElement; use DOMElement;
use Icinga\Chart\Render\RenderContext; use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Format; use Icinga\Chart\Format;
@ -61,8 +62,23 @@ class Circle extends Styleable implements Drawable
$circle->setAttribute('cx', Format::formatSVGNumber($coords[0])); $circle->setAttribute('cx', Format::formatSVGNumber($coords[0]));
$circle->setAttribute('cy', Format::formatSVGNumber($coords[1])); $circle->setAttribute('cy', Format::formatSVGNumber($coords[1]));
$circle->setAttribute('r', $this->radius); $circle->setAttribute('r', $this->radius);
$circle->setAttribute('style', $this->getStyle());
$id = $this->id ?? uniqid('circle-');
$circle->setAttribute('id', $id);
$this->setId($id);
$this->applyAttributes($circle); $this->applyAttributes($circle);
$style = new DOMDocument();
$style->loadHTML($this->getStyle());
$circle->appendChild(
$circle->ownerDocument->importNode(
$style->getElementsByTagName('style')->item(0),
true
)
);
return $circle; return $circle;
} }
} }

View File

@ -3,6 +3,7 @@
namespace Icinga\Chart\Primitive; namespace Icinga\Chart\Primitive;
use DOMDocument;
use DOMElement; use DOMElement;
use Icinga\Chart\Render\RenderContext; use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Format; use Icinga\Chart\Format;
@ -80,8 +81,23 @@ class Line extends Styleable implements Drawable
$line->setAttribute('x2', Format::formatSVGNumber($x2)); $line->setAttribute('x2', Format::formatSVGNumber($x2));
$line->setAttribute('y1', Format::formatSVGNumber($y1)); $line->setAttribute('y1', Format::formatSVGNumber($y1));
$line->setAttribute('y2', Format::formatSVGNumber($y2)); $line->setAttribute('y2', Format::formatSVGNumber($y2));
$line->setAttribute('style', $this->getStyle());
$id = $this->id ?? uniqid('line-');
$line->setAttribute('id', $id);
$this->setId($id);
$this->applyAttributes($line); $this->applyAttributes($line);
$style = new DOMDocument();
$style->loadHTML($this->getStyle());
$line->appendChild(
$line->ownerDocument->importNode(
$style->getElementsByTagName('style')->item(0),
true
)
);
return $line; return $line;
} }
} }

View File

@ -3,6 +3,7 @@
namespace Icinga\Chart\Primitive; namespace Icinga\Chart\Primitive;
use DOMDocument;
use DOMElement; use DOMElement;
use Icinga\Chart\Render\RenderContext; use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Format; use Icinga\Chart\Format;
@ -162,12 +163,24 @@ class Path extends Styleable implements Drawable
} }
$path = $doc->createElement('path'); $path = $doc->createElement('path');
if ($this->id) {
$path->setAttribute('id', $this->id); $id = $this->id ?? uniqid('path-');
} $path->setAttribute('id', $id);
$this->setId($id);
$path->setAttribute('d', $pathDescription); $path->setAttribute('d', $pathDescription);
$path->setAttribute('style', $this->getStyle());
$this->applyAttributes($path); $this->applyAttributes($path);
$style = new DOMDocument();
$style->loadHTML($this->getStyle());
$path->appendChild(
$path->ownerDocument->importNode(
$style->getElementsByTagName('style')->item(0),
true
)
);
$group->appendChild($path); $group->appendChild($path);
return $group; return $group;
} }

View File

@ -3,6 +3,7 @@
namespace Icinga\Chart\Primitive; namespace Icinga\Chart\Primitive;
use DOMDocument;
use DOMElement; use DOMElement;
use Icinga\Chart\Render\RenderContext; use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Format; use Icinga\Chart\Format;
@ -279,9 +280,22 @@ class PieSlice extends Animatable implements Drawable
$slicePath = $doc->createElement('path'); $slicePath = $doc->createElement('path');
$slicePath->setAttribute('d', $this->getPieSlicePath($x, $y, $r)); $slicePath->setAttribute('d', $this->getPieSlicePath($x, $y, $r));
$slicePath->setAttribute('style', $this->getStyle());
$slicePath->setAttribute('data-icinga-graph-type', 'pieslice'); $slicePath->setAttribute('data-icinga-graph-type', 'pieslice');
$id = $this->id ?? uniqid('slice-');
$slicePath->setAttribute('id', $id);
$this->setId($id);
$style = new DOMDocument();
$style->loadHTML($this->getStyle());
$slicePath->appendChild(
$slicePath->ownerDocument->importNode(
$style->getElementsByTagName('style')->item(0),
true
)
);
$this->applyAttributes($slicePath); $this->applyAttributes($slicePath);
$group->appendChild($slicePath); $group->appendChild($slicePath);
if ($this->caption != "") { if ($this->caption != "") {

View File

@ -4,6 +4,7 @@
namespace Icinga\Chart\Primitive; namespace Icinga\Chart\Primitive;
use DOMElement; use DOMElement;
use DOMDocument;
use Icinga\Chart\Render\RenderContext; use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Format; use Icinga\Chart\Format;
@ -95,11 +96,24 @@ class Rect extends Animatable implements Drawable
$rect->setAttribute('y', Format::formatSVGNumber($y)); $rect->setAttribute('y', Format::formatSVGNumber($y));
$rect->setAttribute('width', Format::formatSVGNumber($width)); $rect->setAttribute('width', Format::formatSVGNumber($width));
$rect->setAttribute('height', Format::formatSVGNumber($height)); $rect->setAttribute('height', Format::formatSVGNumber($height));
$rect->setAttribute('style', $this->getStyle());
$id = $this->id ?? uniqid('rect-');
$rect->setAttribute('id', $id);
$this->setId($id);
$this->applyAttributes($rect); $this->applyAttributes($rect);
$this->appendAnimation($rect, $ctx); $this->appendAnimation($rect, $ctx);
$style = new DOMDocument();
$style->loadHTML($this->getStyle());
$rect->appendChild(
$rect->ownerDocument->importNode(
$style->getElementsByTagName('style')->item(0),
true
)
);
return $rect; return $rect;
} }
} }

View File

@ -5,6 +5,8 @@
namespace Icinga\Chart\Primitive; namespace Icinga\Chart\Primitive;
use DOMElement; use DOMElement;
use Icinga\Util\Csp;
use ipl\Web\Style;
/** /**
* Base class for stylable drawables * Base class for stylable drawables
@ -36,14 +38,14 @@ class Styleable
/** /**
* Additional styles to be appended to the style attribute * Additional styles to be appended to the style attribute
* *
* @var string * @var array<string, string>
*/ */
public $additionalStyle = ''; public $additionalStyle = [];
/** /**
* The id of this element * The id of this element
* *
* @var string * @var ?string
*/ */
public $id = null; public $id = null;
@ -83,7 +85,7 @@ class Styleable
/** /**
* Set additional styles for this drawable * Set additional styles for this drawable
* *
* @param string $styles The styles to set additionally * @param array<string, string> $styles The styles to set additionally
* *
* @return $this Fluid interface * @return $this Fluid interface
*/ */
@ -121,15 +123,20 @@ class Styleable
} }
/** /**
* Return the content of the style attribute as a string * Return the ruleset used for styling the DOMNode
* *
* @return string A string containing styles * @return Style A ruleset containing styles
*/ */
public function getStyle() public function getStyle()
{ {
$base = sprintf("fill: %s; stroke: %s;stroke-width: %s;", $this->fill, $this->strokeColor, $this->strokeWidth); $styles = $this->additionalStyle;
$base .= ';' . $this->additionalStyle . ';'; $styles['fill'] = $this->fill;
return $base; $styles['stroke'] = $this->strokeColor;
$styles['stroke-width'] = (string) $this->strokeWidth;
return (new Style())
->setNonce(Csp::getStyleNonce())
->add("#$this->id", $styles);
} }
/** /**

View File

@ -4,10 +4,12 @@
namespace Icinga\Chart\Primitive; namespace Icinga\Chart\Primitive;
use DOMDocument;
use DOMElement; use DOMElement;
use DOMText; use DOMText;
use Icinga\Chart\Render\RenderContext; use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Format; use Icinga\Chart\Format;
use ipl\Html\HtmlDocument;
/** /**
* Wrapper for the SVG text element * Wrapper for the SVG text element
@ -97,6 +99,15 @@ class Text extends Styleable implements Drawable
$this->y = $y; $this->y = $y;
$this->text = $text; $this->text = $text;
$this->fontSize = $fontSize; $this->fontSize = $fontSize;
$this->setAdditionalStyle([
'font-size' => $this->fontSize,
'font-family' => 'Ubuntu, Calibri, Trebuchet MS, Helvetica, Verdana, sans-serif',
'font-weight' => $this->fontWeight,
'font-stretch' => $this->fontStretch,
'font-style' => 'normal',
'text-anchor' => $this->alignment
]);
} }
/** /**
@ -150,19 +161,24 @@ class Text extends Styleable implements Drawable
list($x, $y) = $ctx->toAbsolute($this->x, $this->y); list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
$text = $ctx->getDocument()->createElement('text'); $text = $ctx->getDocument()->createElement('text');
$text->setAttribute('x', Format::formatSVGNumber($x - 15)); $text->setAttribute('x', Format::formatSVGNumber($x - 15));
$text->setAttribute(
'style', $id = $this->id ?? uniqid('text-');
$this->getStyle() $text->setAttribute('id', $id);
. ';font-size:' . $this->fontSize $this->setId($id);
. '; font-family: Ubuntu, Calibri, Trebuchet MS, Helvetica, Verdana, sans-serif'
. ';font-weight: ' . $this->fontWeight
. ';font-stretch: ' . $this->fontStretch
. '; font-style: normal;'
. 'text-anchor: ' . $this->alignment
);
$text->setAttribute('y', Format::formatSVGNumber($y)); $text->setAttribute('y', Format::formatSVGNumber($y));
$text->appendChild(new DOMText($this->text)); $text->appendChild(new DOMText($this->text));
$style = new DOMDocument();
$style->loadHTML($this->getStyle());
$text->appendChild(
$text->ownerDocument->importNode(
$style->getElementsByTagName('style')->item(0),
true
)
);
return $text; return $text;
} }
} }

View File

@ -4,8 +4,10 @@
namespace Icinga\Test { namespace Icinga\Test {
use Exception; use Exception;
use Icinga\Util\Csp;
use Icinga\Web\Request; use Icinga\Web\Request;
use Icinga\Web\Response; use Icinga\Web\Response;
use Icinga\Web\Session;
use ipl\I18n\NoopTranslator; use ipl\I18n\NoopTranslator;
use ipl\I18n\StaticTranslator; use ipl\I18n\StaticTranslator;
use RuntimeException; use RuntimeException;
@ -14,6 +16,7 @@ namespace Icinga\Test {
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\Data\ResourceFactory; use Icinga\Data\ResourceFactory;
use Icinga\Data\Db\DbConnection; use Icinga\Data\Db\DbConnection;
use Tests\Icinga\Lib\FakeSession;
/** /**
* Class BaseTestCase * Class BaseTestCase
@ -108,6 +111,8 @@ namespace Icinga\Test {
parent::setUp(); parent::setUp();
$this->setupRequestMock(); $this->setupRequestMock();
$this->setupResponseMock(); $this->setupResponseMock();
Session::create(new FakeSession());
Csp::createNonce();
StaticTranslator::$instance = new NoopTranslator(); StaticTranslator::$instance = new NoopTranslator();
} }

View File

@ -50,6 +50,6 @@ class Announcements extends AbstractWidget
return $html; return $html;
} }
// Force container update on XHR // Force container update on XHR
return '<div style="display: none;"></div>'; return '<div hidden></div>';
} }
} }

View File

@ -39,14 +39,14 @@ class ApplicationStateMessages extends AbstractWidget
} }
if (! (bool) $enabled) { if (! (bool) $enabled) {
return '<div style="display: none;"></div>'; return '<div hidden></div>';
} }
$active = $this->getMessages(); $active = $this->getMessages();
if (empty($active)) { if (empty($active)) {
// Force container update on XHR // Force container update on XHR
return '<div style="display: none;"></div>'; return '<div hidden></div>';
} }
$html = '<div>'; $html = '<div>';

View File

@ -6,7 +6,9 @@ namespace Icinga\Web\Widget\Chart;
use DateInterval; use DateInterval;
use DateTime; use DateTime;
use Icinga\Util\Color; use Icinga\Util\Color;
use Icinga\Util\Csp;
use Icinga\Web\Widget\AbstractWidget; use Icinga\Web\Widget\AbstractWidget;
use ipl\Web\Style;
/** /**
* Display a colored grid that visualizes a set of values for each day * Display a colored grid that visualizes a set of values for each day
@ -32,6 +34,9 @@ class HistoryColorGrid extends AbstractWidget
private $color; private $color;
public $opacity = 1.0; public $opacity = 1.0;
/** @var array<string, array<string, string>> History grid css rulesets */
protected $rulesets = [];
public function __construct($color = '#51e551', $start = null, $end = null) public function __construct($color = '#51e551', $start = null, $end = null)
{ {
$this->setColor($color); $this->setColor($color);
@ -123,18 +128,30 @@ class HistoryColorGrid extends AbstractWidget
{ {
if (array_key_exists($day, $this->data) && $this->data[$day]['value'] > 0) { if (array_key_exists($day, $this->data) && $this->data[$day]['value'] > 0) {
$entry = $this->data[$day]; $entry = $this->data[$day];
return '<a ' . $this->rulesets['.grid-day-with-entry-' . $entry['value']] = [
'style="background-color: ' . $this->calculateColor($entry['value']) . ';' 'background-color' => $this->calculateColor($entry['value']),
. ' opacity: ' . $this->opacity . ';" ' . 'opacity' => $this->opacity
'aria-label="' . $entry['caption'] . '" ' . ];
'title="' . $entry['caption'] . '" ' .
'href="' . $entry['url'] . '" ' . return '<a class="grid-day-with-entry-'
'></a>'; . $entry['value']
. '" '
. 'aria-label="' . $entry['caption']
. '" '
. 'title="' . $entry['caption']
. '" '
. 'href="' . $entry['url']
. '" '
. '"></a>';
} else { } else {
return '<span ' . if (! isset($this->rulesets['.grid-day-no-entry'])) {
'style="background-color: ' . $this->calculateColor(0) . '; opacity: ' . $this->opacity . ';" ' . $this->rulesets['.grid-day-no-entry'] = [
'title="No entries for ' . $day . '" ' . 'background-color' => $this->calculateColor(0),
'></span>'; 'opacity' => $this->opacity
];
}
return '<span class="grid-day-no-entry"' . ' title="No entries for ' . $day . '"></span>';
} }
} }
@ -366,8 +383,18 @@ class HistoryColorGrid extends AbstractWidget
} }
$grid = $this->createGrid(); $grid = $this->createGrid();
if ($this->orientation === self::ORIENTATION_HORIZONTAL) { if ($this->orientation === self::ORIENTATION_HORIZONTAL) {
return $this->renderHorizontal($grid); $html = $this->renderHorizontal($grid);
} else {
$html = $this->renderVertical($grid);
} }
return $this->renderVertical($grid);
$historyGridStyle = new Style();
$historyGridStyle->setNonce(Csp::getStyleNonce());
foreach ($this->rulesets as $selector => $properties) {
$historyGridStyle->add($selector, $properties);
}
return $html . $historyGridStyle;
} }
} }

View File

@ -471,7 +471,7 @@ class FilterEditor extends AbstractWidget
} }
if ($this->addTo && $this->addTo == $filter->getId()) { if ($this->addTo && $this->addTo == $filter->getId()) {
$parts[] = '<li style="background: #ffb">' . $this->renderNewFilter() .$this->cancelLink(). '</li>'; $parts[] = '<li class="new-filter">' . $this->renderNewFilter() .$this->cancelLink(). '</li>';
} }
$class = $level === 0 ? ' class="datafilter"' : ''; $class = $level === 0 ? ' class="datafilter"' : '';
@ -575,7 +575,7 @@ class FilterEditor extends AbstractWidget
$this->elementId('operator', $filter), $this->elementId('operator', $filter),
$ops, $ops,
$filter === null ? null : $filter->getOperatorName(), $filter === null ? null : $filter->getOperatorName(),
array('style' => 'width: 5em') ['class' => 'filter-operator']
); );
} }
@ -594,7 +594,7 @@ class FilterEditor extends AbstractWidget
$this->elementId('sign', $filter), $this->elementId('sign', $filter),
$signs, $signs,
$filter === null ? null : $filter->getSign(), $filter === null ? null : $filter->getSign(),
array('style' => 'width: 4em') ['class' => 'filter-rule']
); );
} }
@ -738,7 +738,7 @@ class FilterEditor extends AbstractWidget
$html = ' <form method="post" class="search inline" action="' $html = ' <form method="post" class="search inline" action="'
. $preservedUrl . $preservedUrl
. '"><input type="text" name="q" style="width: 8em" class="search" value="" placeholder="' . '"><input type="text" name="q" class="search search-input" value="" placeholder="'
. t('Search...') . t('Search...')
. '" /></form>'; . '" /></form>';
@ -779,7 +779,7 @@ class FilterEditor extends AbstractWidget
. '<form action="' . '<form action="'
. Url::fromRequest() . Url::fromRequest()
. '" class="editor" method="POST">' . '" class="editor" method="POST">'
. '<input type="submit" name="submit" value="Apply" style="display:none;"/>' . '<input type="submit" name="submit" value="Apply" hidden/>'
. '<ul class="tree"><li>' . '<ul class="tree"><li>'
. $this->renderFilter($this->filter) . $this->renderFilter($this->filter)
. '</li></ul>' . '</li></ul>'

View File

@ -52,7 +52,7 @@ EOT;
* @var string * @var string
*/ */
private $closeTpl = <<< 'EOT' private $closeTpl = <<< 'EOT'
<li style="float: right;"> <li class="close-container-btn">
<a href="#" title="{TITLE}" aria-label="{TITLE}" class="close-container-control"> <a href="#" title="{TITLE}" aria-label="{TITLE}" class="close-container-control">
<i aria-hidden="true" class="icon-cancel"></i> <i aria-hidden="true" class="icon-cancel"></i>
</a> </a>

View File

@ -5,11 +5,10 @@
<div class="content"> <div class="content">
<?php foreach ($this->font->glyphs as $icon): ?> <?php foreach ($this->font->glyphs as $icon): ?>
<!-- TODO: move CSS away //--> <div class="icon <?=
<div style="width: 33%; font-size: 1.5em; height: 2em; float: left;" class="<?=
$this->font->css_prefix_text . $icon->css $this->font->css_prefix_text . $icon->css
?>"> ?>">
<?= $this->escape($icon->css) ?> <span style="font-size: 0.6em">(0x<?= dechex($icon->code) ?>)</span> <?= $this->escape($icon->css) ?> <span class="icon-code">(0x<?= dechex($icon->code) ?>)</span>
</div> </div>
<?php endforeach ?> <?php endforeach ?>
</div> </div>

View File

@ -107,3 +107,14 @@ th {
text-align: left; text-align: left;
text-transform: uppercase; text-transform: uppercase;
} }
.icon {
width: 33%;
font-size: 1.5em;
height: 2em;
float: left;
}
.icon-code {
font-size: 0.6em;
}

View File

@ -63,7 +63,7 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
protected static $htmlReplacements = array( protected static $htmlReplacements = array(
"\t", "\t",
"\n", "\n",
'<table style="font-size: 0.75em"' '<table class="output-table"'
); );
/** @var \Icinga\Module\Monitoring\Web\Helper\PluginOutputHookRenderer */ /** @var \Icinga\Module\Monitoring\Web\Helper\PluginOutputHookRenderer */

View File

@ -112,9 +112,9 @@ for ($i = 0; $i < $diff->days; $i += $step) {
$f->add(new DateInterval('P' . $step . 'D')); $f->add(new DateInterval('P' . $step . 'D'));
} }
?> ?>
<div style="width: 33.5em;"> <div class="event-grid">
<?php foreach (array_reverse($grids) as $key => $grid): ?> <?php foreach (array_reverse($grids) as $key => $grid): ?>
<div style=" <?= $this->orientation === 'horizontal' ? '' : 'display: inline-block; vertical-align: top; top; margin: 0.5em;' ?>"> <div class=" <?= $this->orientation === 'horizontal' ? '' : 'vertical' ?>">
<?= $grid; ?> <?= $grid; ?>
<?= $this->orientation === 'horizontal' ? '<br />' : '' ?> <?= $this->orientation === 'horizontal' ? '<br />' : '' ?>
</div> </div>

View File

@ -16,7 +16,7 @@
<table class="name-value-table"> <table class="name-value-table">
<tbody> <tbody>
<tr> <tr>
<th style="width: 20%"></th> <th></th>
<td><strong><?= $this->escape($contact->contact_alias) ?></strong> (<?= $contact->contact_name ?>)</td> <td><strong><?= $this->escape($contact->contact_alias) ?></strong> (<?= $contact->contact_name ?>)</td>
</tr> </tr>
<?php if ($contact->contact_email): ?> <?php if ($contact->contact_email): ?>

View File

@ -1,9 +1,15 @@
<?php <?php
use Icinga\Util\Csp;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Util\Color; use Icinga\Util\Color;
use ipl\Web\Style;
$groupInfo = $timeline->getGroupInfo(); $groupInfo = $timeline->getGroupInfo();
$firstRow = ! $beingExtended; $firstRow = ! $beingExtended;
$timelineStyle = (new Style())
->setNonce(Csp::getStyleNonce())
->setModule('monitoring');
if (! $beingExtended && !$this->compact): ?> if (! $beingExtended && !$this->compact): ?>
<div class="controls"> <div class="controls">
@ -79,24 +85,72 @@ if (! $beingExtended && !$this->compact): ?>
<?php foreach ($groupInfo as $groupName => $labelAndColor): ?> <?php foreach ($groupInfo as $groupName => $labelAndColor): ?>
<?php if (array_key_exists($groupName, $timeInfo[1])): ?> <?php if (array_key_exists($groupName, $timeInfo[1])): ?>
<?php <?php
$styleId = uniqid();
$circleWidth = $timeline->calculateCircleWidth($timeInfo[1][$groupName], 2); $circleWidth = $timeline->calculateCircleWidth($timeInfo[1][$groupName], 2);
$extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$groupName], 2); $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$groupName], 2);
?> ?>
<?php if ($firstRow && $extrapolatedCircleWidth !== $circleWidth): ?> <?php if ($firstRow && $extrapolatedCircleWidth !== $circleWidth): ?>
<div class="circle-box" style="width: <?= $extrapolatedCircleWidth; ?>;"> <?php
<div class="outer-circle extrapolated <?= $timeInfo[1][$groupName]->getClass() ?>" style="<?= sprintf( $timelineStyle->add(
'width: %2$s; height: %2$s; margin-top: -%1$Fem;', "#circle-box-$styleId",
(float) substr($extrapolatedCircleWidth, 0, -2) / 2, ['width' => $extrapolatedCircleWidth]
$extrapolatedCircleWidth );
); ?>">
$timelineStyle->add(
"#outer-circle-$styleId",
[
'width' => $extrapolatedCircleWidth,
'height' => $extrapolatedCircleWidth,
'margin-top' => sprintf(
'-%Fem',
(float)substr($extrapolatedCircleWidth, 0, -2) / 2
)
]
);
?>
<div id="circle-box-<?= $styleId ?>" class="circle-box">
<div id="outer-circle-<?= $styleId ?>" class="outer-circle extrapolated <?= $timeInfo[1][$groupName]->getClass() ?>">
<?php else: ?> <?php else: ?>
<div class="circle-box" style="width: <?= $circleWidth; ?>;"> <?php
<div class="outer-circle" style="<?= sprintf( $timelineStyle->add(
'width: %2$s; height: %2$s; margin-top: -%1$Fem;', "#circle-box-$styleId",
(float) substr($circleWidth, 0, -2) / 2, ['width' => $circleWidth]
$circleWidth );
); ?>">
$timelineStyle->add(
"#outer-circle-$styleId",
[
'width' => $circleWidth,
'height' => $circleWidth,
'margin-top' => sprintf(
'-%Fem',
(float)substr($circleWidth, 0, -2) / 2
)
]
);
?>
<div id="circle-box-<?= $styleId ?>" class="circle-box">
<div id="outer-circle-<?= $styleId ?>" class="outer-circle">
<?php endif ?> <?php endif ?>
<?php
$timelineStyle->add(
"#inner-circle-$styleId",
[
'width' => $circleWidth,
'height' => $circleWidth,
'margin-top' => sprintf(
'-%Fem',
(float)substr($circleWidth, 0, -2) / 2
),
'margin-left' => sprintf(
'-%Fem',
(float)substr($circleWidth, 0, -2) / 2
),
]
);
?>
<?= $this->qlink( <?= $this->qlink(
'', '',
$timeInfo[1][$groupName]->getDetailUrl(), $timeInfo[1][$groupName]->getDetailUrl(),
@ -112,12 +166,8 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
strtolower($labelAndColor['label']), strtolower($labelAndColor['label']),
$titleTime $titleTime
), ),
'class' => 'inner-circle ' . $timeInfo[1][$groupName]->getClass(), 'id' => "inner-circle-$styleId",
'style' => sprintf( 'class' => "inner-circle " . $timeInfo[1][$groupName]->getClass()
'width: %2$s; height: %2$s; margin-top: -%1$Fem; margin-left: -%1$Fem;',
(float) substr($circleWidth, 0, -2) / 2,
(string) $circleWidth
)
) )
); ?> ); ?>
</div> </div>
@ -143,3 +193,4 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
</div> </div>
</div> </div>
<?php endif ?> <?php endif ?>
<?= $timelineStyle; ?>

View File

@ -427,5 +427,6 @@ $dashboard->add(
/* /*
* CSS * CSS
*/ */
$this->provideCssFile('event-grid.less');
$this->provideCssFile('service-grid.less'); $this->provideCssFile('service-grid.less');
$this->provideCssFile('tables.less'); $this->provideCssFile('tables.less');

View File

@ -0,0 +1,9 @@
.event-grid {
width: 33.5em;
.vertical {
display: inline-block;
vertical-align: top;
margin: 0.5em;
}
}

View File

@ -654,6 +654,9 @@ form.instance-features span.description, form.object-features span.description {
.plugin-output { .plugin-output {
border-left: 5px solid @gray-lighter; border-left: 5px solid @gray-lighter;
padding: 0.66em 0.33em; padding: 0.66em 0.33em;
.output-table {
font-size: 0.75em;
}
.state-critical { .state-critical {
background-color: @color-critical; background-color: @color-critical;

View File

@ -1,12 +1,17 @@
<?php <?php
use Icinga\Util\Csp;
use Icinga\Web\Notification; use Icinga\Web\Notification;
use ipl\Web\Style;
$pages = $wizard->getPages(); $pages = $wizard->getPages();
$finished = isset($success); $finished = isset($success);
$configPages = array_slice($pages, 3, count($pages) - 4, true); $configPages = array_slice($pages, 3, count($pages) - 4, true);
$currentPos = array_search($wizard->getCurrentPage(), $pages, true); $currentPos = array_search($wizard->getCurrentPage(), $pages, true);
list($configPagesLeft, $configPagesRight) = array_chunk($configPages, (int)(count($configPages) / 2), true); list($configPagesLeft, $configPagesRight) = array_chunk($configPages, (int)(count($configPages) / 2), true);
$setupStyle = (new Style())
->setSelector('.setup-header > .progress-bar')
->setNonce(Csp::getStyleNonce());
$visitedPages = array_keys($wizard->getPageData()); $visitedPages = array_keys($wizard->getPageData());
$maxProgress = max(array_merge([0], array_keys(array_filter( $maxProgress = max(array_merge([0], array_keys(array_filter(
@ -14,12 +19,19 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
function ($page) use ($visitedPages) { return in_array($page->getName(), $visitedPages); } function ($page) use ($visitedPages) { return in_array($page->getName(), $visitedPages); }
)))); ))));
$setupStyle->add(
'.width-percent-10',
['width' => '10%']
)->add(
'.width-percent-60',
['width' => '60%']
);
?> ?>
<div id="setup-content-wrapper" data-base-target="layout"> <div id="setup-content-wrapper" data-base-target="layout">
<div class="setup-header"> <div class="setup-header">
<?= $this->img('img/icinga-logo-big.png'); ?> <?= $this->img('img/icinga-logo-big.png'); ?>
<div class="progress-bar"> <div class="progress-bar">
<div class="step" style="width: 10%;"> <div class="step width-percent-10">
<h1><?= $this->translate('Welcome', 'setup.progress'); ?></h1> <h1><?= $this->translate('Welcome', 'setup.progress'); ?></h1>
<?php $stateClass = $finished || $currentPos > 0 ? 'complete' : ( <?php $stateClass = $finished || $currentPos > 0 ? 'complete' : (
$maxProgress > 0 ? 'visited' : 'active' $maxProgress > 0 ? 'visited' : 'active'
@ -30,7 +42,7 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
<td class="right"><div class="line right <?= $stateClass; ?>"></div></td> <td class="right"><div class="line right <?= $stateClass; ?>"></div></td>
</tr></tbody></table> </tr></tbody></table>
</div> </div>
<div class="step" style="width: 10%;"> <div class="step width-percent-10">
<h1><?= $this->translate('Modules', 'setup.progress'); ?></h1> <h1><?= $this->translate('Modules', 'setup.progress'); ?></h1>
<?php $stateClass = $finished || $currentPos > 1 ? ' complete' : ( <?php $stateClass = $finished || $currentPos > 1 ? ' complete' : (
$maxProgress > 1 ? ' visited' : ( $maxProgress > 1 ? ' visited' : (
@ -46,7 +58,7 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
<?= $this->restartForm ?> <?= $this->restartForm ?>
<?php endif ?> <?php endif ?>
</div> </div>
<div class="step" style="width: 10%;"> <div class="step width-percent-10">
<h1><?= $this->translate('Requirements', 'setup.progress'); ?></h1> <h1><?= $this->translate('Requirements', 'setup.progress'); ?></h1>
<?php $stateClass = $finished || $currentPos > 2 ? ' complete' : ( <?php $stateClass = $finished || $currentPos > 2 ? ' complete' : (
$maxProgress > 2 ? ' visited' : ( $maxProgress > 2 ? ' visited' : (
@ -62,7 +74,7 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
<?= $this->restartForm ?> <?= $this->restartForm ?>
<?php endif ?> <?php endif ?>
</div> </div>
<div class="step" style="width: 60%;"> <div class="step width-percent-60">
<h1><?= $this->translate('Configuration', 'setup.progress'); ?></h1> <h1><?= $this->translate('Configuration', 'setup.progress'); ?></h1>
<table><tbody><tr> <table><tbody><tr>
<td class="left"> <td class="left">
@ -76,14 +88,43 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
$pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '') $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '')
); ?> ); ?>
<?php if ($page === $firstPage): ?> <?php if ($page === $firstPage): ?>
<div class="line left<?= $stateClass; ?>" style="float: left; width: <?= sprintf( <?php
'%.2F', $setupStyle->add(
100 - (count($configPagesLeft) - 1) * $lineWidth '.step .left-line-' . $pos,
); ?>%; margin-right: 0"></div> [
'float' => 'left',
'width' => sprintf(
'%.2F%%',
100 - (count($configPagesLeft) - 1) * $lineWidth
),
'margin-right' => 0
]
);
?>
<div class="line left<?= $stateClass; ?> left-line-<?= $pos; ?>"></div>
<?php elseif ($page === $lastPage): ?> <?php elseif ($page === $lastPage): ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%; margin-right: -0.1em;"></div> <?php
$setupStyle->add(
'.step .left-line-' . $pos,
[
'float' => 'left',
'width' => $lineWidth . '%',
'margin-right' => '-0.1em'
]
);
?>
<div class="line<?= $stateClass; ?> left-line-<?= $pos; ?>"></div>
<?php else: ?> <?php else: ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%;"></div> <?php
$setupStyle->add(
'.step .left-line-' . $pos,
[
'float' => 'left',
'width' => $lineWidth . '%'
]
);
?>
<div class="line<?= $stateClass; ?> left-line-<?= $pos; ?>"></div>
<?php endif ?> <?php endif ?>
<?php endforeach ?> <?php endforeach ?>
</td> </td>
@ -106,14 +147,43 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
$pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '') $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '')
); ?> ); ?>
<?php if ($page === $firstPage): ?> <?php if ($page === $firstPage): ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%; margin-left: -0.1em;"></div> <?php
$setupStyle->add(
'.step .right-line-' . $pos,
[
'float' => 'left',
'width' => $lineWidth . '%',
'margin-right' => '-0.1em'
]
);
?>
<div class="line<?= $stateClass; ?> right-line-<?= $pos; ?>"></div>
<?php elseif ($page === $lastPage): ?> <?php elseif ($page === $lastPage): ?>
<div class="line right<?= $stateClass; ?>" style="float: left; width: <?= sprintf( <?php
'%.2F', $setupStyle->add(
100 - (count($configPagesRight) - 1) * $lineWidth '.step .right-line-' . $pos,
); ?>%; margin-left: 0;"></div> [
'float' => 'left',
'width' => sprintf(
'%.2F%%',
100 - (count($configPagesRight) - 1) * $lineWidth
),
'margin-right' => 0
]
);
?>
<div class="line right<?= $stateClass; ?> right-line-<?= $pos; ?>"></div>
<?php else: ?> <?php else: ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%;"></div> <?php
$setupStyle->add(
'.step .right-line-' . $pos,
[
'float' => 'left',
'width' => $lineWidth . '%'
]
);
?>
<div class="line<?= $stateClass; ?> right-line-<?= $pos; ?>"></div>
<?php endif ?> <?php endif ?>
<?php endforeach ?> <?php endforeach ?>
</td> </td>
@ -122,7 +192,7 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
<?= $this->restartForm ?> <?= $this->restartForm ?>
<?php endif ?> <?php endif ?>
</div> </div>
<div class="step" style="width: 10%;"> <div class="step width-percent-10">
<h1><?= $this->translate('Finish', 'setup.progress'); ?></h1> <h1><?= $this->translate('Finish', 'setup.progress'); ?></h1>
<?php $stateClass = $finished ? ' complete' : ($pages[$currentPos] === end($pages) ? ' active' : ''); ?> <?php $stateClass = $finished ? ' complete' : ($pages[$currentPos] === end($pages) ? ' active' : ''); ?>
<table><tbody><tr> <table><tbody><tr>
@ -151,3 +221,4 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
} }
?></ul> ?></ul>
</div> </div>
<?= $setupStyle; ?>

View File

@ -26,6 +26,7 @@ parameters:
ignoreErrors: ignoreErrors:
- '#Unsafe usage of new static\(\)#' - '#Unsafe usage of new static\(\)#'
- '#. but return statement is missing#' - '#. but return statement is missing#'
- '#Cannot call method importNode\(\) on DOMDocument\|null.#'
# ldap_connect() returns `LDAP\Connection` in php >= 81 # ldap_connect() returns `LDAP\Connection` in php >= 81
- -

View File

@ -48,3 +48,7 @@
.primary-nav a { .primary-nav a {
font-weight: 500; font-weight: 500;
} }
.remove-nav-item {
padding-left: 2em;
}

View File

@ -103,3 +103,7 @@
#layout.twocols .tabs > li > .close-container-control { #layout.twocols .tabs > li > .close-container-control {
display: block; display: block;
} }
.close-container-btn {
float: right;
}

View File

@ -208,6 +208,10 @@ table.multiselect tr[href] td {
} }
#main div.filter { #main div.filter {
.search.search-input {
width: 8em;
}
form.editor { form.editor {
input[type=text], select { input[type=text], select {
width: 12em; width: 12em;
@ -238,6 +242,20 @@ table.multiselect tr[href] td {
.buttons input:not(:last-child) { .buttons input:not(:last-child) {
margin-right:.5em; margin-right:.5em;
} }
ul.tree {
.filter-operator {
width: 5em;
}
.new-filter {
background: #ffb;
}
.filter-rule {
width: 4em;
}
}
} }
} }
@ -251,6 +269,10 @@ form.role-form {
font-style: normal; font-style: normal;
} }
.unrestricted-role {
text-decoration: line-through;
}
.control-label > * { .control-label > * {
display: inline-block; display: inline-block;
} }
@ -641,3 +663,4 @@ ul.tree li a.error:hover {
html.no-js .progress-label { html.no-js .progress-label {
display: none; display: none;
} }

28
public/js/bootstrap.js vendored Normal file
View File

@ -0,0 +1,28 @@
;(function () {
let html = document.documentElement;
window.name = html.dataset.icingaWindowName;
window.icinga = new Icinga({
baseUrl: html.dataset.icingaBaseUrl,
locale: html.lang,
timezone: html.dataset.icingaTimezone
});
if (! ('icingaIsIframe' in document.documentElement.dataset)) {
html.classList.replace('no-js', 'js');
}
if (window.getComputedStyle) {
let matched;
let element = document.getElementById('layout');
let name = window
.getComputedStyle(html)['font-family']
.replace(/['",]/g, '');
if (null !== (matched = name.match(/^([a-z]+)-layout$/))) {
element.classList.replace('default-layout', name);
if ('object' === typeof window.console) {
window.console.log('Icinga Web 2: setting initial layout to ' + name);
}
}
}
})();

View File

@ -28,7 +28,7 @@
var _this = e.data.self; var _this = e.data.self;
$('#layout').append( $('#layout').append(
'<div id="application-state" class="container" style="display: none" data-icinga-url="' '<div id="application-state" class="container" hidden data-icinga-url="'
+ _this.icinga.loader.baseUrl + _this.icinga.loader.baseUrl
+ '/application-state" data-icinga-refresh="60"></div>' + '/application-state" data-icinga-refresh="60"></div>'
); );

16
public/js/logout.js Normal file
View File

@ -0,0 +1,16 @@
;(function () {
/**
* When JavaScript is available, trigger an XmlHTTPRequest with the non-existing user 'logout' and abort it
* before it is able to finish. This will cause the browser to show a new authentication prompt in the next
* request.
*/
document.getElementById('logout-in-progress').hidden = true;
document.getElementById('logout-successful').hidden = false;
try {
var xhttp = new XMLHttpRequest();
xhttp.open('GET', 'arbitrary url', true, 'logout', 'logout');
xhttp.send('');
xhttp.abort();
} catch (e) {
}
})();

View File

@ -0,0 +1,29 @@
<?php
namespace Tests\Icinga\Lib;
use Icinga\Web\Session\Session;
class FakeSession extends Session
{
public function read()
{
}
public function exists()
{
}
public function purge()
{
}
public function refreshId()
{
}
public function getId()
{
return '1234567890';
}
}

View File

@ -120,8 +120,6 @@ class FormTest extends BaseTestCase
*/ */
public function testWhetherACsrfCounterMeasureIsBeingAdded() public function testWhetherACsrfCounterMeasureIsBeingAdded()
{ {
Mockery::mock('alias:Icinga\Web\Session')->shouldReceive('getSession->getId')->andReturn('1234567890');
$form = new Form(); $form = new Form();
$form->create(); $form->create();