Skip to content
Commits on Source (5)
Alexander A. Klimov <alexander.klimov@icinga.com> <Alexander.Klimov@netways.de>
Alexander A. Klimov <alexander.klimov@icinga.com> <alexander.klimov@netways.de>
Alexander A. Klimov <alexander.klimov@icinga.com> <Al2Klimov@users.noreply.github.com>
Bernd Erk <bernd.erk@icinga.com> <bernd.erk@netways.de>
Bernd Erk <bernd.erk@icinga.com><berk@nb-berk.int.netways.de>
Bernd Erk <bernd.erk@icinga.com><berk@nb-berk.local>
Bernd Erk <bernd.erk@icinga.com><bernd.erk@icinga.com>
Bernd Erk <bernd.erk@icinga.com><bernd.erk@icinga.org>
Blerim Sheqa <blerim.sheqa@icinga.com> <blerim.sheqa@netways.de>
Carlos Cesario <carloscesario@gmail.com> <ccesario@tecnomega.com.br>
Christopher Rüll <christopher.ruell@netways.de> <Christopher.Ruell@netways.de>
Eric Lippmann <eric.lippmann@icinga.com> <eric.lippmann@netways.de>
Eric Lippmann <eric.lippmann@icinga.com> <lippserd@googlemail.com>
Florian Strohmaier <florian.strohmaier@icinga.com> <florian.strohmaier@netways.de>
Florian Strohmaier <florian.strohmaier@icinga.com> <hello@florianstrohmaier.com>
Gunnar Beutner <gunnar.beutner@netways.de> <gunnar@beutner.name>
Jannis Moßhammer <jannis.mosshammer@netways.de>
Johannes Meyer <johannes.meyer@icinga.com> <johannes.meyer@netways.de>
Jennifer Mourek <jennifer.mourek@icinga.com> <jennifer.mourek@netways.de>
Markus Frosch <markus.frosch@icinga.com> <lazyfrosch@icinga.org>
Markus Frosch <markus.frosch@icinga.com> <markus.frosch@netways.de>
Markus Frosch <markus.frosch@icinga.com> <markus@lazyfrosch.de>
......@@ -19,6 +24,8 @@ Matthias Jentsch <matthias.jentsch@netways.de> <mjentsch@localhost.int.netways.d
Michael Friedrich <michael.friedrich@icinga.com> <Michael.Friedrich@netways.de>
Michael Friedrich <michael.friedrich@icinga.com> <michael.friedrich@gmail.com>
Michael Friedrich <michael.friedrich@icinga.com> <michael.friedrich@netways.de>
Nicolai Buchwitz <nicolai.buchwitz@enda.eu> <nbuchwitz@users.noreply.github.com>
Noah Hilverling <noah.hilverling@icinga.com> <noah.hilverling@netways.de>
Sylph Lin <sylph.lin@gmail.com>
Thomas Gelf <thomas.gelf@icinga.com> <root@squeeze-devel1.osmc.lab>
Thomas Gelf <thomas.gelf@icinga.com> <tgelf@tgelf-web2dep.(none)>
......
language: php
dist: trusty
sudo: false
php:
- '5.4'
- '5.5'
- '5.6'
- '7.0'
- '7.1'
matrix:
include:
- php: '5.3'
dist: precise
sudo: required
env:
- PHPCS_VERSION=2.9.1
- LOCALE_GEN=1
- ENABLE_LDAP=1
services:
- mysql
- postgresql
cache:
directories:
- vendor
branches:
only:
- master
- /^v\d/
notifications:
email: false
# also see: test/setup_vendor.sh
before_script:
- php -m
- sh -c '[ -z $LOCALE_GEN ] || sudo locale-gen en_US.UTF-8 de_DE.UTF-8 fr_FR.UTF-8'
- sh -c '[ -z $ENABLE_LDAP ] || phpenv config-add test/travis-ldap.ini'
- test/travis_database.sh
- test/setup_vendor.sh
script:
# also see: modules/test/application/clicommands/PhpCommand.php
- php phpcs.phar
- php phpunit.phar -c modules/test/phpunit.xml --verbose
......@@ -2,64 +2,91 @@ Aaron Collins <acollins@chegg.com>
Alexander A. Klimov <alexander.klimov@icinga.com>
Alexander Fuhr <alexander.fuhr@netways.de>
Alexander Wirt <formorer@debian.org>
ayoubabid <ayoubabid@users.noreply.github.com>
baufrecht <baufrecht@users.noreply.github.com>
Bence Nagy <bence@underyx.me>
Benedikt Heine <bebe@bebehei.de>
Bernd Erk <bernd.erk@icinga.com>
Bernhard Friedreich <bernhard.friedreich@brz.gv.at>
Blerim Sheqa <blerim.sheqa@icinga.com>
Boden Garman <boden.garman@spintel.net.au>
bradynathan <bradynathan@gmail.com>
Carlos Cesario <carloscesario@gmail.com>
Carsten <carsten.koebke@gmx.de>
Chris Reeves <chris.reeves@york.ac.uk>
Christopher Rüll <christopher.ruell@netways.de>
Christoph Niemann <kordolan@googlemail.com>
Christoph Wiechert <wio@psitrax.de>
Cornelius Wachinger <cornelius@dercorn.com>
cstegm <cstegm@users.noreply.github.com>
Daniel <d.lorych@gmail.com>
Daniel Shirley <aditaa@ig2ad.com>
Davide Bizzarri <davide.bizzarri@wuerth-phoenix.com>
Davide Demuru <davide.demuru@buongiorno.com>
Dirk Goetz <dirk.goetz@netways.de>
Emil Vikström <emil@pixelstore.se>
Eric Jaw <naisanza@gmail.com>
Eric Lippmann <eric.lippmann@icinga.com>
Florian Strohmaier <florian.strohmaier@icinga.com>
Francesco Colista <fcolista@alpinelinux.org>
Goran Rakic <grakic@devbase.net>
Gunnar Beutner <gunnar.beutner@netways.de>
hailthemelody@rm-laptop04 <hailthemelody@rm-laptop04>
Hector Sanjuan <hector.sanjuan@nugg.ad>
Heike Jurzik <huhn@lion-3.fritz.box>
Ian Shearin <ishearin@womply.com>
Jannis Moßhammer <jannis.mosshammer@netways.de>
Jennifer Mourek <jennifer.mourek@netways.de>
Jo Rhett <jo@chegg.com>
Jennifer Mourek <jennifer.mourek@icinga.com>
Joe Doherty <git@pjuu.com>
Johannes Meyer <johannes.meyer@netways.de>
Johannes Meyer <johannes.meyer@icinga.com>
Joonas Kylmälä <joonas.kylmala@kirjastot.fi>
Jo Rhett <jo@chegg.com>
Ken Jungclaus <lum33n@web.de>
Klaus Jrgensen <klaus@blackwoodseven.com>
Lee Clemens <java@leeclemens.net>
Louis Sautier <sautier.louis@gmail.com>
mapa82 <maik.paetzold@akra.de>
Marc DeTrano <marc@gridshield.net>
Marcus Cobden <marcus@marcuscobden.co.uk>
Marius Hein <marius.hein@netways.de>
Markus Frosch <markus.frosch@icinga.com>
Matthias Jentsch <matthias.jentsch@netways.de>
Max Stephan <xam.stephan@web.de>
mbaschnitzi <mbaschnitzi@users.noreply.github.com>
Michael Friedrich <michael.friedrich@icinga.com>
Michael T. DeGuzis <mdeguzis@users.noreply.github.com>
Mikesch-mp <Mikesch-mp@koebbes.de>
Mikko Peltokangas <mikko@peltokangas.org>
Munzir Taha <munzirtaha@gmail.com>
Noah Hilverling <noah.hilverling@netways.de>
Nicolai Buchwitz <nicolai.buchwitz@enda.eu>
Noah Hilverling <noah.hilverling@icinga.com>
Paolo Schiro <paolo.schiro@kpnqwest.it>
Paul Richards <paul@minimoo.org>
Pavlos Daoglou <pdaoglou@gmail.com>
Pieter Lexis <pieter.lexis@powerdns.com>
Ramy Talal <ramy@thinkquality.nl>
Raphael Bicker <raphael@bicker.ch>
rbelinsky <rbelinsky@dalet.com>
realitygaps <github@gapsinreality.com>
Rene Moser <rene.moser@swisstxt.ch>
rkcpi <thieme.sandra@gmail.com>
Roland Hopferwieser <rhopfer@ica.jku.at>
Rudy Gevaert <rudy.gevaert@ugent.be>
Rune Darrud <theflyingcorpse@gmail.com>
Russell Kubik <russkubik@3d-p.com>
Sander Ferdinand <sa.ferdinand@gmail.com>
Simone Orsi <simahawk@users.noreply.github.com>
ss23 <stephen@zxsecurity.co.nz>
Susanne Vestner-Ludwig <susanne.vestner-ludwig@inserteffect.com>
Sylph Lin <sylph.lin@gmail.com>
tfylling <torbfylling@gmail.com>
Thomas Gelf <thomas.gelf@icinga.com>
Tim Helfensdörfer <tim@visualappeal.de>
Tobias von der Krone <tobias.vonderkrone@profitbricks.com>
Tomas Barton <barton.tomas@gmail.com>
Tom Ford <exptom@users.noreply.github.com>
Ulf Lange <mopp@gmx.net>
Uwe Ebel <kobmaki@aol.com>
Vladislav Ponomarev <vponomarev@team.mobile.de>
Yuri Konotopov <ykonotopov@gmail.com>
ayoubabid <ayoubabid@users.noreply.github.com>
baufrecht <baufrecht@users.noreply.github.com>
bradynathan <bradynathan@gmail.com>
hailthemelody@rm-laptop04 <hailthemelody@rm-laptop04>
mbaschnitzi <mbaschnitzi@users.noreply.github.com>
rbelinsky <rbelinsky@dalet.com>
realitygaps <github@gapsinreality.com>
rkcpi <thieme.sandra@gmail.com>
xert <xert@users.noreply.github.com>
Yuri Konotopov <ykonotopov@gmail.com>
# Icinga Web 2 Changelog
Please make sure to always read our [Upgrading](doc/80-Upgrading.md) documentation before switching to a new version.
## What's New
### What's New in Version 2.5.2
You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/49?closed=1).
#### UI Changes
The sidebar's search behaviour has been changed so that it does only react to user-input after the user stopped typing.
Also, the cursor does not jump to the end of form-inputs anymore in case of an auto-refresh. We've also fixed an issue
that caused [custom icons](https://github.com/Icinga/icingaweb2/issues/3181#issuecomment-378875462) to be inverted when
placed in the sidebar. Last but not least, the header now expands its width beyond the 3840px mark and single dashlets
do not show a horizontal scrollbar anymore.
#### PHP7 MSSQL Compatibility
Support for Microsoft's `sqlsrv` extension has been added. Also, it's now possible to setup MSSQL resources in the
front-end using the `dblib` extension.
#### Proper Error Responses
An issue introduced with v2.5.1 has been resolved where some errors (especially HTTP 404 Not Found) were masked
by another subsequent error.
#### Broken LDAP Group Memberships
An issue introduced with v2.5.1 has been resolved where users with a domain in their name were not associated with any
LDAP groups.
#### Monitoring Module
Issuing a check using the "Check Now" action now properly causes a check being made by Icinga 2 even if outside the
timeperiod. (Note: This issue was only present if using the Icinga 2 Api as command transport.)
#### Login/Logout Expandability
It's now possible for modules to provide hooks for the user authorization. This for example allows to transparently
authenticate users in third-party applications such as [Grafana](https://github.com/Icinga/icingaweb2/pull/3401#issue-178030542).
### What's New in Version 2.5.1
You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/47?closed=1).
Besides many other bug fixes, Icinga Web 2 v2.5.1 fixes an issue where it was no longer possible to filter by host
custom variables in service related views. Also, this release introduces detail views for the event history and
improved upgrading docs. Furthermore, this version censors sensitive information (e.g. LDAP passwords) in exception
stack traces.
### What's New in Version 2.5.0
You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/45?closed=1).
#### Raised PHP Version Dependency
Icinga Web 2 now requires at least PHP 5.6.
#### UI Changes
The style of the login screen and menu have been changed. Also, the menu of Icinga Web 2 is now collapsible.
Browser tabs will not auto-refresh if they are inactive. Users are now allowed to change the default pagination limit
via their preferences.
#### Domain-aware Authentication for Active Directory and LDAP Backends
If there are multiple AD/LDAP authentication backends with distinct domains, you are now able to make Icinga Web 2
aware of the domains. This can be done by configuring each AD/LDAP backend's domain. You can also use the GUI for this
purpose. Please read our [documentation](doc/05-Authentication.md#domain-aware-auth) for more information about this
feature.
#### Changes in Packaging and Dependencies
Valid for distributions:
* RHEL / CentOS 6 + 7
* Upgrading to PHP 7.0 / 7.1 via RedHat SCL (new dependency)
* See [Upgrading to FPM](doc/02-Installation.md#upgrading-to-fpm) for manual steps that are required
* SUSE SLE 12
* Upgrading PHP to >= 5.6.0 via the alternative packages.
You might have to confirm the replacement of PHP < 5.6 - but that should work with any other PHP app as well
* Make sure to enable the new Apache module `a2enmod php7` and restart `apache2`
#### Discontinued Package Updates
For the following distributions Icinga Web 2 won't be updated past 2.4.x anymore:
* Debian 7 wheezy
* Ubuntu 14.04 LTS (trusty)
* SUSE SLE 11 (all service packs)
Please think about replacing your central Icinga system to a newer distribution release.
Also see [packages.icinga.com](https://packages.icinga.com) for the currently supported distributions.
### What's New in Version 2.4.2
#### Bugfixes
* Bug 2965: Transport config: Default port not changing upon auto-submit
* Bug 2926: Wrong order when sorting by host_severity
* Bug 2923: Number fields should be valid when empty
* Bug 2919: Fix cached loading of module config
* Bug 2911: Acknowledgements are not working without an expiry time
* Bug 2878: process-check-result Button is visible even when user isn't allowed to use it
* Bug 2850: Link to acknowledgements is wrong in the timeline
* Bug 2841: Wrong menu height when switching back from mobile layout
* Bug 2806: Wrong service state count in hostgroup overview
* Bug 2805: Response from the Icinga 2 API w/ an empty result set leads to exception
* Bug 2801: Wrong help text for the director in the icingacli
* Bug 2784: Module and gravatar images are not served with their proper MIME type
* Bug 2776: Defaults not respected when acknowledging problems
* Bug 2767: Monitoring module: Config field protected vars not updated after zeroing config.ini
* Bug 2728: Gracefully handle invalid Icinga 2 API response types
* Bug 2718: Hide check attempt for hard states in history views
* Bug 2716: Web 2 doesn't detect the browser time zone if the time zone offset is negative
* Bug 2714: icingacli module disable fails on consecutive calls
* Bug 2695: Macros cannot be used for a navigation item's url-port
* Bug 2684: [dev.icinga.com #14027] Translation module should not write absolute path to .po files
* Bug 2683: [dev.icinga.com #14025] Translation module should remove temp files
* Bug 2661: [dev.icinga.com #13651] Don't offer the Icinga 2 API as transport if PHP cURL is missing
* Bug 2660: [dev.icinga.com #13649] Make the Icinga 2 API the default command transport
* Bug 2656: [dev.icinga.com #13627] Wrong count of handled critical service in the hover text
* Bug 2645: [dev.icinga.com #13539] Improve error handling and validation of multiple LDAP URIs
* Bug 2598: [dev.icinga.com #12977] Adding an empty user backend fails
* Bug 2545: [dev.icinga.com #12640] MSSQL ressource not working
* Bug 2523: [dev.icinga.com #12410] Click on Host in Service Grid can cause "Invalid Filter" error
* Bug 2519: [dev.icinga.com #12330] Filter editor may show wrong values after searching
* Bug 2509: [dev.icinga.com #12295] group_name_attribute should be "sAMAccountName" by default
### What's New in Version 2.4.1
Our public repositories and issue tracker have been migrated to GitHub.
......
# <a id="contributing"></a> Contributing
Icinga is an open source project and lives from your ideas and contributions.
There are many ways to contribute, from improving the documentation, submitting
bug reports and features requests or writing code to add enhancements or fix bugs.
#### Table of Contents
1. [Introduction](#contributing-intro)
2. [Fork the Project](#contributing-fork)
3. [Branches](#contributing-branches)
4. [Commits](#contributing-commits)
5. [Pull Requests](#contributing-pull-requests)
6. [Testing](#contributing-testing)
7. [Source Code Patches](#contributing-patches-source-code)
8. [Documentation Patches](#contributing-patches-documentation)
9. [Review](#contributing-review)
## <a id="contributing-intro"></a> Introduction
Please consider our [roadmap](https://github.com/Icinga/icingaweb2/milestones) and
[open issues](https://github.com/icinga/icingaweb2/issues) when you start contributing
to the project.
Before starting your work on Icinga Web 2, you should [fork the project](https://help.github.com/articles/fork-a-repo/)
to your GitHub account. This allows you to freely experiment with your changes.
When your changes are complete, submit a [pull request](https://help.github.com/articles/using-pull-requests/).
All pull requests will be reviewed and merged if they suit some general guidelines:
* Changes are located in a topic branch
* For new functionality, proper tests are written
* Changes should follow the existing coding style and standards
Please continue reading in the following sections for a step by step guide.
## <a id="contributing-fork"></a> Fork the Project
[Fork the project](https://help.github.com/articles/fork-a-repo/) to your GitHub account
and clone the repository:
```
git clone git@github.com:dnsmichi/icingaweb2.git
cd icingaweb2
```
Add a new remote `upstream` with this repository as value.
```
git remote add upstream https://github.com/icinga/icingaweb2.git
```
You can pull updates to your fork's master branch:
```
git fetch --all
git pull upstream HEAD
```
Please continue to learn about [branches](CONTRIBUTING.md#contributing-branches).
## <a id="contributing-branches"></a> Branches
Choosing a proper name for a branch helps us identify its purpose and possibly
find an associated bug or feature.
Generally a branch name should include a topic such as `fix` or `feature` followed
by a description and an issue number if applicable. Branches should have only changes
relevant to a specific issue.
```
git checkout -b fix/service-template-typo-1234
git checkout -b feature/config-handling-1235
```
Continue to apply your changes and test them. More details on specific changes:
* [Source Code Patches](#contributing-patches-source-code)
* [Documentation Patches](#contributing-patches-documentation)
## <a id="contributing-commits"></a> Commits
Once you've finished your work in a branch, please ensure to commit
your changes. A good commit message includes a short topic, additional body
and a reference to the issue you wish to solve (if existing).
Fixes:
```
Fix missing style in detail view
refs #4567
```
Features:
```
Add DateTime picker
refs #1234
```
You can add multiple commits during your journey to finish your patch.
Don't worry, you can squash those changes into a single commit later on.
## <a id="contributing-pull-requests"></a> Pull Requests
Once you've committed your changes, please update your local master
branch and rebase your fix/feature branch against it before submitting a PR.
```
git checkout master
git pull upstream HEAD
git checkout fix/style-detail-view
git rebase master
```
Once you've resolved any conflicts, push the branch to your remote repository.
It might be necessary to force push after rebasing - use with care!
New branch:
```
git push --set-upstream origin fix/style-detail-view
```
Existing branch:
```
git push -f origin fix/style-detail-view
```
You can now either use the [hub](https://hub.github.com) CLI tool to create a PR, or nagivate
to your GitHub repository and create a PR there.
The pull request should again contain a telling subject and a reference
with `fixes` to an existing issue id if any. That allows developers
to automatically resolve the issues once your PR gets merged.
```
hub pull-request
<a telling subject>
fixes #1234
```
Thanks a lot for your contribution!
### <a id="contributing-rebase"></a> Rebase a Branch
If you accidentally sent in a PR which was not rebased against the upstream master,
developers might ask you to rebase your PR.
First off, fetch and pull `upstream` master.
```
git checkout master
git fetch --all
git pull upstream HEAD
```
Then change to your working branch and start rebasing it against master:
```
git checkout fix/style-detail-view
git rebase master
```
If you are running into a conflict, rebase will stop and ask you to fix the problems.
```
git status
both modified: path/to/conflict.php
```
Edit the file and search for `>>>`. Fix, build, test and save as needed.
Add the modified file(s) and continue rebasing.
```
git add path/to/conflict.php
git rebase --continue
```
Once succeeded ensure to push your changed history remotely.
```
git push -f origin fix/style-detail-view
```
If you fear to break things, do the rebase in a backup branch first and later replace your current branch.
```
git checkout fix/style-detail-view
git checkout -b fix/style-detail-view-rebase
git rebase master
git branch -D fix/style-detail-view
git checkout -b fix/style-detail-view
git push -f origin fix/style-detail-view
```
### <a id="contributing-squash"></a> Squash Commits
> **Note:**
>
> Be careful with squashing. This might lead to non-recoverable mistakes.
>
> This is for advanced Git users.
Say you want to squash the last 3 commits in your branch into a single one.
Start an interactive (`-i`) rebase from current HEAD minus three commits (`HEAD~3`).
```
git rebase -i HEAD~3
```
Git opens your preferred editor. `pick` the commit in the first line, change `pick` to `squash` on the other lines.
```
pick e4bf04e47 Fix style detail view
squash d7b939d99 Tests
squash b37fd5377 Doc updates
```
Save and let rebase to its job. Then force push the changes to the remote origin.
```
git push -f origin fix/style-detail-view
```
## <a id="contributing-testing"></a> Testing
Basic unit test coverage is provided by running `icingacli test php unit`.
The [development Vagrant box](https://github.com/Icinga/icingaweb2/blob/master/doc/99-Vagrant.md)
provides a pre-built environment for development and tests.
Snapshot packages from the laster development branch are available inside the
[package repository](https://packages.icinga.com).
You can help test-drive the latest Icinga 2 snapshot packages inside the
[Icinga 2 Vagrant boxes](https://github.com/icinga/icinga-vagrant).
## <a id="contributing-patches-source-code"></a> Source Code Patches
Icinga Web 2 is written in PHP and JavaScript.
In order to develop Icinga Web 2 please use the [development Vagrant box](https://github.com/Icinga/icingaweb2/blob/master/doc/99-Vagrant.md).
You can edit the source code in your local git repository and review changes
live from the Vagrant environment.
## <a id="contributing-patches-documentation"></a> Documentation Patches
The documentation is written in GitHub flavored [Markdown](https://guides.github.com/features/mastering-markdown/).
It is located in the `doc/` directory and can be edited with your preferred editor. You can also
edit it online on GitHub.
```
vim doc/02-Installation.md
```
In order to review and test changes, you can use the `doc` module in Icinga Web 2.
Navigate to `Configuration - Modules` and enable the `doc` module. Open
`Documentation - Icinga Web 2` from the menu.
## <a id="contributing-review"></a> Review
### <a id="contributing-pr-review"></a> Pull Request Review
This is only important for developers who will review pull requests. If you want to join
the development team, kindly contact us.
- Ensure that the style guide applies.
- Verify that the patch fixes a problem or linked issue, if any.
- Discuss new features with team members.
- Test the patch in your local dev environment.
If there are changes required, kindly ask for an updated patch.
Once the review is completed, merge the PR via GitHub.
#### <a id="contributing-pr-review-fixes"></a> Pull Request Review Fixes
In order to amend the commit message, fix conflicts or add missing changes, you can
add your changes to the PR.
A PR is just a pointer to a different Git repository and branch.
By default, pull requests allow to push into the repository of the PR creator.
Example for [#4956](https://github.com/Icinga/icinga2/pull/4956):
At the bottom it says "Add more commits by pushing to the fix/persistent-comments-are-not-persistent branch on TheFlyingCorpse/icinga2."
First off, add the remote repository as additional origin and fetch its content:
```
git remote add theflyingcorpse https://github.com/TheFlyingCorpse/icinga2
git fetch --all
```
Checkout the mentioned remote branch into a local branch (Note: `theflyingcorpse` is the name of the remote):
```
git checkout theflyingcorpse/fix/persistent-comments-are-not-persistent -b fix/persistent-comments-are-not-persistent
```
Rebase, amend, squash or add your own commits on top.
Once you are satisfied, push the changes to the remote `theflyingcorpse` and its branch `fix/persistent-comments-are-not-persistent`.
The syntax here is `git push <remote> <localbranch>:<remotebranch>`.
```
git push theflyingcorpse fix/persistent-comments-are-not-persistent:fix/persistent-comments-are-not-persistent
```
In case you've changed the commit history (rebase, amend, squash), you'll need to force push. Be careful, this can't be reverted!
```
git push -f theflyingcorpse fix/persistent-comments-are-not-persistent:fix/persistent-comments-are-not-persistent
```
# Icinga Web 2
[![Build Status](https://travis-ci.org/Icinga/icingaweb2.png?branch=master)](https://travis-ci.org/Icinga/icingaweb2)
[![Github Tag](https://img.shields.io/github/tag/Icinga/icingaweb2.svg)](https://github.com/Icinga/icingaweb2)
![Icinga Logo](https://www.icinga.com/wp-content/uploads/2014/06/icinga_logo.png)
1. [About](#about)
......@@ -15,7 +18,8 @@
and command-line interface developed by the [Icinga Project](https://www.icinga.com/), supporting Icinga 2,
Icinga Core and any other monitoring backend compatible with the IDO database.
![Icinga Web 2](https://www.icinga.com/wp-content/uploads/2016/12/Icinga-Web-2-v2.4.0.png "Icinga Web 2")
![Icinga Web 2 with Graphite](https://www.icinga.com/wp-content/uploads/2017/11/Icinga-Web-2-graphite.png "Icinga Web 2 with Graphite")
![Icinga Web 2 Sidebar Collapsed](https://www.icinga.com/wp-content/uploads/2017/11/Icinga-Web-2-collapsed-sidebar.png "Icinga Web 2 Sidebar Collapsed")
## License
......@@ -39,6 +43,8 @@ or ask an Icinga partner for [professional support](https://www.icinga.com/servi
## Contributing
There are many ways to contribute to Icinga -- whether it be creating pull requests on
[GitHub](https://github.com/Icinga/icingaweb2), sending patches, testing, reporting bugs,
or reviewing and updating the documentation. Every contribution is appreciated.
There are many ways to contribute to Icinga -- whether it be sending patches,
testing, reporting bugs, or reviewing and updating the documentation. Every
contribution is appreciated!
Please continue reading in the [contributing chapter](CONTRIBUTING.md).
d74124bd12f53d13b5a6ea5408e9783cc5190d1e 2017-01-18 15:27:53 +0100
ca7e886b820f0145999fdf7377f5c4132301eada 2018-04-27 10:24:56 +0200
......@@ -3,6 +3,8 @@
namespace Icinga\Clicommands;
use Icinga\Application\Logger;
use Icinga\Application\Modules\Manager;
use Icinga\Cli\Command;
/**
......@@ -14,6 +16,9 @@ use Icinga\Cli\Command;
*/
class ModuleCommand extends Command
{
/**
* @var Manager
*/
protected $modules;
public function init()
......@@ -73,7 +78,9 @@ class ModuleCommand extends Command
"%-14s %-9s %-9s %s\n",
$module,
$mod->getVersion(),
($type === 'enabled' || $this->modules->hasEnabled($module)) ? 'enabled' : 'disabled',
($type === 'enabled' || $this->modules->hasEnabled($module))
? $this->modules->hasInstalled($module) ? 'enabled' : 'dangling'
: 'disabled',
$dir
);
}
......@@ -109,7 +116,12 @@ class ModuleCommand extends Command
if (! $module || $this->hasRemainingParams()) {
return $this->showUsage();
}
$this->modules->disableModule($module);
if ($this->modules->hasEnabled($module)) {
$this->modules->disableModule($module);
} else {
Logger::info('Module "%s" is already disabled', $module);
}
}
/**
......
......@@ -9,6 +9,25 @@ use Icinga\Exception\IcingaException;
class WebCommand extends Command
{
/**
* Serve Icinga Web 2 with PHP's built-in web server
*
* USAGE
*
* icingacli web serve [options] [<document-root>]
*
* OPTIONS
*
* --daemonize Run in background
* --port=<port> The port to listen on
* --listen=<host:port> The address to listen on
* <document-root> The document root directory of Icinga Web 2 (e.g. ./public)
*
* EXAMPLES
*
* icingacli web serve --port=8080
* icingacli web serve --listen=127.0.0.1:8080 ./public
*/
public function serveAction()
{
$minVersion = '5.4.0';
......@@ -21,12 +40,17 @@ class WebCommand extends Command
}
$fork = $this->params->get('daemonize');
$listen = $this->params->get('listen');
$port = $this->params->get('port');
$documentRoot = $this->params->shift();
$socket = $this->params->shift();
if ($listen === null) {
$socket = $port === null ? $this->params->shift() : '0.0.0.0:' . $port;
} else {
$socket = $listen;
}
// TODO: Sanity check!!
if ($socket === null) {
$socket = $this->Config()->get('standalone','listen','0.0.0.0:80');
$socket = $this->Config()->get('standalone', 'listen', '0.0.0.0:80');
}
if ($documentRoot === null) {
$documentRoot = Icinga::app()->getBaseDir('public');
......@@ -68,7 +92,7 @@ class WebCommand extends Command
$pid = pcntl_fork();
if ($pid == -1) {
throw new IcingaException('Could not fork');
} else if ($pid) {
} elseif ($pid) {
echo $this->screen->colorize('[OK]')
. " Icinga Web server forked successfully\n";
fclose(STDIN);
......
......@@ -30,7 +30,7 @@ class AnnouncementsController extends Controller
$repo = new AnnouncementIniRepository();
$this->view->announcements = $repo
->select(array('id', 'author', 'message', 'start', 'end'))
->order('start');
->order('start', 'DESC');
}
/**
......
......@@ -14,43 +14,48 @@ use Icinga\Web\Session;
*/
class ApplicationStateController extends Controller
{
protected $requiresAuthentication = false;
public function indexAction()
{
$this->_helper->layout()->disableLayout();
if (isset($_COOKIE['icingaweb2-session'])) {
$last = (int) $_COOKIE['icingaweb2-session'];
} else {
$last = 0;
}
$now = time();
if ($last + 600 < $now) {
Session::getSession()->write();
$params = session_get_cookie_params();
setcookie(
'icingaweb2-session',
$now,
null,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
$_COOKIE['icingaweb2-session'] = $now;
}
$announcementCookie = new AnnouncementCookie();
$announcementRepo = new AnnouncementIniRepository();
if ($announcementCookie->getEtag() !== $announcementRepo->getEtag()) {
$announcementCookie
->setEtag($announcementRepo->getEtag())
->setNextActive($announcementRepo->findNextActive());
$this->getResponse()->setCookie($announcementCookie);
$this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true);
} else {
$nextActive = $announcementCookie->getNextActive();
if ($nextActive && $nextActive <= $now) {
$announcementCookie->setNextActive($announcementRepo->findNextActive());
if ($this->Auth()->isAuthenticated()) {
if (isset($_COOKIE['icingaweb2-session'])) {
$last = (int) $_COOKIE['icingaweb2-session'];
} else {
$last = 0;
}
$now = time();
if ($last + 600 < $now) {
Session::getSession()->write();
$params = session_get_cookie_params();
setcookie(
'icingaweb2-session',
$now,
null,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
$_COOKIE['icingaweb2-session'] = $now;
}
$announcementCookie = new AnnouncementCookie();
$announcementRepo = new AnnouncementIniRepository();
if ($announcementCookie->getEtag() !== $announcementRepo->getEtag()) {
$announcementCookie
->setEtag($announcementRepo->getEtag())
->setNextActive($announcementRepo->findNextActive());
$this->getResponse()->setCookie($announcementCookie);
$this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true);
} else {
$nextActive = $announcementCookie->getNextActive();
if ($nextActive && $nextActive <= $now) {
$announcementCookie->setNextActive($announcementRepo->findNextActive());
$this->getResponse()->setCookie($announcementCookie);
$this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true);
}
}
}
......
......@@ -3,6 +3,7 @@
namespace Icinga\Controllers;
use Icinga\Application\Hook\AuthenticationHook;
use Icinga\Application\Icinga;
use Icinga\Forms\Authentication\LoginForm;
use Icinga\Web\Controller;
......@@ -35,6 +36,9 @@ class AuthenticationController extends Controller
}
$form = new LoginForm();
if ($this->Auth()->isAuthenticated()) {
// Call provided AuthenticationHook(s) when login action is called
// but icinga web user is already authenticated
AuthenticationHook::triggerLogin($this->Auth()->getUser());
$this->redirectNow($form->getRedirectUrl());
}
if (! $requiresSetup) {
......@@ -66,6 +70,8 @@ class AuthenticationController extends Controller
// Get info whether the user is externally authenticated before removing authorization which destroys the
// session and the user object
$isExternalUser = $auth->getUser()->isExternalUser();
// Call provided AuthenticationHook(s) when logout action is called
AuthenticationHook::triggerLogout($auth->getUser());
$auth->removeAuthorization();
if ($isExternalUser) {
$this->getResponse()->setHttpResponseCode(401);
......
......@@ -102,6 +102,7 @@ class ConfigController extends Controller
$this->view->modules = Icinga::app()->getModuleManager()->select()
->from('modules')
->order('enabled', 'desc')
->order('installed', 'asc')
->order('name');
$this->setupLimitControl();
$this->setupPaginationControl($this->view->modules);
......@@ -113,7 +114,7 @@ class ConfigController extends Controller
$app = Icinga::app();
$manager = $app->getModuleManager();
$name = $this->getParam('name');
if ($manager->hasInstalled($name)) {
if ($manager->hasInstalled($name) || $manager->hasEnabled($name)) {
$this->view->moduleData = $manager->select()->from('modules')->where('name', $name)->fetchRow();
if ($manager->hasLoaded($name)) {
$module = $manager->getModule($name);
......@@ -309,7 +310,10 @@ class ConfigController extends Controller
public function resourceAction()
{
$this->assertPermission('config/application/resources');
$this->view->resources = Config::app('resources', true);
$this->view->resources = Config::app('resources', true)->getConfigObject()
->setKeyColumn('name')
->select()
->order('name');
$this->createApplicationTabs()->activate('resource');
}
......
......@@ -6,6 +6,7 @@ namespace Icinga\Controllers;
use Exception;
use Zend_Controller_Action_Exception;
use Icinga\Exception\ProgrammingError;
use Icinga\Exception\Http\HttpNotFoundException;
use Icinga\Forms\ConfirmRemovalForm;
use Icinga\Forms\Dashboard\DashletForm;
use Icinga\Web\Controller\ActionController;
......@@ -181,7 +182,7 @@ class DashboardController extends ActionController
try {
$dashboardConfig->saveIni();
Notification::success(t('Dashlet has been removed from') . ' ' . $pane->getTitle());
} catch (Exception $e) {
} catch (Exception $e) {
$action->view->error = $e;
$action->view->config = $dashboardConfig;
$action->render('error');
......@@ -197,6 +198,62 @@ class DashboardController extends ActionController
$this->view->form = $form;
}
public function renamePaneAction()
{
$paneName = $this->params->getRequired('pane');
if (! $this->dashboard->hasPane($paneName)) {
throw new HttpNotFoundException('Pane not found');
}
$form = new Form();
$form->setRedirectUrl('dashboard/settings');
$form->setSubmitLabel($this->translate('Update Pane'));
$form->addElement(
'text',
'name',
array(
'required' => true,
'label' => $this->translate('Name')
)
);
$form->addElement(
'text',
'title',
array(
'required' => true,
'label' => $this->translate('Title')
)
);
$form->setDefaults(array(
'name' => $paneName,
'title' => $this->dashboard->getPane($paneName)->getTitle()
));
$form->setOnSuccess(function ($form) use ($paneName) {
$newName = $form->getValue('name');
$newTitle = $form->getValue('title');
$pane = $this->dashboard->getPane($paneName);
$pane->setName($newName);
$pane->setTitle($newTitle);
$this->dashboard->getConfig()->saveIni();
Notification::success(
sprintf($this->translate('Pane "%s" successfully renamed to "%s"'), $paneName, $newName)
);
});
$form->handleRequest();
$this->view->form = $form;
$this->getTabs()->add(
'update-pane',
array(
'title' => $this->translate('Update Pane'),
'url' => $this->getRequest()->getUrl()
)
)->activate('update-pane');
}
public function removePaneAction()
{
$form = new ConfirmRemovalForm();
......@@ -217,7 +274,7 @@ class DashboardController extends ActionController
try {
$dashboardConfig->saveIni();
Notification::success(t('Dashboard has been removed') . ': ' . $pane->getTitle());
} catch (Exception $e) {
} catch (Exception $e) {
$action->view->error = $e;
$action->view->config = $dashboardConfig;
$action->render('error');
......
......@@ -3,12 +3,11 @@
namespace Icinga\Controllers;
use Icinga\Exception\IcingaException;
use Zend_Controller_Plugin_ErrorHandler;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
use Icinga\Exception\Http\HttpBadRequestException;
use Icinga\Exception\Http\HttpMethodNotAllowedException;
use Icinga\Exception\Http\HttpNotFoundException;
use Icinga\Exception\Http\HttpExceptionInterface;
use Icinga\Exception\MissingParameterException;
use Icinga\Security\SecurityException;
use Icinga\Web\Controller\ActionController;
......@@ -24,6 +23,14 @@ class ErrorController extends ActionController
*/
protected $requiresAuthentication = false;
/**
* {@inheritdoc}
*/
public function init()
{
$this->rerenderLayout = $this->params->has('renderLayout');
}
/**
* Display exception
*/
......@@ -46,10 +53,10 @@ class ErrorController extends ActionController
$path = preg_split('~/~', $path);
$path = array_shift($path);
$this->getResponse()->setHttpResponseCode(404);
$this->view->message = $this->translate('Page not found.');
$this->view->messages = array($this->translate('Page not found.'));
if ($isAuthenticated) {
if ($modules->hasInstalled($path) && ! $modules->hasEnabled($path)) {
$this->view->message .= ' ' . sprintf(
$this->view->messages[0] .= ' ' . sprintf(
$this->translate('Enabling the "%s" module might help!'),
$path
);
......@@ -59,12 +66,11 @@ class ErrorController extends ActionController
break;
default:
switch (true) {
case $exception instanceof HttpMethodNotAllowedException:
$this->getResponse()->setHttpResponseCode(405);
$this->getResponse()->setHeader('Allow', $exception->getAllowedMethods());
break;
case $exception instanceof HttpNotFoundException:
$this->getResponse()->setHttpResponseCode(404);
case $exception instanceof HttpExceptionInterface:
$this->getResponse()->setHttpResponseCode($exception->getStatusCode());
foreach ($exception->getHeaders() as $name => $value) {
$this->getResponse()->setHeader($name, $value, true);
}
break;
case $exception instanceof MissingParameterException:
$this->getResponse()->setHttpResponseCode(400);
......@@ -73,20 +79,30 @@ class ErrorController extends ActionController
'Missing parameter ' . $exception->getParameter()
);
break;
case $exception instanceof HttpBadRequestException:
$this->getResponse()->setHttpResponseCode(400);
break;
case $exception instanceof SecurityException:
$this->getResponse()->setHttpResponseCode(403);
break;
default:
$this->getResponse()->setHttpResponseCode(500);
Logger::error("%s\n%s", $exception, $exception->getTraceAsString());
Logger::error("%s\n%s", $exception, IcingaException::getConfidentialTraceAsString($exception));
break;
}
$this->view->message = $exception->getMessage();
$this->view->messages = array();
if ($this->getInvokeArg('displayExceptions')) {
$this->view->stackTrace = $exception->getTraceAsString();
$this->view->stackTraces = array();
do {
$this->view->messages[] = $exception->getMessage();
$this->view->stackTraces[] = IcingaException::getConfidentialTraceAsString($exception);
$exception = $exception->getPrevious();
} while ($exception !== null);
} else {
do {
$this->view->messages[] = $exception->getMessage();
$exception = $exception->getPrevious();
} while ($exception !== null);
}
break;
......@@ -94,7 +110,7 @@ class ErrorController extends ActionController
if ($this->getRequest()->isApiRequest()) {
$this->getResponse()->json()
->setErrorMessage($this->view->message)
->setErrorMessage($this->view->messages[0])
->sendResponse();
}
......
......@@ -5,12 +5,14 @@ namespace Icinga\Controllers;
use Exception;
use Icinga\Application\Logger;
use Icinga\Authentication\User\DomainAwareInterface;
use Icinga\Data\DataArray\ArrayDatasource;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Reducible;
use Icinga\Exception\NotFoundError;
use Icinga\Forms\Config\UserGroup\AddMemberForm;
use Icinga\Forms\Config\UserGroup\UserGroupForm;
use Icinga\User;
use Icinga\Web\Controller\AuthBackendController;
use Icinga\Web\Form;
use Icinga\Web\Notification;
......@@ -27,7 +29,9 @@ class GroupController extends AuthBackendController
$this->assertPermission('config/authentication/groups/show');
$this->createListTabs()->activate('group/list');
$backendNames = array_map(
function ($b) { return $b->getName(); },
function ($b) {
return $b->getName();
},
$this->loadUserGroupBackends('Icinga\Data\Selectable')
);
if (empty($backendNames)) {
......@@ -295,8 +299,27 @@ class GroupController extends AuthBackendController
$users = array();
foreach ($this->loadUserBackends('Icinga\Data\Selectable') as $backend) {
try {
foreach ($backend->select(array('user_name')) as $row) {
$users[] = $row;
if ($backend instanceof DomainAwareInterface) {
$domain = $backend->getDomain();
} else {
$domain = null;
}
foreach ($backend->select(array('user_name')) as $user) {
$userObj = new User($user->user_name);
if ($domain !== null) {
if ($userObj->hasDomain() && $userObj->getDomain() !== $domain) {
// Users listed in a user backend which is configured to be responsible for a domain should
// not have a domain in their username. Ultimately, if the username has a domain, it must
// not differ from the backend's domain. We could log here - but hey, who cares :)
continue;
} else {
$userObj->setDomain($domain);
}
}
$user->user_name = $userObj->getUsername();
$users[] = $user;
}
} catch (Exception $e) {
Logger::error($e);
......
......@@ -27,10 +27,7 @@ class ListController extends Controller
{
$this->getTabs()->add($action, array(
'label' => ucfirst($action),
'url' => Url::fromPath(
'list/'
. str_replace(' ', '', $action)
)
'url' => Url::fromPath('list/' . str_replace(' ', '', $action))
))->extend(new OutputFormat())->extend(new DashboardAction())->extend(new MenuAction())->activate($action);
}
......
......@@ -5,6 +5,7 @@ namespace Icinga\Controllers;
use Exception;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
use Icinga\Exception\NotFoundError;
use Icinga\Data\DataArray\ArrayDatasource;
use Icinga\Data\Filter\FilterMatchCaseInsensitive;
......@@ -407,4 +408,25 @@ class NavigationController extends Controller
$this->httpNotFound(sprintf($this->translate('Navigation item "%s" not found'), $form->getValue('name')));
}
}
public function dashboardAction()
{
$name = $this->params->getRequired('name');
$this->getTabs()->add('dashboard', array(
'active' => true,
'label' => ucwords($name),
'url' => Url::fromRequest()
));
$menu = Icinga::app()->getMenu();
$navigation = $menu->findItem($name);
if ($navigation === null) {
$this->httpNotFound($this->translate('Navigation not found'));
}
$this->view->navigation = $navigation;
}
}
......@@ -3,13 +3,12 @@
namespace Icinga\Controllers;
use Icinga\Web\Controller;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
use Icinga\Web\Controller;
use Icinga\Web\FileCache;
/**
* Delivery static content to clients
* Deliver static content to clients
*/
class StaticController extends Controller
{
......@@ -31,43 +30,52 @@ class StaticController extends Controller
public function gravatarAction()
{
$response = $this->getResponse();
$response->setHeader('Cache-Control', 'public, max-age=1814400, stale-while-revalidate=604800', true);
$noCache = $this->getRequest()->getHeader('Cache-Control') === 'no-cache'
|| $this->getRequest()->getHeader('Pragma') === 'no-cache';
$cache = FileCache::instance();
$filename = md5(strtolower(trim($this->_request->getParam('email'))));
$filename = md5(strtolower(trim($this->getParam('email'))));
$cacheFile = 'gravatar-' . $filename;
header('Cache-Control: public');
header('Pragma: cache');
if ($etag = $cache->etagMatchesCachedFile($cacheFile)) {
header("HTTP/1.1 304 Not Modified");
return;
}
header('Content-Type: image/jpg');
if ($cache->has($cacheFile)) {
header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"');
if (! $noCache && $cache->has($cacheFile, time() - 1814400)) {
if ($cache->etagMatchesCachedFile($cacheFile)) {
$response->setHttpResponseCode(304);
return;
}
$response->setHeader('Content-Type', 'image/jpg', true);
$response->setHeader('ETag', sprintf('"%s"', $cache->etagForCachedFile($cacheFile)));
$cache->send($cacheFile);
return;
}
$img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=120&d=mm');
$cache->store($cacheFile, $img);
header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"');
$response->setHeader('ETag', sprintf('"%s"', $cache->etagForCachedFile($cacheFile)));
echo $img;
}
/**
* Return an image from the application's or the module's public folder
* Return an image from a module's public folder
*/
public function imgAction()
{
// TODO(el): I think this action only retrieves images from modules
$module = $this->_getParam('module_name');
$file = $this->_getParam('file');
$basedir = Icinga::app()->getModuleManager()->getModule($module)->getBaseDir();
$moduleRoot = Icinga::app()
->getModuleManager()
->getModule($this->getParam('module_name'))
->getBaseDir();
$filePath = realpath($basedir . '/public/img/' . $file);
$file = $this->getParam('file');
$filePath = realpath($moduleRoot . '/public/img/' . $file);
if ($filePath === false) {
$this->httpNotFound('%s does not exist', $filePath);
}
if (preg_match('/\.([a-z]+)$/i', $file, $m)) {
$extension = $m[1];
if ($extension === 'svg') {
......@@ -78,12 +86,24 @@ class StaticController extends Controller
}
$s = stat($filePath);
header('Content-Type: image/' . $extension);
header(sprintf('ETag: "%x-%x-%x"', $s['ino'], $s['size'], (float) str_pad($s['mtime'], 16, '0')));
header('Cache-Control: public, max-age=3600');
header('Pragma: cache');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $s['mtime']) . ' GMT');
$eTag = sprintf('%x-%x-%x', $s['ino'], $s['size'], (float) str_pad($s['mtime'], 16, '0'));
$this->getResponse()->setHeader(
'Cache-Control',
'public, max-age=1814400, stale-while-revalidate=604800',
true
);
readfile($filePath);
if ($this->getRequest()->getServer('HTTP_IF_NONE_MATCH') === $eTag) {
$this->getResponse()
->setHttpResponseCode(304);
} else {
$this->getResponse()
->setHeader('ETag', $eTag)
->setHeader('Content-Type', 'image/' . $extension, true)
->setHeader('Last-Modified', gmdate('D, d M Y H:i:s', $s['mtime']) . ' GMT');
readfile($filePath);
}
}
}