mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-11-13 01:19:57 +01: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 }}"}'
|
||||||
|
|||||||
14
.github/workflows/php.yml
vendored
14
.github/workflows/php.yml
vendored
@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- support/*
|
|
||||||
- release/*
|
- release/*
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
@ -18,7 +17,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php: ['8.2', '8.3', '8.4']
|
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
|
||||||
os: ['ubuntu-latest']
|
os: ['ubuntu-latest']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -47,13 +46,16 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
phpunit-version: 9.6
|
phpunit-version: 9.5
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php: ['8.2', '8.3', '8.4']
|
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
|
||||||
os: ['ubuntu-latest']
|
os: ['ubuntu-latest']
|
||||||
|
include:
|
||||||
|
- php: '7.2'
|
||||||
|
phpunit-version: 8.5
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
@ -98,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 8.2
|
|
||||||
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
|
||||||
|
|||||||
2
.mailmap
2
.mailmap
@ -17,7 +17,6 @@ Florian Strohmaier <florian.strohmaier@icinga.com> <hello@florianstrohmaier.com>
|
|||||||
Florian Strohmaier <florian.strohmaier@icinga.com> <florian.strohmaier@me.com>
|
Florian Strohmaier <florian.strohmaier@icinga.com> <florian.strohmaier@me.com>
|
||||||
Gunnar Beutner <gunnar.beutner@netways.de> <gunnar@beutner.name>
|
Gunnar Beutner <gunnar.beutner@netways.de> <gunnar@beutner.name>
|
||||||
Jannis Moßhammer <jannis.mosshammer@netways.de>
|
Jannis Moßhammer <jannis.mosshammer@netways.de>
|
||||||
Jan Schuppik <jan.schuppik@icinga.com> <114286749+Jan-Schuppik@users.noreply.github.com>
|
|
||||||
Johannes Meyer <johannes.meyer@icinga.com> <johannes.meyer@netways.de>
|
Johannes Meyer <johannes.meyer@icinga.com> <johannes.meyer@netways.de>
|
||||||
Jennifer Mourek <jennifer.mourek@icinga.com> <jennifer.mourek@netways.de>
|
Jennifer Mourek <jennifer.mourek@icinga.com> <jennifer.mourek@netways.de>
|
||||||
Marius Hein <marius.hein@netways.de> <mhein@itsocks.de>
|
Marius Hein <marius.hein@netways.de> <mhein@itsocks.de>
|
||||||
@ -43,6 +42,5 @@ Thomas Gelf <thomas.gelf@icinga.com> <thomas@gelf.net>
|
|||||||
Tobias Bauriedel <tobias.bauriedel@netways.de> <tobias@bauriedel.de>
|
Tobias Bauriedel <tobias.bauriedel@netways.de> <tobias@bauriedel.de>
|
||||||
Yonas Habteab <yonas.habteab@icinga.com> <yonas.habteab@netways.de>
|
Yonas Habteab <yonas.habteab@icinga.com> <yonas.habteab@netways.de>
|
||||||
Ravi Kumar Kempapura Srinivasa <ravi.srinivasa@icinga.com> <33730024+raviks789@users.noreply.github.com>
|
Ravi Kumar Kempapura Srinivasa <ravi.srinivasa@icinga.com> <33730024+raviks789@users.noreply.github.com>
|
||||||
Ravi Kumar Kempapura Srinivasa <ravi.srinivasa@icinga.com> <raviks789@gmail.com>
|
|
||||||
Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com> <54990055+sukhwinder33445@users.noreply.github.com>
|
Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com> <54990055+sukhwinder33445@users.noreply.github.com>
|
||||||
Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com> <sukhwinder33445@gmail.com>
|
Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com> <sukhwinder33445@gmail.com>
|
||||||
|
|||||||
4
AUTHORS
4
AUTHORS
@ -54,12 +54,10 @@ Ian Shearin <ishearin@womply.com>
|
|||||||
ignasr <ignas.linux@gmail.com>
|
ignasr <ignas.linux@gmail.com>
|
||||||
Janne Heß <janne@hess.ooo>
|
Janne Heß <janne@hess.ooo>
|
||||||
Jannis Moßhammer <jannis.mosshammer@netways.de>
|
Jannis Moßhammer <jannis.mosshammer@netways.de>
|
||||||
Jan Schuppik <jan.schuppik@icinga.com>
|
|
||||||
Jennifer Mourek <jennifer.mourek@icinga.com>
|
Jennifer Mourek <jennifer.mourek@icinga.com>
|
||||||
Jiri Pejchal <jiri.pejchal@gmail.com>
|
Jiri Pejchal <jiri.pejchal@gmail.com>
|
||||||
Joe Doherty <git@pjuu.com>
|
Joe Doherty <git@pjuu.com>
|
||||||
Johannes Meyer <johannes.meyer@icinga.com>
|
Johannes Meyer <johannes.meyer@icinga.com>
|
||||||
Johannes Rauh <johannes.rauh@icinga.com>
|
|
||||||
Joonas Kylmälä <joonas.kylmala@kirjastot.fi>
|
Joonas Kylmälä <joonas.kylmala@kirjastot.fi>
|
||||||
Jorge Vallecillo <jorgevallecilloc@gmail.com>
|
Jorge Vallecillo <jorgevallecilloc@gmail.com>
|
||||||
Jo Rhett <jo@chegg.com>
|
Jo Rhett <jo@chegg.com>
|
||||||
@ -75,7 +73,6 @@ Marc DeTrano <marc@gridshield.net>
|
|||||||
Marcel Weinberg <marcel.weinberg@secucloud.com>
|
Marcel Weinberg <marcel.weinberg@secucloud.com>
|
||||||
Marcus Cobden <marcus@marcuscobden.co.uk>
|
Marcus Cobden <marcus@marcuscobden.co.uk>
|
||||||
Marian Rainer-Harbach <marian@rainer-harbach.at>
|
Marian Rainer-Harbach <marian@rainer-harbach.at>
|
||||||
marianrh <19990392+marianrh@users.noreply.github.com>
|
|
||||||
Mario Rimann <mario@rimann.org>
|
Mario Rimann <mario@rimann.org>
|
||||||
Marius Hein <marius.hein@netways.de>
|
Marius Hein <marius.hein@netways.de>
|
||||||
Markus Frosch <markus.frosch@icinga.com>
|
Markus Frosch <markus.frosch@icinga.com>
|
||||||
@ -128,7 +125,6 @@ Rune Darrud <theflyingcorpse@gmail.com>
|
|||||||
Russell Kubik <russkubik@3d-p.com>
|
Russell Kubik <russkubik@3d-p.com>
|
||||||
Sander Ferdinand <sa.ferdinand@gmail.com>
|
Sander Ferdinand <sa.ferdinand@gmail.com>
|
||||||
sant-swedge <simon.wedge@sant.ox.ac.uk>
|
sant-swedge <simon.wedge@sant.ox.ac.uk>
|
||||||
Silas <67681686+Tqnsls@users.noreply.github.com>
|
|
||||||
Simone Orsi <simahawk@users.noreply.github.com>
|
Simone Orsi <simahawk@users.noreply.github.com>
|
||||||
ss23 <stephen@zxsecurity.co.nz>
|
ss23 <stephen@zxsecurity.co.nz>
|
||||||
Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com>
|
Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com>
|
||||||
|
|||||||
75
CHANGELOG.md
75
CHANGELOG.md
@ -4,80 +4,9 @@ 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.5
|
|
||||||
|
|
||||||
You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/85?closed=1).
|
|
||||||
|
|
||||||
#### PHP 8.4 Support
|
|
||||||
|
|
||||||
We're again a little behind schedule, but now we support PHP 8.4! This means that installations on Ubuntu 25.04 and
|
|
||||||
Fedora 42+ can now install Icinga Web without worrying about PHP related incompatibilities. Icinga packages will be
|
|
||||||
available in the next few days.
|
|
||||||
|
|
||||||
#### Good Things Take Time
|
|
||||||
|
|
||||||
There's only a single (notable) recent issue that is fixed with this release. All the others are a bit older.
|
|
||||||
|
|
||||||
* External URLs set up as dashlets are not *embedded* the same as navigation items [#5346](https://github.com/Icinga/icingaweb2/issues/5346)
|
|
||||||
|
|
||||||
But the team sat together a few weeks ago and fixed a bug here and there. And of course, also in Icinga Web!
|
|
||||||
|
|
||||||
* Users who are not allowed to change the theme, cannot change the theme mode either [#5385](https://github.com/Icinga/icingaweb2/issues/5385)
|
|
||||||
* Filtering for older-than events with relative time does not work [#5263](https://github.com/Icinga/icingaweb2/issues/5263)
|
|
||||||
* External logout not working from the navigation dashboard [#5000](https://github.com/Icinga/icingaweb2/issues/5000)
|
|
||||||
* Empty values are NULL in CSV exports [#5350](https://github.com/Icinga/icingaweb2/issues/5350)
|
|
||||||
|
|
||||||
#### Breaking, Somewhat
|
|
||||||
|
|
||||||
This is mainly for developers.
|
|
||||||
|
|
||||||
With the support of PHP 8.4, we introduced a new environment variable, `ICINGAWEB_ENVIRONMENT`. Unless set to `dev`,
|
|
||||||
Icinga Web will not show nor log deprecation notices anymore.
|
|
||||||
|
|
||||||
### 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](https://github.com/Icinga/icingaweb2/milestone/83?closed=1).
|
|
||||||
|
|
||||||
#### 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](https://github.com/Icinga/icingaweb2/milestone/81?closed=1).
|
You can find all issues related to this release on our Roadmap.
|
||||||
|
|
||||||
#### General Fixes
|
#### General Fixes
|
||||||
|
|
||||||
@ -133,7 +62,7 @@ but it’s now slightly improved.
|
|||||||
|
|
||||||
### What's New in Version 2.12.1
|
### What's New in Version 2.12.1
|
||||||
|
|
||||||
You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/80?closed=1).
|
You can find all issues related to this release on our Roadmap.
|
||||||
|
|
||||||
#### PHP 8.3 Support
|
#### PHP 8.3 Support
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,18 +133,6 @@ class MigrationForm extends CompatForm
|
|||||||
. ' that has the appropriate credentials to resolve this issue.'
|
. ' that has the appropriate credentials to resolve this issue.'
|
||||||
),
|
),
|
||||||
implode(', ', $mm->getRequiredDatabasePrivileges())
|
implode(', ', $mm->getRequiredDatabasePrivileges())
|
||||||
))),
|
|
||||||
new HtmlElement('br'),
|
|
||||||
new HtmlElement('br'),
|
|
||||||
new HtmlElement('span', null, Text::create(sprintf(
|
|
||||||
$this->translate(
|
|
||||||
'The database name may contain either an underscore or a percent sign.'
|
|
||||||
. ' In MySQL these characters represent a wildcard. If part of a database name,'
|
|
||||||
. ' they might not have been escaped when manually granting privileges.'
|
|
||||||
. ' Privileges might not be detected in this case. Check the documentation and'
|
|
||||||
. ' update your grants accordingly: %s'
|
|
||||||
),
|
|
||||||
'https://dev.mysql.com/doc/refman/8.0/en/grant.html#grant-quoting'
|
|
||||||
)))
|
)))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -21,5 +21,7 @@
|
|||||||
<?= $this->translate('In case you can access the file by yourself, you can open it and insert the config manually:'); ?>
|
<?= $this->translate('In case you can access the file by yourself, you can open it and insert the config manually:'); ?>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<pre><code><?= $this->escape($configString); ?></code></pre>
|
<pre>
|
||||||
|
<code><?= $this->escape($configString); ?></code>
|
||||||
|
</pre>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -59,7 +59,7 @@ sudo htpasswd -c /etc/icingaweb2/.http-users icingaadmin
|
|||||||
|
|
||||||
#### Apache Configuration <a id="authentication-configuration-external-authentication-example-apache"></a>
|
#### Apache Configuration <a id="authentication-configuration-external-authentication-example-apache"></a>
|
||||||
|
|
||||||
Add the following configuration to the `<Directory>` directive in the `icingaweb2.conf` web server
|
Add the following configuration to the `<Directory>` directive in the `icingaweb2.conf` web server
|
||||||
configuration file.
|
configuration file.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -157,7 +157,6 @@ Here is a list of all Icinga Web components that are capable of strict CSP.
|
|||||||
| Icinga Web GenericTTS Integration | [v2.1.0](https://github.com/Icinga/icingaweb2-module-generictts/releases/tag/v2.1.0) |
|
| Icinga Web GenericTTS Integration | [v2.1.0](https://github.com/Icinga/icingaweb2-module-generictts/releases/tag/v2.1.0) |
|
||||||
| Icinga Web Nagvis Integration | [v1.2.0](https://github.com/Icinga/icingaweb2-module-nagvis/releases/tag/v1.2.0) |
|
| Icinga Web Nagvis Integration | [v1.2.0](https://github.com/Icinga/icingaweb2-module-nagvis/releases/tag/v1.2.0) |
|
||||||
| Icinga Web AWS Integration | [v1.1.0](https://github.com/Icinga/icingaweb2-module-aws/releases/tag/v1.1.0) |
|
| Icinga Web AWS Integration | [v1.1.0](https://github.com/Icinga/icingaweb2-module-aws/releases/tag/v1.1.0) |
|
||||||
| Icinga Web vSphere Integration | [v1.8.0](https://github.com/Icinga/icingaweb2-module-vspheredb/releases/tag/v1.8.0) |
|
|
||||||
|
|
||||||
|
|
||||||
## Advanced Authentication Tips <a id="advanced-topics-authentication-tips"></a>
|
## Advanced Authentication Tips <a id="advanced-topics-authentication-tips"></a>
|
||||||
|
|||||||
@ -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`
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
<exclude-pattern>*/application/views/helpers/*</exclude-pattern>
|
<exclude-pattern>*/application/views/helpers/*</exclude-pattern>
|
||||||
<exclude-pattern>*/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php</exclude-pattern>
|
<exclude-pattern>*/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php</exclude-pattern>
|
||||||
</rule>
|
</rule>
|
||||||
<rule ref="Squiz.Classes.ValidClassName.NotPascalCase">
|
<rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
|
||||||
<exclude-pattern>*/application/views/helpers/*</exclude-pattern>
|
<exclude-pattern>*/application/views/helpers/*</exclude-pattern>
|
||||||
<exclude-pattern>*/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php</exclude-pattern>
|
<exclude-pattern>*/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php</exclude-pattern>
|
||||||
</rule>
|
</rule>
|
||||||
|
|||||||
@ -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.5';
|
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'];
|
||||||
@ -17,8 +17,8 @@ if (isset($_SERVER['REQUEST_URI'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Workaround, PHPs internal Webserver seems to mess up SCRIPT_FILENAME
|
// Workaround, PHPs internal Webserver seems to mess up SCRIPT_FILENAME
|
||||||
// as it prefixes its absolute path with DOCUMENT_ROOT
|
// as it prefixes it's absolute path with DOCUMENT_ROOT
|
||||||
if (preg_match('/^PHP.*Development Server/', $_SERVER['SERVER_SOFTWARE'])) {
|
if (preg_match('/^PHP .* Development Server/', $_SERVER['SERVER_SOFTWARE'])) {
|
||||||
$script = basename($_SERVER['SCRIPT_FILENAME']);
|
$script = basename($_SERVER['SCRIPT_FILENAME']);
|
||||||
$_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME'] = '/' . $script;
|
$_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME'] = '/' . $script;
|
||||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT']
|
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT']
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -352,7 +352,7 @@ class Form extends Zend_Form
|
|||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*
|
*
|
||||||
* @throws ProgrammingError In case $url is neither a string nor an instance of Icinga\Web\Url
|
* @throws ProgrammingError In case $url is neither a string nor a instance of Icinga\Web\Url
|
||||||
*/
|
*/
|
||||||
public function setRedirectUrl($url)
|
public function setRedirectUrl($url)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -139,7 +139,6 @@ class StyleSheet
|
|||||||
$this->lessCompiler->addLessFile($this->pubPath . '/' . $lessFile);
|
$this->lessCompiler->addLessFile($this->pubPath . '/' . $lessFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
$auth = Auth::getInstance();
|
|
||||||
$mm = $this->app->getModuleManager();
|
$mm = $this->app->getModuleManager();
|
||||||
|
|
||||||
foreach ($mm->getLoadedModules() as $moduleName => $module) {
|
foreach ($mm->getLoadedModules() as $moduleName => $module) {
|
||||||
@ -158,6 +157,7 @@ class StyleSheet
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! (bool) $themingConfig->get('disabled', false)) {
|
if (! (bool) $themingConfig->get('disabled', false)) {
|
||||||
|
$auth = Auth::getInstance();
|
||||||
if ($auth->isAuthenticated()) {
|
if ($auth->isAuthenticated()) {
|
||||||
$userTheme = $auth->getUser()->getPreferences()->getValue('icingaweb', 'theme');
|
$userTheme = $auth->getUser()->getPreferences()->getValue('icingaweb', 'theme');
|
||||||
if ($userTheme !== null) {
|
if ($userTheme !== null) {
|
||||||
@ -174,7 +174,7 @@ class StyleSheet
|
|||||||
Logger::warning(sprintf(
|
Logger::warning(sprintf(
|
||||||
'Theme "%s" set by user "%s" has not been found.',
|
'Theme "%s" set by user "%s" has not been found.',
|
||||||
$theme,
|
$theme,
|
||||||
$auth->isAuthenticated() ? $auth->getUser()->getUsername() : 'anonymous'
|
($user = Auth::getInstance()->getUser()) !== null ? $user->getUsername() : 'anonymous'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,10 +184,10 @@ class StyleSheet
|
|||||||
}
|
}
|
||||||
|
|
||||||
$mode = 'none';
|
$mode = 'none';
|
||||||
if ($auth->isAuthenticated()) {
|
if ($user = Auth::getInstance()->getUser()) {
|
||||||
$file = $themePath !== null ? @file_get_contents($themePath) : false;
|
$file = $themePath !== null ? @file_get_contents($themePath) : false;
|
||||||
if (! $file || strpos($file, self::LIGHT_MODE_IDENTIFIER) !== false) {
|
if (! $file || strpos($file, self::LIGHT_MODE_IDENTIFIER) !== false) {
|
||||||
$mode = $auth->getUser()->getPreferences()->getValue('icingaweb', 'theme_mode', self::DEFAULT_MODE);
|
$mode = $user->getPreferences()->getValue('icingaweb', 'theme_mode', self::DEFAULT_MODE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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.5
|
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.5
|
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.5
|
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,13 +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]
|
||||||
* Only one of --fpm-socket-path or --fpm-url can be set at a time
|
|
||||||
*
|
|
||||||
* --fpm-uri=<uri> Alias for --fpm-url
|
|
||||||
*
|
|
||||||
* --fpm-socket-path=<socketpath> Socket path where to pass requests to FPM
|
|
||||||
* Only one of --fpm-socket-path or --fpm-url can be set at a time
|
|
||||||
*
|
*
|
||||||
* --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
|
* --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
|
||||||
*
|
*
|
||||||
@ -126,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()
|
||||||
{
|
{
|
||||||
@ -167,16 +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'));
|
if (empty($fpmUri)) {
|
||||||
if (empty($fpmUrl)) {
|
|
||||||
$fpmUrl = trim($this->params->get('fpm-uri'));
|
|
||||||
}
|
|
||||||
if (empty($fpmSocketPath) && empty($fpmUrl)) {
|
|
||||||
$fpmUrl = $webserver->getFpmUrl();
|
|
||||||
} elseif (!empty($fpmSocketPath) && !empty($fpmUrl)) {
|
|
||||||
$this->fail($this->translate(
|
$this->fail($this->translate(
|
||||||
'Only 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'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
$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) {
|
||||||
|
|||||||
@ -91,19 +91,6 @@ class DatabaseCreationPage extends Form
|
|||||||
*/
|
*/
|
||||||
public function createElements(array $formData)
|
public function createElements(array $formData)
|
||||||
{
|
{
|
||||||
if ($this->config['db'] === 'mysql' && preg_match('/[_%]/', $this->config['dbname'])) {
|
|
||||||
$this->warning(sprintf(
|
|
||||||
$this->translate(
|
|
||||||
'The database name may contain either an underscore or a percent sign.'
|
|
||||||
. ' In MySQL these characters represent a wildcard. If part of a database name,'
|
|
||||||
. ' they might not have been escaped when manually granting privileges.'
|
|
||||||
. ' Privileges might not be detected in this case. Check the documentation and'
|
|
||||||
. ' update your grants accordingly: %s'
|
|
||||||
),
|
|
||||||
'https://dev.mysql.com/doc/refman/8.0/en/grant.html#grant-quoting'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$skipValidation = isset($formData['skip_validation']) && $formData['skip_validation'];
|
$skipValidation = isset($formData['skip_validation']) && $formData['skip_validation'];
|
||||||
$this->addElement(
|
$this->addElement(
|
||||||
'text',
|
'text',
|
||||||
|
|||||||
@ -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,16 +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()
|
|
||||||
{
|
|
||||||
return empty($this->fpmSocketPath)
|
|
||||||
? $this->fpmUrlSchema . $this->fpmUrl
|
|
||||||
: $this->fpmSocketSchema . $this->fpmSocketPath . '|' . $this->fpmUrlSchema . 'localhost';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getTemplate()
|
protected function getTemplate()
|
||||||
{
|
{
|
||||||
@ -32,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>
|
||||||
|
|
||||||
@ -80,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>
|
||||||
@ -94,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>
|
||||||
|
|
||||||
@ -140,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.5
|
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.5
|
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.5
|
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.
|
||||||
|
|||||||
@ -33,7 +33,3 @@ table.action {
|
|||||||
table.avp {
|
table.avp {
|
||||||
.name-value-table();
|
.name-value-table();
|
||||||
}
|
}
|
||||||
|
|
||||||
input.search {
|
|
||||||
background-size: 0;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -233,9 +233,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
border: 1px solid @gray-lighter;
|
border: 1px solid @gray-lighter;
|
||||||
background: @body-bg-color;
|
background: @body-bg-color;
|
||||||
color: @text-color;
|
|
||||||
box-shadow: 0 0 1em 0 rgba(0,0,0,.25);
|
box-shadow: 0 0 1em 0 rgba(0,0,0,.25);
|
||||||
z-index: 15;
|
z-index: 1;
|
||||||
.rounded-corners();
|
.rounded-corners();
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|||||||
@ -21,34 +21,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form > .search-icon {
|
|
||||||
position: absolute;
|
|
||||||
left: 0.25em;
|
|
||||||
top: ~"calc(50% - 0.5em)";
|
|
||||||
pointer-events: none;
|
|
||||||
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,
|
||||||
|
|||||||
@ -228,20 +228,11 @@ form.icinga-form {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form.icinga-form {
|
form.icinga-form select:not([multiple]),
|
||||||
select:not([multiple]),
|
form.icinga-form input[type="datetime-local"] {
|
||||||
input[type="text"],
|
// Compensate inconsistent select height calculations
|
||||||
input[type="password"],
|
line-height: 1em;
|
||||||
input[type="number"],
|
|
||||||
input[type="datetime-local"],
|
|
||||||
input[type="date"],
|
|
||||||
input[type="time"] {
|
|
||||||
// Use the same height for select and all supported input elements. input[type="file"] is not styled here
|
|
||||||
// because it requires more height due to the built-in ‘Choose file’ button.
|
|
||||||
// Elements such as checkbox, radio and submit buttons have their own custom style.
|
|
||||||
line-height: 1;
|
|
||||||
height: 2.25em;
|
height: 2.25em;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove native dropdown arrow in IE10+
|
// Remove native dropdown arrow in IE10+
|
||||||
|
|||||||
@ -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,22 +2,19 @@
|
|||||||
|
|
||||||
#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 {
|
||||||
background-color: @menu-bg-color;
|
background-color: @menu-bg-color;
|
||||||
color: @menu-color;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu .nav-item {
|
#menu .nav-item {
|
||||||
@ -36,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 {
|
||||||
@ -54,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 {
|
||||||
@ -71,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,
|
||||||
@ -91,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;
|
||||||
@ -99,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;
|
||||||
@ -111,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;
|
||||||
@ -122,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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,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;
|
||||||
@ -150,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;
|
||||||
@ -160,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;
|
||||||
}
|
}
|
||||||
@ -214,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;
|
||||||
@ -246,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 {
|
||||||
@ -254,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 {
|
||||||
@ -267,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;
|
||||||
@ -283,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 {
|
||||||
@ -326,7 +303,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide activity caret when displayed as flyout
|
// Hide activity caret when displayed as flyout
|
||||||
&::after {
|
&:after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,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;
|
||||||
@ -527,35 +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;
|
|
||||||
color: @menu-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,
|
||||||
@ -567,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 {
|
||||||
|
|||||||
@ -225,6 +225,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.controls input.search,
|
||||||
|
input.search {
|
||||||
|
background-image: url(../img/icons/search.png);
|
||||||
|
}
|
||||||
|
|
||||||
.search-bar,
|
.search-bar,
|
||||||
.button-link,
|
.button-link,
|
||||||
.view-mode-switcher > label {
|
.view-mode-switcher > label {
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
this.on('submit', '#modal form', this.onFormSubmit, this);
|
this.on('submit', '#modal form', this.onFormSubmit, this);
|
||||||
this.on('change', '#modal form select.autosubmit', this.onFormAutoSubmit, this);
|
this.on('change', '#modal form select.autosubmit', this.onFormAutoSubmit, this);
|
||||||
this.on('change', '#modal form input.autosubmit', this.onFormAutoSubmit, this);
|
this.on('change', '#modal form input.autosubmit', this.onFormAutoSubmit, this);
|
||||||
this.on('click', '[data-icinga-modal][href]', this.onModalToggleClick, this);
|
this.on('click', '[data-icinga-modal]', this.onModalToggleClick, this);
|
||||||
this.on('mousedown', '#layout > #modal', this.onModalLeave, this);
|
this.on('mousedown', '#layout > #modal', this.onModalLeave, this);
|
||||||
this.on('click', '.modal-header > button', this.onModalClose, this);
|
this.on('click', '.modal-header > button', this.onModalClose, this);
|
||||||
this.on('keydown', this.onKeyDown, this);
|
this.on('keydown', this.onKeyDown, this);
|
||||||
@ -114,7 +114,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let $autoSubmittedBy;
|
let $autoSubmittedBy;
|
||||||
if (event.detail !== null && typeof event.detail === 'object' && "submittedBy" in event.detail) {
|
if (typeof event.detail !== 'undefined' && "submittedBy" in event.detail) {
|
||||||
$autoSubmittedBy = $(event.detail.submittedBy);
|
$autoSubmittedBy = $(event.detail.submittedBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -261,11 +247,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $autoSubmittedBy
|
if (! $autoSubmittedBy && typeof event.detail !== 'undefined' && "submittedBy" in event.detail) {
|
||||||
&& event.detail !== null
|
|
||||||
&& typeof event.detail === 'object'
|
|
||||||
&& "submittedBy" in event.detail
|
|
||||||
) {
|
|
||||||
$autoSubmittedBy = $(event.detail.submittedBy);
|
$autoSubmittedBy = $(event.detail.submittedBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,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;
|
||||||
|
|
||||||
@ -410,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';
|
||||||
@ -1058,10 +1054,6 @@
|
|||||||
errorThrown + ':',
|
errorThrown + ':',
|
||||||
$(req.responseText).text().replace(/\s+/g, ' ').slice(0, 100)
|
$(req.responseText).text().replace(/\s+/g, ' ').slice(0, 100)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (req.status === 401) {
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
this.renderContentToContainer(
|
this.renderContentToContainer(
|
||||||
req.responseText,
|
req.responseText,
|
||||||
req.$target,
|
req.$target,
|
||||||
@ -1071,7 +1063,6 @@
|
|||||||
req.autosubmit,
|
req.autosubmit,
|
||||||
req.scripted
|
req.scripted
|
||||||
);
|
);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (errorThrown === 'abort') {
|
if (errorThrown === 'abort') {
|
||||||
this.icinga.logger.debug(
|
this.icinga.logger.debug(
|
||||||
|
|||||||
@ -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