mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-05 13:04:27 +02:00
Compare commits
No commits in common. "main" and "v2.12.2" have entirely different histories.
15
.github/workflows/L10n-update.yml
vendored
15
.github/workflows/L10n-update.yml
vendored
@ -6,6 +6,15 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update:
|
trigger-update:
|
||||||
uses: icinga/github-actions/.github/workflows/L10n-update.yml@main
|
name: L10n Update Trigger
|
||||||
secrets: inherit
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Repository dispatch
|
||||||
|
uses: peter-evans/repository-dispatch@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.ICINGABOT_TOKEN }}
|
||||||
|
repository: Icinga/L10n
|
||||||
|
event-type: update
|
||||||
|
client-payload: '{"origin": "${{ github.repository }}", "commit": "${{ github.sha }}"}'
|
||||||
|
4
.github/workflows/php.yml
vendored
4
.github/workflows/php.yml
vendored
@ -100,9 +100,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup dependencies
|
- name: Setup dependencies
|
||||||
run: |
|
run: |
|
||||||
composer init -n --require mockery/mockery:* --require ipl/i18n:@dev --require ipl/web:@dev
|
composer require -n --no-progress mockery/mockery ipl/i18n:@dev ipl/web:@dev
|
||||||
composer config platform.php 7.2.9
|
|
||||||
composer install -n --no-progress
|
|
||||||
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
|
||||||
|
41
CHANGELOG.md
41
CHANGELOG.md
@ -4,47 +4,6 @@ Please make sure to always read our [Upgrading](doc/80-Upgrading.md) documentati
|
|||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
### What's New in Version 2.12.4
|
|
||||||
|
|
||||||
This is a hotfix release which fixes the following issue:
|
|
||||||
|
|
||||||
Database login broken after upgrade [#5343](https://github.com/Icinga/icingaweb2/issues/5343)
|
|
||||||
|
|
||||||
### What's New in Version 2.12.3
|
|
||||||
|
|
||||||
**Notice:** This is a security release. It is recommended to upgrade _immediately_.
|
|
||||||
|
|
||||||
You can find all issues related to this release on our Roadmap.
|
|
||||||
|
|
||||||
#### Vulnerabilities, Closed
|
|
||||||
|
|
||||||
Cross site scripting is one of the worst attacks on web based platforms. Especially, if carrying it out is as easy as
|
|
||||||
the first two mentioned here. You might recognize the open redirect on the login. You are correct, we attempted to fix
|
|
||||||
it already with v2.11.3 but underestimated PHP's quirks. The last is difficult to exploit, hence the lowest severity
|
|
||||||
of all, but don't be fooled by that!
|
|
||||||
|
|
||||||
All four of them are backported to v2.11.5.
|
|
||||||
|
|
||||||
* XSS in embedded content [CVE-2025-27405](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-3x37-fjc3-ch8w)
|
|
||||||
* DOM-based XSS [CVE-2025-27404](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-c6pg-h955-wf66)
|
|
||||||
* Open redirect on login page [CVE-2025-30164](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-8r73-6686-wv8q)
|
|
||||||
* Reflected XSS [CVE-2025-27609](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-5cjw-fwjc-8j38)
|
|
||||||
|
|
||||||
Big thanks to all finders / reporters! :+1:
|
|
||||||
|
|
||||||
#### Bugs, Exterminated
|
|
||||||
|
|
||||||
Did you know, that we started [Icinga Notifications](https://icinga.com/docs/icinga-notifications/latest/) with support
|
|
||||||
for PostgreSQL first? Reason for that is, we wanted to make sure we are fully compatible with it right away. To ensure
|
|
||||||
things like logging in with a PostgreSQL authentication/group backend is case-insensitive, like it was always the case
|
|
||||||
for MySQL. Now it **really** is case-insensitive! There are also two issues fixed, which many of you will probably have
|
|
||||||
noticed since v2.12.2, sorry that it took so long :)
|
|
||||||
|
|
||||||
* Login against Postgres DB is case-sensitive [#5223](https://github.com/Icinga/icingaweb2/issues/5223)
|
|
||||||
* Role list has no functioning quick search [#5300](https://github.com/Icinga/icingaweb2/issues/5300)
|
|
||||||
* After clicking on Check now, the page does not refresh itself [#5293](https://github.com/Icinga/icingaweb2/issues/5293)
|
|
||||||
* Service States display wrong since update to 2.12.2 [#5290](https://github.com/Icinga/icingaweb2/issues/5290)
|
|
||||||
|
|
||||||
### What's New in Version 2.12.2
|
### What's New in Version 2.12.2
|
||||||
|
|
||||||
You can find all issues related to this release on our Roadmap.
|
You can find all issues related to this release on our Roadmap.
|
||||||
|
@ -3,108 +3,18 @@
|
|||||||
|
|
||||||
namespace Icinga\Controllers;
|
namespace Icinga\Controllers;
|
||||||
|
|
||||||
use Icinga\Web\Session;
|
use Icinga\Web\Controller;
|
||||||
use ipl\Html\BaseHtmlElement;
|
|
||||||
use ipl\Html\Html;
|
|
||||||
use ipl\Html\HtmlString;
|
|
||||||
use ipl\Html\Text;
|
|
||||||
use ipl\Web\Compat\CompatController;
|
|
||||||
use ipl\Web\Url;
|
|
||||||
use ipl\Web\Widget\Icon;
|
|
||||||
use ipl\Web\Widget\Tabs;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display external or internal links within an iframe
|
* Display external or internal links within an iframe
|
||||||
*/
|
*/
|
||||||
class IframeController extends CompatController
|
class IframeController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display iframe w/ the given URL
|
* Display iframe w/ the given URL
|
||||||
*/
|
*/
|
||||||
public function indexAction(): void
|
public function indexAction()
|
||||||
{
|
{
|
||||||
$url = Url::fromPath($this->params->getRequired('url'));
|
$this->view->url = $this->params->getRequired('url');
|
||||||
$urlHash = $this->getRequest()->getHeader('X-Icinga-URLHash');
|
|
||||||
$expectedHash = hash('sha256', $url->getAbsoluteUrl() . Session::getSession()->getId());
|
|
||||||
$iframeUrl = Url::fromPath('iframe', ['url' => $url->getAbsoluteUrl()]);
|
|
||||||
|
|
||||||
if (! in_array($url->getScheme(), ['http', 'https'], true)) {
|
|
||||||
$this->httpBadRequest('Invalid URL scheme');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->injectTabs();
|
|
||||||
|
|
||||||
$this->getTabs()->setRefreshUrl($iframeUrl);
|
|
||||||
|
|
||||||
if ($urlHash) {
|
|
||||||
if ($urlHash !== $expectedHash) {
|
|
||||||
$this->httpBadRequest('Invalid URL hash');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->addContent(Html::tag('div', ['class' => 'iframe-warning'], [
|
|
||||||
Html::tag('h2', $this->translate('Attention!')),
|
|
||||||
Html::tag('p', ['class' => 'note'], $this->translate(
|
|
||||||
'You are about to open untrusted content embedded in Icinga Web! Only proceed,'
|
|
||||||
.' by clicking the link below, if you recognize and trust the source!'
|
|
||||||
)),
|
|
||||||
Html::tag('a', ['data-url-hash' => $expectedHash, 'href' => Html::escape($iframeUrl)], $url),
|
|
||||||
Html::tag('p', ['class' => 'reason'], [
|
|
||||||
new Icon('circle-info'),
|
|
||||||
Text::create($this->translate(
|
|
||||||
'You see this warning because you do not seem to have followed a link in Icinga Web.'
|
|
||||||
. ' You can bypass this in the future by configuring a navigation item instead.'
|
|
||||||
))
|
|
||||||
])
|
|
||||||
]));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->getTabs()->setHash($expectedHash);
|
|
||||||
|
|
||||||
$this->addContent(Html::tag(
|
|
||||||
'div',
|
|
||||||
['class' => 'iframe-container'],
|
|
||||||
Html::tag('iframe', [
|
|
||||||
'src' => $url,
|
|
||||||
'sandbox' => 'allow-same-origin allow-scripts allow-popups allow-forms',
|
|
||||||
])
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function injectTabs(): void
|
|
||||||
{
|
|
||||||
$this->tabs = new class extends Tabs {
|
|
||||||
private $hash;
|
|
||||||
|
|
||||||
public function setHash($hash)
|
|
||||||
{
|
|
||||||
$this->hash = $hash;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function assemble()
|
|
||||||
{
|
|
||||||
$tabHtml = substr($this->tabs->render(), 34, -5);
|
|
||||||
if ($this->refreshUrl !== null) {
|
|
||||||
$tabHtml = preg_replace(
|
|
||||||
[
|
|
||||||
'/(?<=class="refresh-container-control spinner" href=")([^"]*)/',
|
|
||||||
'/(\s)(?=href)/'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
$this->refreshUrl->getAbsoluteUrl(),
|
|
||||||
' data-url-hash="' . $this->hash . '" '
|
|
||||||
],
|
|
||||||
$tabHtml
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseHtmlElement::add(HtmlString::create($tabHtml));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->controls->setTabs($this->tabs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,15 @@ use ipl\Web\Widget\StateBadge;
|
|||||||
</div>
|
</div>
|
||||||
<div class="about-social">
|
<div class="about-social">
|
||||||
<?= $this->qlink(
|
<?= $this->qlink(
|
||||||
|
null,
|
||||||
|
'https://www.twitter.com/icinga',
|
||||||
|
null,
|
||||||
|
array(
|
||||||
|
'target' => '_blank',
|
||||||
|
'icon' => 'twitter',
|
||||||
|
'title' => $this->translate('Icinga on Twitter')
|
||||||
|
)
|
||||||
|
) ?> <?= $this->qlink(
|
||||||
null,
|
null,
|
||||||
'https://www.facebook.com/icinga',
|
'https://www.facebook.com/icinga',
|
||||||
null,
|
null,
|
||||||
|
@ -28,6 +28,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul id="social">
|
<ul id="social">
|
||||||
|
<li>
|
||||||
|
<?= $this->qlink(
|
||||||
|
null,
|
||||||
|
'https://twitter.com/icinga',
|
||||||
|
null,
|
||||||
|
array(
|
||||||
|
'target' => '_blank',
|
||||||
|
'icon' => 'twitter',
|
||||||
|
'title' => $this->translate('Icinga on Twitter')
|
||||||
|
)
|
||||||
|
) ?>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<?= $this->qlink(
|
<?= $this->qlink(
|
||||||
null,
|
null,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<?= $this->tabs->render($this); ?>
|
<?= $this->tabs->render($this); ?>
|
||||||
<br/>
|
<br/>
|
||||||
<div>
|
<div>
|
||||||
<h1>Could not <?= $action; ?> module "<?= $this->escape($moduleName); ?>"</h1>
|
<h1>Could not <?= $action; ?> module "<?= $moduleName; ?>"</h1>
|
||||||
<p>
|
<p>
|
||||||
While operation the following error occurred:
|
While operation the following error occurred:
|
||||||
<br />
|
<br />
|
||||||
|
@ -23,7 +23,7 @@ $modReason = [];
|
|||||||
|
|
||||||
if (isset($requiredVendor, $requiredProject) && $requiredVendor && $requiredProject) {
|
if (isset($requiredVendor, $requiredProject) && $requiredVendor && $requiredProject) {
|
||||||
// TODO: I don't like this, can we define requirements somewhere else?
|
// TODO: I don't like this, can we define requirements somewhere else?
|
||||||
$coreDeps = ['icinga-php-library' => '>= 0.14.2', 'icinga-php-thirdparty' => '>= 0.12'];
|
$coreDeps = ['icinga-php-library' => '>= 0.13.2', 'icinga-php-thirdparty' => '>= 0.12'];
|
||||||
|
|
||||||
foreach ($coreDeps as $libraryName => $requiredVersion) {
|
foreach ($coreDeps as $libraryName => $requiredVersion) {
|
||||||
if (! $libraries->has($libraryName)) {
|
if (! $libraries->has($libraryName)) {
|
||||||
|
8
application/views/scripts/iframe/index.phtml
Normal file
8
application/views/scripts/iframe/index.phtml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php if (! $compact): ?>
|
||||||
|
<div class="controls">
|
||||||
|
<?= $tabs ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
<div class="iframe-container">
|
||||||
|
<iframe src="<?= $this->escape($url) ?>" frameborder="no"></iframe>
|
||||||
|
</div>
|
@ -8,7 +8,6 @@ $searchDashboard->setUser($this->Auth()->getUser());
|
|||||||
|
|
||||||
if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?>
|
if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?>
|
||||||
<form action="<?= $this->href('search') ?>" method="get" role="search" class="search-control">
|
<form action="<?= $this->href('search') ?>" method="get" role="search" class="search-control">
|
||||||
<i class="icon fa-search fa search-icon"></i>
|
|
||||||
<input type="text" name="q" id="search" class="search search-input" required
|
<input type="text" name="q" id="search" class="search search-input" required
|
||||||
placeholder="<?= $this->translate('Search') ?> …"
|
placeholder="<?= $this->translate('Search') ?> …"
|
||||||
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
||||||
|
@ -399,7 +399,7 @@ You will need to install certain dependencies depending on your setup:
|
|||||||
monitor your infrastructure
|
monitor your infrastructure
|
||||||
* A web server, e.g. Apache or Nginx
|
* A web server, e.g. Apache or Nginx
|
||||||
* PHP version ≥ 7.2
|
* PHP version ≥ 7.2
|
||||||
* [Icinga PHP Library (ipl)](https://github.com/Icinga/icinga-php-library) (≥ 0.14.2)
|
* [Icinga PHP Library (ipl)](https://github.com/Icinga/icinga-php-library) (≥ 0.13.2)
|
||||||
* [Icinga PHP Thirdparty](https://github.com/Icinga/icinga-php-thirdparty) (≥ 0.12)
|
* [Icinga PHP Thirdparty](https://github.com/Icinga/icinga-php-thirdparty) (≥ 0.12)
|
||||||
* The following PHP modules must be installed: cURL, json, gettext, fileinfo, intl, dom, OpenSSL and xml
|
* The following PHP modules must be installed: cURL, json, gettext, fileinfo, intl, dom, OpenSSL and xml
|
||||||
* The [pdfexport](https://github.com/Icinga/icingaweb2-module-pdfexport) module (≥0.10) is required for the
|
* The [pdfexport](https://github.com/Icinga/icingaweb2-module-pdfexport) module (≥0.10) is required for the
|
||||||
|
@ -3,16 +3,6 @@
|
|||||||
Specific version upgrades are described below. Please note that upgrades are incremental. An upgrade from
|
Specific version upgrades are described below. Please note that upgrades are incremental. An upgrade from
|
||||||
v2.6 to v2.8 requires to follow the instructions for v2.7 too.
|
v2.6 to v2.8 requires to follow the instructions for v2.7 too.
|
||||||
|
|
||||||
## Upgrading to Icinga Web 2.13
|
|
||||||
|
|
||||||
**Breaking changes**
|
|
||||||
|
|
||||||
* The following columns of the `Servicestatus` table, which previously displayed the date time (string) as a fetched value, now display the unix timestamp to support relative time filters:
|
|
||||||
* `service_last_time_ok`
|
|
||||||
* `service_last_time_unknown`
|
|
||||||
* `service_last_time_warning`
|
|
||||||
* `service_last_time_critical`
|
|
||||||
|
|
||||||
## Upgrading to Icinga Web 2.12.2
|
## Upgrading to Icinga Web 2.12.2
|
||||||
|
|
||||||
**Framework changes affecting third-party code**
|
**Framework changes affecting third-party code**
|
||||||
|
@ -58,8 +58,8 @@ Disabled by default.
|
|||||||
If you experience any problems while running SELinux in enforcing mode try to reproduce it in permissive mode. If the
|
If you experience any problems while running SELinux in enforcing mode try to reproduce it in permissive mode. If the
|
||||||
problem persists, it is not related to SELinux because in permissive mode SELinux will not deny anything.
|
problem persists, it is not related to SELinux because in permissive mode SELinux will not deny anything.
|
||||||
|
|
||||||
When filing a bug report please add the following information additionally to the common ones:
|
When filing a bug report please add the following information additionally to the
|
||||||
|
[common ones](https://icinga.com/icinga/faq/):
|
||||||
* Output of `semodule -l | grep -e icinga2 -e icingaweb2 -e nagios -e apache`
|
* Output of `semodule -l | grep -e icinga2 -e icingaweb2 -e nagios -e apache`
|
||||||
* Output of `semanage boolean -l | grep icinga`
|
* Output of `semanage boolean -l | grep icinga`
|
||||||
* Output of `ps -eZ | grep httpd`
|
* Output of `ps -eZ | grep httpd`
|
||||||
|
@ -568,7 +568,7 @@ abstract class ApplicationBootstrap
|
|||||||
*/
|
*/
|
||||||
protected function setupErrorHandling()
|
protected function setupErrorHandling()
|
||||||
{
|
{
|
||||||
error_reporting(getenv('ICINGAWEB_ENVIRONMENT') === 'dev' ? E_ALL : E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
|
error_reporting(E_ALL | E_STRICT);
|
||||||
ini_set('display_startup_errors', 1);
|
ini_set('display_startup_errors', 1);
|
||||||
ini_set('display_errors', 1);
|
ini_set('display_errors', 1);
|
||||||
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
||||||
@ -579,6 +579,7 @@ abstract class ApplicationBootstrap
|
|||||||
switch ($errno) {
|
switch ($errno) {
|
||||||
case E_NOTICE:
|
case E_NOTICE:
|
||||||
case E_WARNING:
|
case E_WARNING:
|
||||||
|
case E_STRICT:
|
||||||
case E_RECOVERABLE_ERROR:
|
case E_RECOVERABLE_ERROR:
|
||||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace Icinga\Application;
|
|||||||
*/
|
*/
|
||||||
class Version
|
class Version
|
||||||
{
|
{
|
||||||
const VERSION = '2.12.4';
|
const VERSION = '2.12.2';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the version of this instance of Icinga Web 2
|
* Get the version of this instance of Icinga Web 2
|
||||||
|
@ -8,7 +8,7 @@ use Icinga\Web\Controller\StaticController;
|
|||||||
use Icinga\Web\JavaScript;
|
use Icinga\Web\JavaScript;
|
||||||
use Icinga\Web\StyleSheet;
|
use Icinga\Web\StyleSheet;
|
||||||
|
|
||||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
|
error_reporting(E_ALL | E_STRICT);
|
||||||
|
|
||||||
if (isset($_SERVER['REQUEST_URI'])) {
|
if (isset($_SERVER['REQUEST_URI'])) {
|
||||||
$ruri = $_SERVER['REQUEST_URI'];
|
$ruri = $_SERVER['REQUEST_URI'];
|
||||||
|
@ -42,9 +42,4 @@ class RolesConfig extends IniRepository
|
|||||||
|
|
||||||
return $columns;
|
return $columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function initializeSearchColumns(): array
|
|
||||||
{
|
|
||||||
return ['name'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ use Icinga\Exception\AuthenticationException;
|
|||||||
use Icinga\Repository\DbRepository;
|
use Icinga\Repository\DbRepository;
|
||||||
use Icinga\User;
|
use Icinga\User;
|
||||||
use PDO;
|
use PDO;
|
||||||
use Zend_Db_Expr;
|
|
||||||
|
|
||||||
class DbUserBackend extends DbRepository implements UserBackendInterface, Inspectable
|
class DbUserBackend extends DbRepository implements UserBackendInterface, Inspectable
|
||||||
{
|
{
|
||||||
@ -180,28 +179,23 @@ class DbUserBackend extends DbRepository implements UserBackendInterface, Inspec
|
|||||||
{
|
{
|
||||||
if ($this->ds->getDbType() === 'pgsql') {
|
if ($this->ds->getDbType() === 'pgsql') {
|
||||||
// Since PostgreSQL version 9.0 the default value for bytea_output is 'hex' instead of 'escape'
|
// Since PostgreSQL version 9.0 the default value for bytea_output is 'hex' instead of 'escape'
|
||||||
$columns = ['password_hash' => new Zend_Db_Expr('ENCODE(password_hash, \'escape\')')];
|
$columns = array('password_hash' => 'ENCODE(password_hash, \'escape\')');
|
||||||
} else {
|
} else {
|
||||||
// password_hash is intentionally not a valid query column,
|
$columns = array('password_hash');
|
||||||
// by wrapping it in an expression it is not validated
|
|
||||||
$columns = ['password_hash' => new Zend_Db_Expr('password_hash')];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = $this
|
$nameColumn = 'name';
|
||||||
->select()
|
|
||||||
->from('user', $columns)
|
|
||||||
->where('active', true);
|
|
||||||
|
|
||||||
if ($this->ds->getDbType() === 'mysql') {
|
if ($this->ds->getDbType() === 'mysql') {
|
||||||
$username = strtolower($username);
|
$username = strtolower($username);
|
||||||
$nameColumn = new Zend_Db_Expr('BINARY LOWER(name)');
|
$nameColumn = 'BINARY LOWER(name)';
|
||||||
|
|
||||||
$query->getQuery()->where($nameColumn, $username);
|
|
||||||
} else { // pgsql
|
|
||||||
$query->where('user', $username);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$statement = $this->ds->getDbAdapter()->prepare($query->getQuery()->getSelectQuery());
|
$query = $this->ds->select()
|
||||||
|
->from($this->prependTablePrefix('user'), $columns)
|
||||||
|
->where($nameColumn, $username)
|
||||||
|
->where('active', true);
|
||||||
|
|
||||||
|
$statement = $this->ds->getDbAdapter()->prepare($query->getSelectQuery());
|
||||||
$statement->execute();
|
$statement->execute();
|
||||||
$statement->bindColumn(1, $lob, PDO::PARAM_LOB);
|
$statement->bindColumn(1, $lob, PDO::PARAM_LOB);
|
||||||
$statement->fetch(PDO::FETCH_BOUND);
|
$statement->fetch(PDO::FETCH_BOUND);
|
||||||
|
@ -204,7 +204,7 @@ class DbUserGroupBackend extends DbRepository implements Inspectable, UserGroupB
|
|||||||
$membershipQuery = $this
|
$membershipQuery = $this
|
||||||
->select()
|
->select()
|
||||||
->from('group_membership', array('group_name'))
|
->from('group_membership', array('group_name'))
|
||||||
->where('user', $user->getUsername());
|
->where('user_name', $user->getUsername());
|
||||||
|
|
||||||
$memberships = array();
|
$memberships = array();
|
||||||
foreach ($membershipQuery as $membership) {
|
foreach ($membershipQuery as $membership) {
|
||||||
|
@ -37,7 +37,7 @@ class Csv
|
|||||||
}
|
}
|
||||||
$out = array();
|
$out = array();
|
||||||
foreach ($row as & $val) {
|
foreach ($row as & $val) {
|
||||||
$out[] = '"' . ($val == '0' ? '0' : ($val ? str_replace('"', '""', $val) : '')) . '"';
|
$out[] = '"' . ($val ? str_replace('"', '""', $val) : '') . '"';
|
||||||
}
|
}
|
||||||
$csv .= implode(',', $out) . "\r\n";
|
$csv .= implode(',', $out) . "\r\n";
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,9 @@ namespace Icinga\File;
|
|||||||
|
|
||||||
use Dompdf\Dompdf;
|
use Dompdf\Dompdf;
|
||||||
use Dompdf\Options;
|
use Dompdf\Options;
|
||||||
use Exception;
|
|
||||||
use Icinga\Application\Icinga;
|
use Icinga\Application\Icinga;
|
||||||
use Icinga\Exception\ProgrammingError;
|
use Icinga\Exception\ProgrammingError;
|
||||||
use Icinga\Util\Environment;
|
use Icinga\Util\Environment;
|
||||||
use Icinga\Web\FileCache;
|
|
||||||
use Icinga\Web\Hook;
|
use Icinga\Web\Hook;
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
|
|
||||||
@ -66,17 +64,8 @@ class Pdf
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmpDir = FileCache::instance()->directory('legacy_pdf');
|
|
||||||
if ($tmpDir === false) {
|
|
||||||
throw new Exception('Could not create temporary directory for PDF rendering');
|
|
||||||
}
|
|
||||||
|
|
||||||
$options = new Options();
|
$options = new Options();
|
||||||
$options->set('defaultPaperSize', 'A4');
|
$options->set('defaultPaperSize', 'A4');
|
||||||
$options->set('fontDir', $tmpDir);
|
|
||||||
$options->set('fontCache', $tmpDir);
|
|
||||||
$options->set('tempDir', $tmpDir);
|
|
||||||
$options->set('chroot', $tmpDir);
|
|
||||||
$dompdf = new Dompdf($options);
|
$dompdf = new Dompdf($options);
|
||||||
$dompdf->loadHtml($html);
|
$dompdf->loadHtml($html);
|
||||||
$dompdf->render();
|
$dompdf->render();
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
namespace Icinga\Util;
|
namespace Icinga\Util;
|
||||||
|
|
||||||
use DateTimeZone;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve timezone information from cookie
|
* Retrieve timezone information from cookie
|
||||||
*/
|
*/
|
||||||
@ -17,6 +15,13 @@ class TimezoneDetect
|
|||||||
*/
|
*/
|
||||||
private static $success;
|
private static $success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timezone offset in minutes
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private static $offset = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
@ -29,6 +34,13 @@ class TimezoneDetect
|
|||||||
*/
|
*/
|
||||||
public static $cookieName = 'icingaweb2-tzo';
|
public static $cookieName = 'icingaweb2-tzo';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timezone name
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $timezone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new object and try to identify the timezone
|
* Create new object and try to identify the timezone
|
||||||
*/
|
*/
|
||||||
@ -38,13 +50,30 @@ class TimezoneDetect
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($_COOKIE[self::$cookieName] ?? null, DateTimeZone::listIdentifiers(), true)) {
|
if (array_key_exists(self::$cookieName, $_COOKIE)) {
|
||||||
self::$timezoneName = $_COOKIE[self::$cookieName];
|
$matches = array();
|
||||||
self::$success = true;
|
if (preg_match('/\A(-?\d+)[\-,](\d+)\z/', $_COOKIE[self::$cookieName], $matches)) {
|
||||||
} else {
|
$offset = $matches[1];
|
||||||
self::$success = false;
|
$timezoneName = timezone_name_from_abbr('', (int) $offset, (int) $matches[2]);
|
||||||
|
|
||||||
|
self::$success = (bool) $timezoneName;
|
||||||
|
if (self::$success) {
|
||||||
|
self::$offset = $offset;
|
||||||
|
self::$timezoneName = $timezoneName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get offset
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getOffset()
|
||||||
|
{
|
||||||
|
return self::$offset;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get timezone name
|
* Get timezone name
|
||||||
@ -73,5 +102,6 @@ class TimezoneDetect
|
|||||||
{
|
{
|
||||||
self::$success = null;
|
self::$success = null;
|
||||||
self::$timezoneName = null;
|
self::$timezoneName = null;
|
||||||
|
self::$offset = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ namespace Icinga\Web;
|
|||||||
use Icinga\Application\Logger;
|
use Icinga\Application\Logger;
|
||||||
use Icinga\Authentication\Auth;
|
use Icinga\Authentication\Auth;
|
||||||
use Icinga\Web\Navigation\Navigation;
|
use Icinga\Web\Navigation\Navigation;
|
||||||
use Icinga\Web\Navigation\Renderer\RecursiveMenuNavigationRenderer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main menu for Icinga Web 2
|
* Main menu for Icinga Web 2
|
||||||
@ -150,14 +149,4 @@ class Menu extends Navigation
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and return the renderer for this navigation
|
|
||||||
*
|
|
||||||
* @return RecursiveMenuNavigationRenderer
|
|
||||||
*/
|
|
||||||
public function getRenderer()
|
|
||||||
{
|
|
||||||
return new RecursiveMenuNavigationRenderer($this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ use Icinga\Application\Icinga;
|
|||||||
use Icinga\Exception\ProgrammingError;
|
use Icinga\Exception\ProgrammingError;
|
||||||
use Icinga\Util\StringHelper;
|
use Icinga\Util\StringHelper;
|
||||||
use Icinga\Web\Navigation\NavigationItem;
|
use Icinga\Web\Navigation\NavigationItem;
|
||||||
use Icinga\Web\Session;
|
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
use Icinga\Web\View;
|
use Icinga\Web\View;
|
||||||
|
|
||||||
@ -191,10 +190,6 @@ class NavigationItemRenderer
|
|||||||
|
|
||||||
$target = $item->getTarget();
|
$target = $item->getTarget();
|
||||||
if ($url->isExternal() && (!$target || in_array($target, $this->internalLinkTargets, true))) {
|
if ($url->isExternal() && (!$target || in_array($target, $this->internalLinkTargets, true))) {
|
||||||
$item->setAttribute('data-url-hash', hash(
|
|
||||||
'sha256',
|
|
||||||
$url->getAbsoluteUrl() . Session::getSession()->getId()
|
|
||||||
));
|
|
||||||
$url = Url::fromPath('iframe', array('url' => $url));
|
$url = Url::fromPath('iframe', array('url' => $url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2025 Icinga GmbH | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Web\Navigation\Renderer;
|
|
||||||
|
|
||||||
use Icinga\Web\Navigation\NavigationItem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renderer for the multi level navigation in the sidebar menu
|
|
||||||
*/
|
|
||||||
class RecursiveMenuNavigationRenderer extends RecursiveNavigationRenderer
|
|
||||||
{
|
|
||||||
public function beginChildren(): void
|
|
||||||
{
|
|
||||||
parent::beginChildren();
|
|
||||||
|
|
||||||
$parentItem = $this->getInnerIterator()->current()->getParent();
|
|
||||||
$item = new NavigationItem($parentItem->getName());
|
|
||||||
$item->setLabel($parentItem->getLabel());
|
|
||||||
$item->setCssClass('nav-item-header');
|
|
||||||
|
|
||||||
$renderer = new NavigationItemRenderer();
|
|
||||||
$renderer->setEscapeLabel(false);
|
|
||||||
$content = $renderer->render($item);
|
|
||||||
|
|
||||||
$this->content[] = $this->getInnerIterator()->beginItemMarkup($item);
|
|
||||||
$this->content[] = $content;
|
|
||||||
$this->content[] = $this->getInnerIterator()->endItemMarkup();
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,10 +4,11 @@
|
|||||||
namespace Icinga\Web\Navigation\Renderer;
|
namespace Icinga\Web\Navigation\Renderer;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
use Icinga\Exception\IcingaException;
|
use Icinga\Exception\IcingaException;
|
||||||
use Icinga\Web\Navigation\Navigation;
|
use Icinga\Web\Navigation\Navigation;
|
||||||
use Icinga\Web\Navigation\NavigationItem;
|
use Icinga\Web\Navigation\NavigationItem;
|
||||||
use RecursiveIteratorIterator;
|
use Icinga\Web\Navigation\Renderer\NavigationItemRenderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renderer for multi level navigation
|
* Renderer for multi level navigation
|
||||||
|
@ -179,9 +179,10 @@ class Url
|
|||||||
}
|
}
|
||||||
|
|
||||||
$urlParts = parse_url($url);
|
$urlParts = parse_url($url);
|
||||||
if ((isset($urlParts['scheme']) && $urlParts['scheme'] !== $request->getScheme())
|
if (isset($urlParts['scheme']) && (
|
||||||
|
$urlParts['scheme'] !== $request->getScheme()
|
||||||
|| (isset($urlParts['host']) && $urlParts['host'] !== $request->getServer('SERVER_NAME'))
|
|| (isset($urlParts['host']) && $urlParts['host'] !== $request->getServer('SERVER_NAME'))
|
||||||
|| (isset($urlParts['port']) && $urlParts['port'] != $request->getServer('SERVER_PORT'))
|
|| (isset($urlParts['port']) && $urlParts['port'] != $request->getServer('SERVER_PORT')))
|
||||||
) {
|
) {
|
||||||
$urlObject->setIsExternal();
|
$urlObject->setIsExternal();
|
||||||
}
|
}
|
||||||
|
@ -205,8 +205,7 @@ class View extends Zend_View_Abstract
|
|||||||
'th-thumb-empty' => true,
|
'th-thumb-empty' => true,
|
||||||
'github-circled' => true,
|
'github-circled' => true,
|
||||||
'history' => true,
|
'history' => true,
|
||||||
'binoculars' => true,
|
'binoculars' => true
|
||||||
'letter' => true
|
|
||||||
//</editor-fold>
|
//</editor-fold>
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
namespace Icinga\Web\Widget\Dashboard;
|
namespace Icinga\Web\Widget\Dashboard;
|
||||||
|
|
||||||
use Icinga\Web\Session;
|
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
use Icinga\Data\ConfigObject;
|
use Icinga\Data\ConfigObject;
|
||||||
|
use Icinga\Exception\IcingaException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dashboard pane dashlet
|
* A dashboard pane dashlet
|
||||||
@ -57,15 +57,18 @@ class Dashlet extends UserWidget
|
|||||||
*/
|
*/
|
||||||
private $template =<<<'EOD'
|
private $template =<<<'EOD'
|
||||||
|
|
||||||
<div class="container" data-icinga-url="{URL}" data-url-hash="{URL_HASH}">
|
<div class="container" data-icinga-url="{URL}">
|
||||||
<h1><a
|
<h1><a href="{FULL_URL}" aria-label="{TOOLTIP}" title="{TOOLTIP}" data-base-target="col1">{TITLE}</a></h1>
|
||||||
href="{FULL_URL}"
|
|
||||||
aria-label="{TOOLTIP}"
|
|
||||||
title="{TOOLTIP}"
|
|
||||||
data-url-hash="{FULL_URL_HASH}"
|
|
||||||
data-base-target="col1"
|
|
||||||
>{TITLE}</a></h1>
|
|
||||||
<p class="progress-label">{PROGRESS_LABEL}<span>.</span><span>.</span><span>.</span></p>
|
<p class="progress-label">{PROGRESS_LABEL}<span>.</span><span>.</span><span>.</span></p>
|
||||||
|
<noscript>
|
||||||
|
<div class="iframe-container">
|
||||||
|
<iframe
|
||||||
|
src="{IFRAME_URL}"
|
||||||
|
frameborder="no"
|
||||||
|
title="{TITLE_PREFIX}{TITLE}">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
</div>
|
</div>
|
||||||
EOD;
|
EOD;
|
||||||
|
|
||||||
@ -247,22 +250,13 @@ EOD;
|
|||||||
|
|
||||||
$url = $this->getUrl();
|
$url = $this->getUrl();
|
||||||
$url->setParam('showCompact', true);
|
$url->setParam('showCompact', true);
|
||||||
$fullUrl = $url->getUrlWithout(['showCompact', 'limit', 'view']);
|
$iframeUrl = clone $url;
|
||||||
|
$iframeUrl->setParam('isIframe');
|
||||||
$urlHash = '';
|
|
||||||
$fullUrlHash = '';
|
|
||||||
if ($url->getPath() === 'iframe') {
|
|
||||||
$urlHash = hash('sha256', Url::fromPath($url->getParam('url'))->getAbsoluteUrl()
|
|
||||||
. Session::getSession()->getId());
|
|
||||||
$fullUrlHash = hash('sha256', Url::fromPath($fullUrl->getParam('url'))->getAbsoluteUrl()
|
|
||||||
. Session::getSession()->getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
$searchTokens = array(
|
$searchTokens = array(
|
||||||
'{URL}',
|
'{URL}',
|
||||||
'{URL_HASH}',
|
'{IFRAME_URL}',
|
||||||
'{FULL_URL}',
|
'{FULL_URL}',
|
||||||
'{FULL_URL_HASH}',
|
|
||||||
'{TOOLTIP}',
|
'{TOOLTIP}',
|
||||||
'{TITLE}',
|
'{TITLE}',
|
||||||
'{TITLE_PREFIX}',
|
'{TITLE_PREFIX}',
|
||||||
@ -271,9 +265,8 @@ EOD;
|
|||||||
|
|
||||||
$replaceTokens = array(
|
$replaceTokens = array(
|
||||||
$url,
|
$url,
|
||||||
$urlHash,
|
$iframeUrl,
|
||||||
$fullUrl,
|
$url->getUrlWithout(['showCompact', 'limit', 'view']),
|
||||||
$fullUrlHash,
|
|
||||||
sprintf($view->translate('Show %s', 'dashboard.dashlet.tooltip'), $view->escape($this->getTitle())),
|
sprintf($view->translate('Show %s', 'dashboard.dashlet.tooltip'), $view->escape($this->getTitle())),
|
||||||
$view->escape($this->getTitle()),
|
$view->escape($this->getTitle()),
|
||||||
$view->translate('Dashlet') . ': ',
|
$view->translate('Dashlet') . ': ',
|
||||||
|
@ -3,17 +3,17 @@
|
|||||||
|
|
||||||
namespace Icinga\Web\Widget;
|
namespace Icinga\Web\Widget;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Icinga\Application\Icinga;
|
|
||||||
use Icinga\Data\Filter\Filter;
|
|
||||||
use Icinga\Data\Filter\FilterChain;
|
|
||||||
use Icinga\Data\Filter\FilterExpression;
|
|
||||||
use Icinga\Data\Filter\FilterOr;
|
|
||||||
use Icinga\Data\Filterable;
|
use Icinga\Data\Filterable;
|
||||||
use Icinga\Data\FilterColumns;
|
use Icinga\Data\FilterColumns;
|
||||||
|
use Icinga\Data\Filter\Filter;
|
||||||
|
use Icinga\Data\Filter\FilterExpression;
|
||||||
|
use Icinga\Data\Filter\FilterChain;
|
||||||
|
use Icinga\Data\Filter\FilterOr;
|
||||||
|
use Icinga\Web\Url;
|
||||||
|
use Icinga\Application\Icinga;
|
||||||
use Icinga\Exception\ProgrammingError;
|
use Icinga\Exception\ProgrammingError;
|
||||||
use Icinga\Web\Notification;
|
use Icinga\Web\Notification;
|
||||||
use Icinga\Web\Url;
|
use Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter
|
* Filter
|
||||||
@ -738,8 +738,7 @@ class FilterEditor extends AbstractWidget
|
|||||||
|
|
||||||
$html = ' <form method="post" class="search inline" action="'
|
$html = ' <form method="post" class="search inline" action="'
|
||||||
. $preservedUrl
|
. $preservedUrl
|
||||||
. '"><i class="icon fa-search fa search-icon"></i>'
|
. '"><input type="text" name="q" class="search search-input" value="" placeholder="'
|
||||||
. '<input type="text" name="q" class="search search-input" value="" placeholder="'
|
|
||||||
. t('Search...')
|
. t('Search...')
|
||||||
. '" /></form>';
|
. '" /></form>';
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ use ipl\Html\FormElement\InputElement;
|
|||||||
use ipl\Html\HtmlElement;
|
use ipl\Html\HtmlElement;
|
||||||
use ipl\Web\Control\SearchBar\Suggestions;
|
use ipl\Web\Control\SearchBar\Suggestions;
|
||||||
use ipl\Web\Url;
|
use ipl\Web\Url;
|
||||||
use ipl\Web\Widget\Icon;
|
|
||||||
|
|
||||||
class SingleValueSearchControl extends Form
|
class SingleValueSearchControl extends Form
|
||||||
{
|
{
|
||||||
@ -107,8 +106,6 @@ class SingleValueSearchControl extends Form
|
|||||||
{
|
{
|
||||||
$suggestionsId = Icinga::app()->getRequest()->protectId('single-value-suggestions');
|
$suggestionsId = Icinga::app()->getRequest()->protectId('single-value-suggestions');
|
||||||
|
|
||||||
$this->addHtml(new Icon('search', Attributes::create(['class' => 'search-icon'])));
|
|
||||||
|
|
||||||
$this->addElement(
|
$this->addElement(
|
||||||
'text',
|
'text',
|
||||||
$this->searchParameter,
|
$this->searchParameter,
|
||||||
|
@ -112,7 +112,7 @@ class Window
|
|||||||
{
|
{
|
||||||
if (! isset(static::$window)) {
|
if (! isset(static::$window)) {
|
||||||
$id = Icinga::app()->getRequest()->getHeader('X-Icinga-WindowId');
|
$id = Icinga::app()->getRequest()->getHeader('X-Icinga-WindowId');
|
||||||
if (empty($id) || $id === static::UNDEFINED || ! preg_match('/^\w+$/', $id)) {
|
if (empty($id) || $id === static::UNDEFINED) {
|
||||||
Icinga::app()->getResponse()->setOverrideWindowId();
|
Icinga::app()->getResponse()->setOverrideWindowId();
|
||||||
$id = static::generateId();
|
$id = static::generateId();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Module: doc
|
Module: doc
|
||||||
Version: 2.12.4
|
Version: 2.12.2
|
||||||
Description: Documentation module
|
Description: Documentation module
|
||||||
Extracts, shows and exports documentation for Icinga Web 2 and its modules.
|
Extracts, shows and exports documentation for Icinga Web 2 and its modules.
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
|
/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
// Mixins
|
||||||
|
|
||||||
|
.gradient(@a: @gray-lighter; @b: @gray-lightest) {
|
||||||
|
background: @a;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(@a), to(@b));
|
||||||
|
background: -webkit-linear-gradient(top, @a, @b);
|
||||||
|
background: -moz-linear-gradient(top, @a, @b);
|
||||||
|
background: -ms-linear-gradient(top, @a, @b);
|
||||||
|
background: -o-linear-gradient(top, @a, @b);
|
||||||
|
background: linear-gradient(to bottom, @a, @b);
|
||||||
|
}
|
||||||
|
|
||||||
// General styles
|
// General styles
|
||||||
|
|
||||||
code {
|
code {
|
||||||
@ -72,7 +84,7 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tbody > tr:nth-child(odd) {
|
tbody > tr:nth-child(odd) {
|
||||||
background: @gray-light;
|
.gradient()
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody > tr:nth-child(even) {
|
tbody > tr:nth-child(even) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Module: migrate
|
Module: migrate
|
||||||
Version: 2.12.4
|
Version: 2.12.2
|
||||||
Description: Migrate module
|
Description: Migrate module
|
||||||
This module was introduced with the domain-aware authentication feature in version 2.5.0.
|
This module was introduced with the domain-aware authentication feature in version 2.5.0.
|
||||||
It helps you migrating users and user configurations according to a given domain.
|
It helps you migrating users and user configurations according to a given domain.
|
||||||
|
@ -57,8 +57,7 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm
|
|||||||
. ' the host or service that is having problems. Make sure you enter a brief description of'
|
. ' the host or service that is having problems. Make sure you enter a brief description of'
|
||||||
. ' what you are doing.'
|
. ' what you are doing.'
|
||||||
),
|
),
|
||||||
'attribs' => array('class' => 'autofocus'),
|
'attribs' => array('class' => 'autofocus')
|
||||||
'value' => $config->get('settings', 'acknowledge_comment_text')
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
|
@ -227,7 +227,7 @@ class BackendConfigForm extends ConfigForm
|
|||||||
'autosubmit' => true
|
'autosubmit' => true
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$resourceName = $this->getView()->escape($formData['resource'] ?? $this->getValue('resource'));
|
$resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource');
|
||||||
$this->addElement(
|
$this->addElement(
|
||||||
'note',
|
'note',
|
||||||
'resource_note',
|
'resource_note',
|
||||||
|
@ -284,10 +284,10 @@ $section->add(N_('Timeline'), array(
|
|||||||
/*
|
/*
|
||||||
* Reporting Section
|
* Reporting Section
|
||||||
*/
|
*/
|
||||||
$section = $this->menuSection(N_('Reporting'), [
|
$section = $this->menuSection(N_('Reporting'), array(
|
||||||
'icon' => 'fa-chart-simple',
|
'icon' => 'barchart',
|
||||||
'priority' => 100
|
'priority' => 100
|
||||||
]);
|
));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Current Incidents
|
* Current Incidents
|
||||||
|
@ -21,7 +21,6 @@ by this module.
|
|||||||
|
|
||||||
Option | Description
|
Option | Description
|
||||||
----------------------------------|-----------------------------------------------
|
----------------------------------|-----------------------------------------------
|
||||||
acknowledge_comment_text | **Optional.** Set default text for "Comment" in Acknowledgement dialog by default.
|
|
||||||
acknowledge_expire | **Optional.** Check "Use Expire Time" in Acknowledgement dialog by default. Defaults to **0 (false)**.
|
acknowledge_expire | **Optional.** Check "Use Expire Time" in Acknowledgement dialog by default. Defaults to **0 (false)**.
|
||||||
acknowledge_expire_time | **Optional.** Set default value for "Expire Time" in Acknowledgement dialog, its calculated as now + this setting. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **1 hour (PT1H)**.
|
acknowledge_expire_time | **Optional.** Set default value for "Expire Time" in Acknowledgement dialog, its calculated as now + this setting. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **1 hour (PT1H)**.
|
||||||
acknowledge_notify | **Optional.** Check "Send Notification" in Acknowledgement dialog by default. Defaults to **1 (true)**.
|
acknowledge_notify | **Optional.** Check "Send Notification" in Acknowledgement dialog by default. Defaults to **1 (true)**.
|
||||||
|
@ -202,10 +202,10 @@ class ServicestatusQuery extends IdoQuery
|
|||||||
'service_last_notification' => 'UNIX_TIMESTAMP(ss.last_notification)',
|
'service_last_notification' => 'UNIX_TIMESTAMP(ss.last_notification)',
|
||||||
'service_last_state_change' => 'UNIX_TIMESTAMP(ss.last_state_change)',
|
'service_last_state_change' => 'UNIX_TIMESTAMP(ss.last_state_change)',
|
||||||
'service_last_state_change_ts' => 'ss.last_state_change',
|
'service_last_state_change_ts' => 'ss.last_state_change',
|
||||||
'service_last_time_critical' => 'UNIX_TIMESTAMP(ss.last_time_critical)',
|
'service_last_time_critical' => 'ss.last_time_critical',
|
||||||
'service_last_time_ok' => 'UNIX_TIMESTAMP(ss.last_time_ok)',
|
'service_last_time_ok' => 'ss.last_time_ok',
|
||||||
'service_last_time_unknown' => 'UNIX_TIMESTAMP(ss.last_time_unknown)',
|
'service_last_time_unknown' => 'ss.last_time_unknown',
|
||||||
'service_last_time_warning' => 'UNIX_TIMESTAMP(ss.last_time_warning)',
|
'service_last_time_warning' => 'ss.last_time_warning',
|
||||||
'service_long_output' => 'ss.long_output',
|
'service_long_output' => 'ss.long_output',
|
||||||
'service_max_check_attempts' => 'ss.max_check_attempts',
|
'service_max_check_attempts' => 'ss.max_check_attempts',
|
||||||
'service_modified_service_attributes' => 'ss.modified_service_attributes',
|
'service_modified_service_attributes' => 'ss.modified_service_attributes',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Module: monitoring
|
Module: monitoring
|
||||||
Version: 2.12.4
|
Version: 2.12.2
|
||||||
Description: Icinga monitoring module
|
Description: Icinga monitoring module
|
||||||
IDO accessor and UI for your monitoring. This is the initial instalment for a
|
IDO accessor and UI for your monitoring. This is the initial instalment for a
|
||||||
graphical presentation of Icinga environments. The predecessor of Icinga DB.
|
graphical presentation of Icinga environments. The predecessor of Icinga DB.
|
||||||
|
@ -102,11 +102,7 @@ class ConfigCommand extends Command
|
|||||||
*
|
*
|
||||||
* --enable-fpm Enable FPM handler for Apache (Nginx is always enabled)
|
* --enable-fpm Enable FPM handler for Apache (Nginx is always enabled)
|
||||||
*
|
*
|
||||||
* --fpm-url=<url> Address where to pass requests to FPM [127.0.0.1:9000]
|
* --fpm-uri=<uri> Address or path where to pass requests to FPM [127.0.0.1:9000]
|
||||||
*
|
|
||||||
* --fpm-uri=<uri> Alias for --fpm-url
|
|
||||||
*
|
|
||||||
* --fpm-socket-path=<socketpath> Socket path where to pass requests to FPM, overrides --fpm-url
|
|
||||||
*
|
*
|
||||||
* --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
|
* --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
|
||||||
*
|
*
|
||||||
@ -124,13 +120,9 @@ class ConfigCommand extends Command
|
|||||||
* icingacli setup config webserver apache \
|
* icingacli setup config webserver apache \
|
||||||
* --file=/etc/apache2/conf.d/icingaweb2.conf
|
* --file=/etc/apache2/conf.d/icingaweb2.conf
|
||||||
*
|
*
|
||||||
* icingacli setup config webserver apache \
|
|
||||||
* --file=/etc/apache2/conf.d/icingaweb2.conf
|
|
||||||
* --fpm-url=localhost:9000
|
|
||||||
*
|
|
||||||
* icingacli setup config webserver nginx \
|
* icingacli setup config webserver nginx \
|
||||||
* --root=/usr/share/icingaweb2/public \
|
* --root=/usr/share/icingaweb2/public \
|
||||||
* --fpm-socket-path=/var/run/php8.3-fpm.sock
|
* --fpm-uri=unix:/var/run/php5-fpm.sock
|
||||||
*/
|
*/
|
||||||
public function webserverAction()
|
public function webserverAction()
|
||||||
{
|
{
|
||||||
@ -165,18 +157,10 @@ class ConfigCommand extends Command
|
|||||||
|
|
||||||
$enableFpm = $this->params->shift('enable-fpm', $webserver->getEnableFpm());
|
$enableFpm = $this->params->shift('enable-fpm', $webserver->getEnableFpm());
|
||||||
|
|
||||||
$fpmSocketPath = trim($this->params->get('fpm-socket-path', $webserver->getFpmSocketPath()));
|
$fpmUri = trim($this->params->get('fpm-uri', $webserver->getFpmUri()));
|
||||||
$fpmUrl = trim($this->params->get('fpm-url', $webserver->getFpmUrl()));
|
if (empty($fpmUri)) {
|
||||||
if (empty($fpmUrl)) {
|
|
||||||
$fpmUrl = trim($this->params->get('fpm-uri', $webserver->getFpmUrl()));
|
|
||||||
}
|
|
||||||
if (empty($fpmSocketPath) && empty($fpmUrl)) {
|
|
||||||
$this->fail($this->translate(
|
$this->fail($this->translate(
|
||||||
'One of the arguments --fpm-socket-path or --fpm-url must be set to pass requests to FPM'
|
'The argument --fpm-uri expects an address or path where to pass requests to FPM'
|
||||||
));
|
|
||||||
} elseif (!empty($fpmSocketPath) && !empty($fpmUrl)) {
|
|
||||||
$this->fail($this->translate(
|
|
||||||
'Only one of the arguments --fpm-socket-path or --fpm-url must be set to pass requests to FPM'
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
$webserver
|
$webserver
|
||||||
@ -184,8 +168,7 @@ class ConfigCommand extends Command
|
|||||||
->setConfigDir($configDir)
|
->setConfigDir($configDir)
|
||||||
->setUrlPath($urlPath)
|
->setUrlPath($urlPath)
|
||||||
->setEnableFpm($enableFpm)
|
->setEnableFpm($enableFpm)
|
||||||
->setFpmUrl($fpmUrl)
|
->setFpmUri($fpmUri);
|
||||||
->setFpmSocketPath($fpmSocketPath);
|
|
||||||
$config = $webserver->generate() . "\n";
|
$config = $webserver->generate() . "\n";
|
||||||
if (($file = $this->params->get('file')) !== null) {
|
if (($file = $this->params->get('file')) !== null) {
|
||||||
if (file_exists($file) === true) {
|
if (file_exists($file) === true) {
|
||||||
|
@ -602,7 +602,7 @@ class WebWizard extends Wizard implements SetupWizard
|
|||||||
)));
|
)));
|
||||||
|
|
||||||
$set->add(new WebLibraryRequirement(array(
|
$set->add(new WebLibraryRequirement(array(
|
||||||
'condition' => ['icinga-php-library', '>=', '0.14.2'],
|
'condition' => ['icinga-php-library', '>=', '0.13.2'],
|
||||||
'alias' => 'Icinga PHP library',
|
'alias' => 'Icinga PHP library',
|
||||||
'description' => mt(
|
'description' => mt(
|
||||||
'setup',
|
'setup',
|
||||||
|
@ -33,25 +33,11 @@ abstract class Webserver
|
|||||||
protected $configDir;
|
protected $configDir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Address where to pass requests to FPM
|
* Address or path where to pass requests to FPM
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $fpmUrl;
|
protected $fpmUri;
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket path where to pass requests to FPM
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $fpmSocketPath;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FPM socket connection schema
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $fpmSocketSchema = 'unix:';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable to pass requests to FPM
|
* Enable to pass requests to FPM
|
||||||
@ -86,7 +72,6 @@ abstract class Webserver
|
|||||||
public function generate()
|
public function generate()
|
||||||
{
|
{
|
||||||
$template = $this->getTemplate();
|
$template = $this->getTemplate();
|
||||||
$fpmUri = $this->createFpmUri();
|
|
||||||
|
|
||||||
$searchTokens = array(
|
$searchTokens = array(
|
||||||
'{urlPath}',
|
'{urlPath}',
|
||||||
@ -100,7 +85,7 @@ abstract class Webserver
|
|||||||
$this->getDocumentRoot(),
|
$this->getDocumentRoot(),
|
||||||
preg_match('~/$~', $this->getUrlPath()) ? $this->getDocumentRoot() . '/' : $this->getDocumentRoot(),
|
preg_match('~/$~', $this->getUrlPath()) ? $this->getDocumentRoot() . '/' : $this->getDocumentRoot(),
|
||||||
$this->getConfigDir(),
|
$this->getConfigDir(),
|
||||||
$fpmUri
|
$this->getFpmUri()
|
||||||
);
|
);
|
||||||
$template = str_replace($searchTokens, $replaceTokens, $template);
|
$template = str_replace($searchTokens, $replaceTokens, $template);
|
||||||
return $template;
|
return $template;
|
||||||
@ -113,13 +98,6 @@ abstract class Webserver
|
|||||||
*/
|
*/
|
||||||
abstract protected function getTemplate();
|
abstract protected function getTemplate();
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the connection string for the respective web server
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
abstract protected function createFpmUri();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the URL path of Icinga Web 2
|
* Set the URL path of Icinga Web 2
|
||||||
*
|
*
|
||||||
@ -230,47 +208,25 @@ abstract class Webserver
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the address where to pass requests to FPM
|
* Get the address or path where to pass requests to FPM
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getFpmUrl()
|
public function getFpmUri()
|
||||||
{
|
{
|
||||||
return $this->fpmUrl;
|
return $this->fpmUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the address where to pass requests to FPM
|
* Set the address or path where to pass requests to FPM
|
||||||
*
|
*
|
||||||
* @param string $url
|
* @param string $uri
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setFpmUrl($url)
|
public function setFpmUri($uri)
|
||||||
{
|
{
|
||||||
$this->fpmUrl = (string) $url;
|
$this->fpmUri = (string) $uri;
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the socket path where to pass requests to FPM
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getFpmSocketPath()
|
|
||||||
{
|
|
||||||
return $this->fpmSocketPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the socket path where to pass requests to FPM
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setFpmSocketPath($socketPath)
|
|
||||||
{
|
|
||||||
$this->fpmSocketPath = (string) $socketPath;
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -10,21 +10,7 @@ use Icinga\Module\Setup\Webserver;
|
|||||||
*/
|
*/
|
||||||
class Apache extends Webserver
|
class Apache extends Webserver
|
||||||
{
|
{
|
||||||
protected $fpmUrl = '127.0.0.1:9000';
|
protected $fpmUri = '127.0.0.1:9000';
|
||||||
|
|
||||||
protected $fpmUrlSchema = 'fcgi://';
|
|
||||||
|
|
||||||
protected function createFpmUri()
|
|
||||||
{
|
|
||||||
$apacheFpmUri = "";
|
|
||||||
if (empty($this->fpmSocketPath)) {
|
|
||||||
$apacheFpmUri = $this->fpmUrlSchema . $this->fpmUrl;
|
|
||||||
} else {
|
|
||||||
$apacheFpmUri = $this->fpmSocketSchema . $this->fpmSocketPath . '|' . $this->fpmUrlSchema . 'localhost';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $apacheFpmUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getTemplate()
|
protected function getTemplate()
|
||||||
{
|
{
|
||||||
@ -37,7 +23,7 @@ Alias {urlPath} "{aliasDocumentRoot}"
|
|||||||
# # Forward PHP requests to FPM
|
# # Forward PHP requests to FPM
|
||||||
# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||||
# <LocationMatch "^{urlPath}/(.*\.php)$">
|
# <LocationMatch "^{urlPath}/(.*\.php)$">
|
||||||
# ProxyPassMatch "{fpmUri}/{documentRoot}/$1"
|
# ProxyPassMatch "fcgi://{fpmUri}/{documentRoot}/$1"
|
||||||
# </LocationMatch>
|
# </LocationMatch>
|
||||||
#</IfVersion>
|
#</IfVersion>
|
||||||
|
|
||||||
@ -85,7 +71,7 @@ Alias {urlPath} "{aliasDocumentRoot}"
|
|||||||
# # Forward PHP requests to FPM
|
# # Forward PHP requests to FPM
|
||||||
# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||||
# <FilesMatch "\.php$">
|
# <FilesMatch "\.php$">
|
||||||
# SetHandler "proxy:{fpmUri}"
|
# SetHandler "proxy:fcgi://{fpmUri}"
|
||||||
# ErrorDocument 503 {urlPath}/error_unavailable.html
|
# ErrorDocument 503 {urlPath}/error_unavailable.html
|
||||||
# </FilesMatch>
|
# </FilesMatch>
|
||||||
# </IfVersion>
|
# </IfVersion>
|
||||||
@ -99,7 +85,7 @@ Alias {urlPath} "{aliasDocumentRoot}"
|
|||||||
# Forward PHP requests to FPM
|
# Forward PHP requests to FPM
|
||||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||||
<LocationMatch "^{urlPath}/(.*\.php)$">
|
<LocationMatch "^{urlPath}/(.*\.php)$">
|
||||||
ProxyPassMatch "{fpmUri}/{documentRoot}/$1"
|
ProxyPassMatch "fcgi://{fpmUri}/{documentRoot}/$1"
|
||||||
</LocationMatch>
|
</LocationMatch>
|
||||||
</IfVersion>
|
</IfVersion>
|
||||||
|
|
||||||
@ -145,7 +131,7 @@ Alias {urlPath} "{aliasDocumentRoot}"
|
|||||||
# Forward PHP requests to FPM
|
# Forward PHP requests to FPM
|
||||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||||
<FilesMatch "\.php$">
|
<FilesMatch "\.php$">
|
||||||
SetHandler "proxy:{fpmUri}"
|
SetHandler "proxy:fcgi://{fpmUri}"
|
||||||
ErrorDocument 503 {urlPath}/error_unavailable.html
|
ErrorDocument 503 {urlPath}/error_unavailable.html
|
||||||
</FilesMatch>
|
</FilesMatch>
|
||||||
</IfVersion>
|
</IfVersion>
|
||||||
|
@ -10,15 +10,10 @@ use Icinga\Module\Setup\Webserver;
|
|||||||
*/
|
*/
|
||||||
class Nginx extends Webserver
|
class Nginx extends Webserver
|
||||||
{
|
{
|
||||||
protected $fpmUrl = '127.0.0.1:9000';
|
protected $fpmUri = '127.0.0.1:9000';
|
||||||
|
|
||||||
protected $enableFpm = true;
|
protected $enableFpm = true;
|
||||||
|
|
||||||
protected function createFpmUri()
|
|
||||||
{
|
|
||||||
return empty($this->fpmSocketPath) ? $this->fpmUrl : $this->fpmSocketSchema . $this->fpmSocketPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getTemplate()
|
protected function getTemplate()
|
||||||
{
|
{
|
||||||
return <<<'EOD'
|
return <<<'EOD'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Module: setup
|
Module: setup
|
||||||
Version: 2.12.4
|
Version: 2.12.2
|
||||||
Description: Setup module
|
Description: Setup module
|
||||||
Web based wizard for setting up Icinga Web 2 and its modules.
|
Web based wizard for setting up Icinga Web 2 and its modules.
|
||||||
This includes the data backends (e.g. relational database, LDAP),
|
This includes the data backends (e.g. relational database, LDAP),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Module: test
|
Module: test
|
||||||
Version: 2.12.4
|
Version: 2.12.2
|
||||||
Description: Translation module
|
Description: Translation module
|
||||||
This module allows developers to run (unit) tests against Icinga Web 2 and
|
This module allows developers to run (unit) tests against Icinga Web 2 and
|
||||||
any of its modules. Usually you do not need to enable this.
|
any of its modules. Usually you do not need to enable this.
|
||||||
|
@ -130,7 +130,7 @@ below.
|
|||||||

|

|
||||||
|
|
||||||
And when you want to test your changes, please read more about under the chapter
|
And when you want to test your changes, please read more about under the chapter
|
||||||
[Testing Translations](03-Translation.md#module-translation-tests).
|
[Testing Translations](Testing Translations).
|
||||||
|
|
||||||
## Testing Translations <a id="module-translation-tests"></a>
|
## Testing Translations <a id="module-translation-tests"></a>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Module: translation
|
Module: translation
|
||||||
Version: 2.12.4
|
Version: 2.12.2
|
||||||
Description: Translation module
|
Description: Translation module
|
||||||
This module allows developers and translators to translate modules for multiple
|
This module allows developers and translators to translate modules for multiple
|
||||||
languages. You do not need this module to run an internationalized web frontend.
|
languages. You do not need this module to run an internationalized web frontend.
|
||||||
|
@ -21,35 +21,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form > .search-icon {
|
|
||||||
position: absolute;
|
|
||||||
left: 0.25em;
|
|
||||||
top: ~"calc(50% - 0.5em)";
|
|
||||||
pointer-events: none;
|
|
||||||
color: @menu-color;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
form:has(> .search-icon) {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls input.search,
|
.controls input.search,
|
||||||
input.search {
|
input.search {
|
||||||
.transition(border 0.3s ease);
|
.transition(border 0.3s ease);
|
||||||
|
.transition(background-image 0.2s ease);
|
||||||
|
|
||||||
|
background-image: url(../img/icons/search_white.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 1em 1em;
|
||||||
|
background-position: .25em center;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
width: 20em;
|
width: 20em;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-image: url(../img/icons/search_icinga_blue.png);
|
||||||
|
}
|
||||||
|
|
||||||
&:focus:not([readonly]) {
|
&:focus:not([readonly]) {
|
||||||
border-color: @icinga-blue;
|
border-color: @icinga-blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form:has(input.search:focus) > .search-icon {
|
@light-mode: {
|
||||||
color: @icinga-blue;
|
#menu input.search,
|
||||||
}
|
.controls input.search,
|
||||||
|
input.search {
|
||||||
|
background-image: url(../img/icons/search.png);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
.backend-selection,
|
.backend-selection,
|
||||||
.pagination-control,
|
.pagination-control,
|
||||||
|
@ -303,7 +303,7 @@
|
|||||||
|
|
||||||
> .badge {
|
> .badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 1em;
|
right: .5em;
|
||||||
bottom: .25em;
|
bottom: .25em;
|
||||||
font-size: 75%;
|
font-size: 75%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -330,8 +330,7 @@
|
|||||||
padding-left: .75em;
|
padding-left: .75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-level-1 > .nav-item > a > i,
|
.nav-level-1 > .nav-item i {
|
||||||
.nav-level-1 > .nav-item > span > i {
|
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
margin-right: .5em;
|
margin-right: .5em;
|
||||||
}
|
}
|
||||||
|
@ -282,39 +282,6 @@ a:hover > .icon-cancel {
|
|||||||
|
|
||||||
// Responsive iFrames
|
// Responsive iFrames
|
||||||
|
|
||||||
.iframe-warning {
|
|
||||||
h2, p, a {
|
|
||||||
display: block;
|
|
||||||
width: fit-content;
|
|
||||||
font-size: 200%;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1000%;
|
|
||||||
color: @state-warning;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
|
||||||
background: @gray-lighter;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reason {
|
|
||||||
.icon {
|
|
||||||
color: @text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
font-size: 100%;
|
|
||||||
background: @gray-lightest;
|
|
||||||
color: @text-color-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.iframe-container {
|
.iframe-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 0;
|
height: 0;
|
||||||
@ -328,7 +295,6 @@ a:hover > .icon-cancel {
|
|||||||
top: 0;
|
top: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
#menu [class^="icon-"],
|
#menu [class^="icon-"],
|
||||||
#menu [class*=" icon-"] {
|
#menu [class*=" icon-"] {
|
||||||
&::before {
|
&:before {
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@nav-item-height: 3.166666667em; // 38px
|
|
||||||
@icon-width: 1.7em; // 1.5em width + 0.2em right margin
|
@icon-width: 1.7em; // 1.5em width + 0.2em right margin
|
||||||
|
|
||||||
#menu {
|
#menu {
|
||||||
@ -16,7 +15,6 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu .nav-item {
|
#menu .nav-item {
|
||||||
@ -35,11 +33,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#layout:not(.sidebar-collapsed) #menu .nav-item > a:first-of-type,
|
#layout:not(.sidebar-collapsed) #menu .nav-item > a:first-of-type {
|
||||||
#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-2 > .nav-item > a:first-of-type,
|
|
||||||
#layout.minimal-layout #menu .nav-level-1 > .nav-item > a:first-of-type {
|
|
||||||
// Respect overflowing content
|
// Respect overflowing content
|
||||||
.text-ellipsis();
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item {
|
#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item {
|
||||||
@ -53,6 +51,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#menu .nav-level-1 > .nav-item {
|
#menu .nav-level-1 > .nav-item {
|
||||||
|
line-height: 2.167em; // 26 px
|
||||||
color: @menu-color;
|
color: @menu-color;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
@ -70,9 +69,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> a {
|
> a {
|
||||||
// To center the content, padding top is: height - line-height / 2
|
padding: 0.5em 0.5em 0.5em .75em;
|
||||||
padding: ~"calc((@{nav-item-height} - 1.5em) / 2) .5em .5em .75em";
|
|
||||||
height: @nav-item-height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active:not(.selected) > a:focus,
|
&.active:not(.selected) > a:focus,
|
||||||
@ -90,7 +87,7 @@
|
|||||||
opacity: .8;
|
opacity: .8;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > a > .icon-letter::before {
|
& > a > .icon-letter:before {
|
||||||
content: attr(data-letter);
|
content: attr(data-letter);
|
||||||
font-family: @font-family;
|
font-family: @font-family;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
@ -98,10 +95,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item > a {
|
|
||||||
padding: ~"calc((@{nav-item-height} - 2em) / 2) .5em .5em .75em";
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu ul:not(.nav-level-2) > .selected > a {
|
#menu ul:not(.nav-level-2) > .selected > a {
|
||||||
background-color: @menu-highlight-color;
|
background-color: @menu-highlight-color;
|
||||||
color: @text-color-inverted;
|
color: @text-color-inverted;
|
||||||
@ -110,7 +103,7 @@
|
|||||||
background-color: @menu-highlight-hover-bg-color;
|
background-color: @menu-highlight-hover-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&:after {
|
||||||
.transform(rotate(45deg));
|
.transform(rotate(45deg));
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -121,9 +114,8 @@
|
|||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
height: 1.25em;
|
height: 1.25em;
|
||||||
|
margin-top: -1.75em;
|
||||||
width: 1.25em;
|
width: 1.25em;
|
||||||
top: ~"calc(50% - (1.25em / 2))";
|
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +123,6 @@
|
|||||||
// Collapse menu by default
|
// Collapse menu by default
|
||||||
display: none;
|
display: none;
|
||||||
line-height: 1.833em; // 22px
|
line-height: 1.833em; // 22px
|
||||||
z-index: 12;
|
|
||||||
|
|
||||||
> a {
|
> a {
|
||||||
color: @menu-2ndlvl-color;
|
color: @menu-2ndlvl-color;
|
||||||
@ -149,7 +140,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Little caret on active level-2 item
|
// Little caret on active level-2 item
|
||||||
&.active::after {
|
&.active:after {
|
||||||
.transform(rotate(45deg));
|
.transform(rotate(45deg));
|
||||||
|
|
||||||
background-color: @body-bg-color;
|
background-color: @body-bg-color;
|
||||||
@ -159,7 +150,7 @@
|
|||||||
height: 1.25em;
|
height: 1.25em;
|
||||||
width: 1.25em;
|
width: 1.25em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: ~"calc(50% - (1.25em / 2))";
|
top: .5em;
|
||||||
right: -.75em;
|
right: -.75em;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
@ -213,12 +204,9 @@
|
|||||||
opacity: .6;
|
opacity: .6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu .search-icon {
|
|
||||||
left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu input.search {
|
#menu input.search {
|
||||||
background-color: @menu-bg-color;
|
background: transparent url('../img/icons/search_white.png') no-repeat 1em center;
|
||||||
|
background-size: 1em auto;
|
||||||
border: none;
|
border: none;
|
||||||
color: @menu-color;
|
color: @menu-color;
|
||||||
line-height: 2.167em;
|
line-height: 2.167em;
|
||||||
@ -245,7 +233,7 @@
|
|||||||
|
|
||||||
// Badge offset correction
|
// Badge offset correction
|
||||||
#menu > nav > .nav-level-1 > .badge-nav-item > a > .badge {
|
#menu > nav > .nav-level-1 > .badge-nav-item > a > .badge {
|
||||||
margin-left: auto;
|
margin-top: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu .nav-level-2 > .badge-nav-item > a > .badge {
|
#menu .nav-level-2 > .badge-nav-item > a > .badge {
|
||||||
@ -253,10 +241,6 @@
|
|||||||
margin-right: .5em
|
margin-right: .5em
|
||||||
}
|
}
|
||||||
|
|
||||||
#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item:not(.active).hover > .nav-level-2 {
|
|
||||||
padding-top: @vertical-padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hovered menu
|
// Hovered menu
|
||||||
#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item.hover,
|
#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item.hover,
|
||||||
#layout:not(.minimal-layout) #menu .nav-level-1 > .nav-item:not(.active).hover {
|
#layout:not(.minimal-layout) #menu .nav-level-1 > .nav-item:not(.active).hover {
|
||||||
@ -266,15 +250,12 @@
|
|||||||
border-color: @gray-light;
|
border-color: @gray-light;
|
||||||
border-radius: .25em;
|
border-radius: .25em;
|
||||||
box-shadow: 0 0 1em 0 rgba(0,0,0,.3);
|
box-shadow: 0 0 1em 0 rgba(0,0,0,.3);
|
||||||
padding-bottom: @vertical-padding;
|
padding: @vertical-padding 0;
|
||||||
width: 14em;
|
width: 14em;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 11;
|
z-index: 1;
|
||||||
margin-top: -1px; // Align content with the menu item, not its border
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
--caretSide: 1.25em;
|
|
||||||
|
|
||||||
|
&:after {
|
||||||
.transform(rotate(45deg));
|
.transform(rotate(45deg));
|
||||||
|
|
||||||
background-color: @body-bg-color;
|
background-color: @body-bg-color;
|
||||||
@ -282,20 +263,17 @@
|
|||||||
border-left: 1px solid @gray-light;
|
border-left: 1px solid @gray-light;
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
height: var(--caretSide);
|
height: 1.1em;
|
||||||
width: var(--caretSide);
|
width: 1.1em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: ~"calc(@{nav-item-height} / 2 - var(--caretSide) / 2)";
|
top: 1em;
|
||||||
left: ~"calc(-1 * var(--caretSide) / 2 - 1px)";
|
left: -.6em;
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bottom-up {
|
&.bottom-up:after {
|
||||||
--caretY: 100%;
|
top: unset;
|
||||||
margin-top: 1px;
|
bottom: 1em;
|
||||||
|
|
||||||
&::after {
|
|
||||||
top: ~"calc(var(--caretY) - (@{nav-item-height} / 2) - (var(--caretSide) / 2))";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .nav-item {
|
> .nav-item {
|
||||||
@ -325,7 +303,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide activity caret when displayed as flyout
|
// Hide activity caret when displayed as flyout
|
||||||
&::after {
|
&:after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,13 +320,17 @@
|
|||||||
|
|
||||||
#layout:not(.minimal-layout) #menu .nav-level-1 > .nav-item:not(.active).hover {
|
#layout:not(.minimal-layout) #menu .nav-level-1 > .nav-item:not(.active).hover {
|
||||||
> .nav-level-2 {
|
> .nav-level-2 {
|
||||||
|
// Position relative to parent
|
||||||
margin-left: 16em;
|
margin-left: 16em;
|
||||||
|
margin-top: -3.167em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item.hover {
|
#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item.hover {
|
||||||
> .nav-level-2 {
|
> .nav-level-2 {
|
||||||
|
// Position relative to parent
|
||||||
margin-left: 4em;
|
margin-left: 4em;
|
||||||
|
margin-top: -3.333em;
|
||||||
|
|
||||||
> .badge-nav-item {
|
> .badge-nav-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -526,34 +508,12 @@ html.no-js #toggle-sidebar {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#open-sidebar::before,
|
#open-sidebar:before,
|
||||||
#close-sidebar::before {
|
#close-sidebar:before {
|
||||||
width: 1.4em;
|
width: 1.4em;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#layout.sidebar-collapsed #menu .nav-level-1 > .nav-item.hover .nav-level-2 > .nav-item-header {
|
|
||||||
background-color: @menu-bg-color;
|
|
||||||
border-bottom: 1px solid @gray-light;
|
|
||||||
border-top-left-radius: .25em;
|
|
||||||
border-top-right-radius: .25em;
|
|
||||||
|
|
||||||
span {
|
|
||||||
padding-left: 1.375em;
|
|
||||||
padding-right: 0.545em;
|
|
||||||
height: @nav-item-height;
|
|
||||||
line-height: @nav-item-height;
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
font-weight: @font-weight-bold;
|
|
||||||
.text-ellipsis();
|
|
||||||
|
|
||||||
> .badge {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item.active .nav-level-2 > li {
|
#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item.active .nav-level-2 > li {
|
||||||
&.nav-item:not(.badge-nav-item) {
|
&.nav-item:not(.badge-nav-item) {
|
||||||
&:not(.selected):not(.active) a:hover,
|
&:not(.selected):not(.active) a:hover,
|
||||||
@ -565,10 +525,6 @@ html.no-js #toggle-sidebar {
|
|||||||
|
|
||||||
#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item.active .nav-level-2 > li,
|
#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item.active .nav-level-2 > li,
|
||||||
#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item:not(.active).hover .nav-level-2 > li {
|
#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item:not(.active).hover .nav-level-2 > li {
|
||||||
&.nav-item-header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.badge-nav-item {
|
&.badge-nav-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
border-top: none;
|
border-top: none;
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
min-width: 14em;
|
min-width: 14em;
|
||||||
z-index: 1001;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs > .dropdown-nav-item > ul > li:hover > a {
|
.tabs > .dropdown-nav-item > ul > li:hover > a {
|
||||||
|
@ -83,9 +83,7 @@
|
|||||||
if ($container[0].contains(origFocus)
|
if ($container[0].contains(origFocus)
|
||||||
&& origFocus.form
|
&& origFocus.form
|
||||||
&& ! origFocus.matches(
|
&& ! origFocus.matches(
|
||||||
'input[type=submit], input[type=reset], input[type=button]'
|
'input[type=submit], input[type=reset], input[type=button], .autofocus, .autosubmit:not(:hover)'
|
||||||
+ ', button[type=submit], button[type=reset], button[type=button]'
|
|
||||||
+ ', .autofocus, .autosubmit:not(:hover)'
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.icinga.logger.debug('Not changing content for ' + containerId + ' form has focus');
|
this.icinga.logger.debug('Not changing content for ' + containerId + ' form has focus');
|
||||||
|
@ -6,24 +6,15 @@
|
|||||||
|
|
||||||
Icinga.Behaviors = Icinga.Behaviors || {};
|
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||||
|
|
||||||
try {
|
|
||||||
var d3 = require("icinga/icinga-php-thirdparty/mbostock/d3");
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('D3.js library is unavailable. Navigation to flyout may not work as expected.');
|
|
||||||
}
|
|
||||||
|
|
||||||
var Navigation = function (icinga) {
|
var Navigation = function (icinga) {
|
||||||
Icinga.EventListener.call(this, icinga);
|
Icinga.EventListener.call(this, icinga);
|
||||||
this.on('click', '#menu a', this.linkClicked, this);
|
this.on('click', '#menu a', this.linkClicked, this);
|
||||||
this.on('click', '#menu tr[href]', this.linkClicked, this);
|
this.on('click', '#menu tr[href]', this.linkClicked, this);
|
||||||
this.on('rendered', '#menu', this.onRendered, this);
|
this.on('rendered', '#menu', this.onRendered, this);
|
||||||
if (typeof d3 !== "undefined") {
|
this.on('mouseenter', '#menu .primary-nav .nav-level-1 > .nav-item', this.showFlyoutMenu, this);
|
||||||
this.on('mousemove', '#menu .primary-nav .nav-level-1 > .nav-item', this.onMouseMove, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.on('mouseenter', '#menu .primary-nav .nav-level-1 > .nav-item', this.onMouseEnter, this);
|
|
||||||
this.on('mouseleave', '#menu .primary-nav', this.hideFlyoutMenu, this);
|
this.on('mouseleave', '#menu .primary-nav', this.hideFlyoutMenu, this);
|
||||||
this.on('click', '#toggle-sidebar', this.toggleSidebar, this);
|
this.on('click', '#toggle-sidebar', this.toggleSidebar, this);
|
||||||
|
|
||||||
this.on('click', '#menu .config-nav-item button', this.toggleConfigFlyout, this);
|
this.on('click', '#menu .config-nav-item button', this.toggleConfigFlyout, this);
|
||||||
this.on('mouseenter', '#menu .config-menu .config-nav-item', this.showConfigFlyout, this);
|
this.on('mouseenter', '#menu .config-menu .config-nav-item', this.showConfigFlyout, this);
|
||||||
this.on('mouseleave', '#menu .config-menu .config-nav-item', this.hideConfigFlyout, this);
|
this.on('mouseleave', '#menu .config-menu .config-nav-item', this.hideConfigFlyout, this);
|
||||||
@ -39,21 +30,6 @@
|
|||||||
*/
|
*/
|
||||||
this.active = null;
|
this.active = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the extended flyout zone, an area formed by the previous cursor position, and top-left
|
|
||||||
* and bottom-left flyout points.
|
|
||||||
*
|
|
||||||
* @type {Array}
|
|
||||||
*/
|
|
||||||
this.extendedFlyoutZone = new Array(3);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timer for managing the delay in showing a flyout on mouse movement.
|
|
||||||
*
|
|
||||||
* @type {null|number}
|
|
||||||
*/
|
|
||||||
this.flyoutTimer = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The menu
|
* The menu
|
||||||
*
|
*
|
||||||
@ -306,111 +282,61 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Captures the mouse enter events to the navigation item and show the flyout.
|
* Show the fly-out menu
|
||||||
*
|
*
|
||||||
* @param e
|
* @param e
|
||||||
*/
|
*/
|
||||||
Navigation.prototype.onMouseEnter = function(e) {
|
Navigation.prototype.showFlyoutMenu = function(e) {
|
||||||
const $layout = $('#layout');
|
var $layout = $('#layout');
|
||||||
const _this = e.data.self;
|
|
||||||
if ($layout.hasClass('minimal-layout')) {
|
if ($layout.hasClass('minimal-layout')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $target = $(this);
|
var $target = $(this);
|
||||||
|
var $flyout = $target.find('.nav-level-2');
|
||||||
|
|
||||||
if (
|
if (! $flyout.length) {
|
||||||
typeof d3 !== "undefined"
|
|
||||||
&& ! _this.extendedFlyoutZone.includes(undefined)
|
|
||||||
&& d3.polygonContains(_this.extendedFlyoutZone, [e.clientX, e.clientY])
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $target[0].matches(':has(.nav-level-2)')) {
|
|
||||||
$layout.removeClass('menu-hovered');
|
$layout.removeClass('menu-hovered');
|
||||||
$target.siblings().not($target).removeClass('hover');
|
$target.siblings().not($target).removeClass('hover');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var delay = 300;
|
||||||
|
|
||||||
|
if ($layout.hasClass('menu-hovered')) {
|
||||||
|
delay = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
try {
|
||||||
if (! $target.is(':hover')) {
|
if (! $target.is(':hover')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} catch(e) { /* Bypass because if IE8 */ }
|
||||||
|
|
||||||
$layout.addClass('menu-hovered');
|
$layout.addClass('menu-hovered');
|
||||||
_this.extendedFlyoutZone[0] = [e.clientX, e.clientY];
|
|
||||||
_this.showFlyoutMenu($target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures the mouse move events within the navigation item
|
|
||||||
* and show the flyout if needed.
|
|
||||||
*
|
|
||||||
* @param e
|
|
||||||
*/
|
|
||||||
Navigation.prototype.onMouseMove = function(e) {
|
|
||||||
const _this = e.data.self;
|
|
||||||
clearTimeout(_this.flyoutTimer);
|
|
||||||
|
|
||||||
const $target = $(this);
|
|
||||||
|
|
||||||
if (! $target[0].matches(':has(.nav-level-2)')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $target.hasClass('hover')) {
|
|
||||||
if (
|
|
||||||
! _this.extendedFlyoutZone.includes(undefined)
|
|
||||||
&& d3.polygonContains(_this.extendedFlyoutZone, [e.clientX, e.clientY])
|
|
||||||
) {
|
|
||||||
_this.flyoutTimer = setTimeout(function() {
|
|
||||||
_this.showFlyoutMenu($target);
|
|
||||||
}, 200);
|
|
||||||
} else {
|
|
||||||
// The extended flyout zone keeps shrinking when the mouse moves towards the target's flyout.
|
|
||||||
// Hence, if the mouse is moved and stopped over a new target, sometimes it could be slightly outside
|
|
||||||
// the extended flyout zone. This in turn will not trigger the flyoutTimer.
|
|
||||||
// Hence, the showFlyoutMenu should be manually called.
|
|
||||||
_this.showFlyoutMenu($target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_this.extendedFlyoutZone[0] = [e.clientX, e.clientY];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the fly-out menu for the given target navigation item
|
|
||||||
*
|
|
||||||
* @param $target
|
|
||||||
*/
|
|
||||||
Navigation.prototype.showFlyoutMenu = function($target) {
|
|
||||||
const $flyout = $target.find('.nav-level-2');
|
|
||||||
$target.siblings().not($target).removeClass('hover');
|
$target.siblings().not($target).removeClass('hover');
|
||||||
$target.addClass('hover');
|
$target.addClass('hover');
|
||||||
|
|
||||||
const targetRect = $target[0].getBoundingClientRect();
|
var targetHeight = $target.offset().top + $target.outerHeight();
|
||||||
const flyoutRect = $flyout[0].getBoundingClientRect();
|
$flyout.css({
|
||||||
|
bottom: 'auto',
|
||||||
|
top: targetHeight
|
||||||
|
});
|
||||||
|
|
||||||
const css = { "--caretY": "" };
|
var rect = $flyout[0].getBoundingClientRect();
|
||||||
if (targetRect.top + flyoutRect.height > window.innerHeight) {
|
|
||||||
css.top = targetRect.bottom - flyoutRect.height;
|
|
||||||
if (css.top < 10) {
|
|
||||||
css.top = 10;
|
|
||||||
// Not sure why -2, but it aligns the caret perfectly with the menu item
|
|
||||||
css["--caretY"] = `${targetRect.bottom - 10 - 2}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (rect.bottom > window.innerHeight) {
|
||||||
$flyout.addClass('bottom-up');
|
$flyout.addClass('bottom-up');
|
||||||
|
$flyout.css({
|
||||||
|
bottom: window.innerHeight - targetHeight,
|
||||||
|
top: 'auto'
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
$flyout.removeClass('bottom-up');
|
$flyout.removeClass('bottom-up');
|
||||||
css.top = targetRect.top;
|
|
||||||
}
|
}
|
||||||
|
}, delay);
|
||||||
$flyout.css(css);
|
|
||||||
|
|
||||||
this.extendedFlyoutZone[1] = [flyoutRect.left, css.top];
|
|
||||||
this.extendedFlyoutZone[2] = [flyoutRect.left, css.top + flyoutRect.height];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -422,8 +348,6 @@
|
|||||||
var $layout = $('#layout');
|
var $layout = $('#layout');
|
||||||
var $nav = $(e.currentTarget);
|
var $nav = $(e.currentTarget);
|
||||||
var $hovered = $nav.find('.nav-level-1 > .nav-item.hover');
|
var $hovered = $nav.find('.nav-level-1 > .nav-item.hover');
|
||||||
const _this = e.data.self;
|
|
||||||
_this.extendedFlyoutZone.fill(undefined);
|
|
||||||
|
|
||||||
if (! $hovered.length) {
|
if (! $hovered.length) {
|
||||||
$layout.removeClass('menu-hovered');
|
$layout.removeClass('menu-hovered');
|
||||||
|
@ -170,22 +170,8 @@
|
|||||||
var $dashlet = $(this);
|
var $dashlet = $(this);
|
||||||
var url = $dashlet.data('icingaUrl');
|
var url = $dashlet.data('icingaUrl');
|
||||||
if (typeof url !== 'undefined') {
|
if (typeof url !== 'undefined') {
|
||||||
const urlHash = this.dataset.urlHash;
|
|
||||||
if (urlHash) {
|
|
||||||
_this.icinga.loader.loadUrl(
|
|
||||||
url,
|
|
||||||
$dashlet,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
true,
|
|
||||||
undefined,
|
|
||||||
{ "X-Icinga-URLHash": urlHash }
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
_this.icinga.loader.loadUrl(url, $dashlet).autorefresh = true;
|
_this.icinga.loader.loadUrl(url, $dashlet).autorefresh = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -295,7 +281,6 @@
|
|||||||
var $eventTarget = $(event.target);
|
var $eventTarget = $(event.target);
|
||||||
var href = $a.attr('href');
|
var href = $a.attr('href');
|
||||||
var linkTarget = $a.attr('target');
|
var linkTarget = $a.attr('target');
|
||||||
const urlHash = this.dataset.urlHash;
|
|
||||||
var $target;
|
var $target;
|
||||||
var formerUrl;
|
var formerUrl;
|
||||||
|
|
||||||
@ -406,20 +391,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load link URL
|
// Load link URL
|
||||||
if (urlHash) {
|
|
||||||
icinga.loader.loadUrl(
|
|
||||||
href,
|
|
||||||
$target,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ "X-Icinga-URLHash": urlHash }
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
icinga.loader.loadUrl(href, $target);
|
icinga.loader.loadUrl(href, $target);
|
||||||
}
|
|
||||||
|
|
||||||
if ($a.closest('#menu').length > 0) {
|
if ($a.closest('#menu').length > 0) {
|
||||||
// Menu links should remove all but the first layout column
|
// Menu links should remove all but the first layout column
|
||||||
|
@ -242,10 +242,6 @@
|
|||||||
loadUrl: function (url, $target, data, method, action, autorefresh, progressTimer, extraHeaders) {
|
loadUrl: function (url, $target, data, method, action, autorefresh, progressTimer, extraHeaders) {
|
||||||
var id = null;
|
var id = null;
|
||||||
|
|
||||||
if (url.startsWith('//') || ! url.startsWith(this.baseUrl + '/')) {
|
|
||||||
throw new Error('URL ' + url + ' is not relative to ' + this.baseUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default method is GET
|
// Default method is GET
|
||||||
if ('undefined' === typeof method) {
|
if ('undefined' === typeof method) {
|
||||||
method = 'GET';
|
method = 'GET';
|
||||||
|
@ -4,6 +4,28 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum timezone offset
|
||||||
|
*
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
Date.prototype.getStdTimezoneOffset = function() {
|
||||||
|
var year = new Date().getFullYear();
|
||||||
|
var offsetInJanuary = new Date(year, 0, 2).getTimezoneOffset();
|
||||||
|
var offsetInJune = new Date(year, 5, 2).getTimezoneOffset();
|
||||||
|
|
||||||
|
return Math.max(offsetInJanuary, offsetInJune);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for daylight saving time zone
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
Date.prototype.isDst = function() {
|
||||||
|
return this.getStdTimezoneOffset() !== this.getTimezoneOffset();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write timezone information into a cookie
|
* Write timezone information into a cookie
|
||||||
*
|
*
|
||||||
@ -29,11 +51,15 @@
|
|||||||
* Write timezone information into cookie
|
* Write timezone information into cookie
|
||||||
*/
|
*/
|
||||||
writeTimezone: function() {
|
writeTimezone: function() {
|
||||||
|
var date = new Date();
|
||||||
|
var timezoneOffset = (date.getTimezoneOffset()*60) * -1;
|
||||||
|
var dst = date.isDst();
|
||||||
|
|
||||||
if (this.readCookie(this.cookieName)) {
|
if (this.readCookie(this.cookieName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.writeCookie(this.cookieName, Intl.DateTimeFormat().resolvedOptions().timeZone);
|
this.writeCookie(this.cookieName, timezoneOffset + '-' + Number(dst), 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,9 +67,17 @@
|
|||||||
*
|
*
|
||||||
* @param {String} name
|
* @param {String} name
|
||||||
* @param {String} value
|
* @param {String} value
|
||||||
|
* @param {Number} days
|
||||||
*/
|
*/
|
||||||
writeCookie: function(name, value) {
|
writeCookie: function(name, value, days) {
|
||||||
document.cookie = name + '=' + value + '; path=/';
|
var expires = '';
|
||||||
|
|
||||||
|
if (days) {
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(date.getTime()+(days*24*60*60*1000));
|
||||||
|
var expires = '; expires=' + date.toGMTString();
|
||||||
|
}
|
||||||
|
document.cookie = name + '=' + value + expires + '; path=/';
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,7 +21,7 @@ class LocalFileStorageTest extends BaseTestCase
|
|||||||
parent::__construct($name, $data, $dataName);
|
parent::__construct($name, $data, $dataName);
|
||||||
|
|
||||||
$this->oldErrorReportingLevel = error_reporting();
|
$this->oldErrorReportingLevel = error_reporting();
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL | E_STRICT);
|
||||||
|
|
||||||
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
||||||
if (error_reporting() === 0) {
|
if (error_reporting() === 0) {
|
||||||
@ -32,6 +32,7 @@ class LocalFileStorageTest extends BaseTestCase
|
|||||||
switch ($errno) {
|
switch ($errno) {
|
||||||
case E_NOTICE:
|
case E_NOTICE:
|
||||||
case E_WARNING:
|
case E_WARNING:
|
||||||
|
case E_STRICT:
|
||||||
case E_RECOVERABLE_ERROR:
|
case E_RECOVERABLE_ERROR:
|
||||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||||
}
|
}
|
||||||
|
@ -8,42 +8,38 @@ use Icinga\Util\TimezoneDetect;
|
|||||||
|
|
||||||
class TimezoneDetectTest extends BaseTestCase
|
class TimezoneDetectTest extends BaseTestCase
|
||||||
{
|
{
|
||||||
public function testInvalidTimezoneNameInCookie(): void
|
public function testPositiveTimezoneOffsetSeparatedByComma()
|
||||||
{
|
{
|
||||||
$tzDetect = new TimezoneDetect();
|
$this->assertTimezoneDetection('3600,0', 'Europe/Paris');
|
||||||
$tzDetect->reset();
|
|
||||||
|
|
||||||
$_COOKIE[TimezoneDetect::$cookieName] = 'ABC';
|
|
||||||
$tzDetect = new TimezoneDetect();
|
|
||||||
$this->assertFalse(
|
|
||||||
$tzDetect->success(),
|
|
||||||
false,
|
|
||||||
'Failed to assert invalid timezone name is detected'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertNull(
|
|
||||||
$tzDetect->getTimezoneName(),
|
|
||||||
'Failed to assert that the timezone name will not be set for invalid timezone'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testValidTimezoneNameInCookie(): void
|
public function testPositiveTimezoneOffsetSeparatedByHyphen()
|
||||||
|
{
|
||||||
|
$this->assertTimezoneDetection('3600-0', 'Europe/Paris');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNegativeTimezoneOffsetSeparatedByComma()
|
||||||
|
{
|
||||||
|
$this->assertTimezoneDetection('-3600,0', 'Atlantic/Azores');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNegativeTimezoneOffsetSeparatedByHyphen()
|
||||||
|
{
|
||||||
|
$this->assertTimezoneDetection('-3600-0', 'Atlantic/Azores');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertTimezoneDetection($cookieValue, $expectedTimezoneName)
|
||||||
{
|
{
|
||||||
$tzDetect = new TimezoneDetect();
|
$tzDetect = new TimezoneDetect();
|
||||||
$tzDetect->reset();
|
$tzDetect->reset();
|
||||||
|
|
||||||
$_COOKIE[TimezoneDetect::$cookieName] = "Europe/Berlin";
|
$_COOKIE[TimezoneDetect::$cookieName] = $cookieValue;
|
||||||
$tzDetect = new TimezoneDetect();
|
$tzDetect = new TimezoneDetect();
|
||||||
$this->assertTrue(
|
|
||||||
$tzDetect->success(),
|
|
||||||
true,
|
|
||||||
'Failed to assert that the valid timezone name is detected'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
$tzDetect->getTimezoneName(),
|
$tzDetect->getTimezoneName(),
|
||||||
"Europe/Berlin",
|
$expectedTimezoneName,
|
||||||
'Failed to assert that the valid timezone name was correctly set'
|
'Failed asserting that the timezone "' . $expectedTimezoneName
|
||||||
|
. '" is being detected from the cookie value "' . $cookieValue . '"'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,11 +71,6 @@ class UrlTest extends BaseTestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testWhetherProtocolRelativeUrlsAreDetectedAsBeingExternal()
|
|
||||||
{
|
|
||||||
$this->assertTrue(Url::fromPath('//testhost/path/to/my/url.html')->isExternal());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testWhetherGetAbsoluteUrlReturnsTheGivenUsernameAndPassword()
|
public function testWhetherGetAbsoluteUrlReturnsTheGivenUsernameAndPassword()
|
||||||
{
|
{
|
||||||
$url = Url::fromPath('http://testusername:testpassword@testsite.com/path/to/my/url.html');
|
$url = Url::fromPath('http://testusername:testpassword@testsite.com/path/to/my/url.html');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user