diff --git a/.gitattributes b/.gitattributes index 35002d0cb..6ae8ee7af 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,11 @@ # Exclude files related to git when generating an archive .git* export-ignore +# Exclude Vagrant and Puppet related files when generating an archive +.puppet* export-ignore +Vagrantfile export-ignore # Normalize puppet manifests' line endings to LF on checkin and prevent conversion to CRLF when the files are checked out -.vagrant-puppet/* eol=lf +.puppet* eol=lf + +# Include version information on `git archive' +/application/VERSION export-subst diff --git a/.gitignore b/.gitignore index 3cc377b8d..6c60b5fbb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ # Except those related to git and vagrant !.git* -!.vagrant-puppet/* +!.puppet* # Exclude application log files var/log/* diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..0ce0c9686 --- /dev/null +++ b/.mailmap @@ -0,0 +1,12 @@ + + + + +Jannis Moßhammer + + + + +Thomas Gelf +Thomas Gelf +Sylph Lin diff --git a/.puppet/TODO.md b/.puppet/TODO.md new file mode 100644 index 000000000..04af9a108 --- /dev/null +++ b/.puppet/TODO.md @@ -0,0 +1,22 @@ +Fix steps that are always provisioned: + +==> default: Notice: /Stage[main]/Icinga2/Icinga2::Feature[statusdata]/Parent_dirs[/etc/icinga2/features-enabled/statusdata.conf]/Exec[parent_dirs-/etc/icinga2/features-enabled/statusdata.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2_dev/Icinga2::Config[constants]/Parent_dirs[/etc/icinga2/constants.conf]/Exec[parent_dirs-/etc/icinga2/constants.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2_dev/Icinga2::Config[conf.d/commands]/Parent_dirs[/etc/icinga2/conf.d/commands.conf]/Exec[parent_dirs-/etc/icinga2/conf.d/commands.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2/Icinga2::Feature[command]/Parent_dirs[/etc/icinga2/features-enabled/command.conf]/Exec[parent_dirs-/etc/icinga2/features-enabled/command.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Icingaweb2_dev/Pgsql::Database::Populate[icingaweb]/Exec[populate-icingaweb-pgsql-db]/returns: executed successfully +==> default: Notice: /Stage[main]/Php/Exec[php-timezone]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2_dev/Icinga2::Config[conf.d/test-config]/Parent_dirs[/etc/icinga2/conf.d/test-config.conf]/Exec[parent_dirs-/etc/icinga2/conf.d/test-config.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Icingaweb2_dev/Exec[populate-openldap]/returns: executed successfully +==> default: Notice: /Stage[main]/Monitoring_test_config/Git_cmmi[Monitoring-Generator-TestConfig]/Cmmi_dir[Monitoring-Generator-TestConfig]/Exec[configure-Monitoring-Generator-TestConfig]/returns: executed successfully +==> default: Notice: /Stage[main]/Monitoring_test_config/Git_cmmi[Monitoring-Generator-TestConfig]/Cmmi_dir[Monitoring-Generator-TestConfig]/Exec[make-Monitoring-Generator-TestConfig]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2/Icinga2::Feature[compatlog]/Parent_dirs[/etc/icinga2/features-enabled/compatlog.conf]/Exec[parent_dirs-/etc/icinga2/features-enabled/compatlog.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2_mysql/Icinga2::Feature[ido-mysql]/Parent_dirs[/etc/icinga2/features-enabled/ido-mysql.conf]/Exec[parent_dirs-/etc/icinga2/features-enabled/ido-mysql.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Apache/Service[httpd]: Triggered 'refresh' from 1 events +==> default: Notice: /Stage[main]/Icingaweb2_dev/Exec[enable-monitoring-module]/returns: executed successfully +==> default: Notice: /Stage[main]/Icingaweb2_dev/Exec[enable-test-module]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2_mysql/Icinga2::Feature[ido-mysql]/Icinga2::Config[features-available/ido-mysql]/Parent_dirs[/etc/icinga2/features-available/ido-mysql.conf]/Exec[parent_dirs-/etc/icinga2/features-available/ido-mysql.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2_pgsql/Icinga2::Feature[ido-pgsql]/Icinga2::Config[features-available/ido-pgsql]/Parent_dirs[/etc/icinga2/features-available/ido-pgsql.conf]/Exec[parent_dirs-/etc/icinga2/features-available/ido-pgsql.conf]/returns: executed successfully +==> default: Notice: /Stage[main]/Icinga2_pgsql/Icinga2::Feature[ido-pgsql]/Parent_dirs[/etc/icinga2/features-enabled/ido-pgsql.conf]/Exec[parent_dirs-/etc/icinga2/features-enabled/ido-pgsql.conf]/returns: executed successfully + +Fix provisioning for CentOS 7 diff --git a/.vagrant-puppet/files/etc/profile.d/env.sh b/.puppet/files/etc/profile.d/env.sh similarity index 100% rename from .vagrant-puppet/files/etc/profile.d/env.sh rename to .puppet/files/etc/profile.d/env.sh diff --git a/.puppet/hiera/common.yaml b/.puppet/hiera/common.yaml new file mode 100644 index 000000000..d7802181f --- /dev/null +++ b/.puppet/hiera/common.yaml @@ -0,0 +1,8 @@ +--- +icingaweb2::config: /etc/icingaweb2 +icingaweb2::log: /var/log/icingaweb2/icingaweb2.log +icingaweb2::web_path: icingaweb2 +icingaweb2::db_user: icingaweb2 +icingaweb2::db_pass: icingaweb2 +icingaweb2::db_name: icingaweb2 +icingaweb2::group: icingaweb2 diff --git a/.puppet/hiera/hiera.yaml b/.puppet/hiera/hiera.yaml new file mode 100644 index 000000000..22012bbd7 --- /dev/null +++ b/.puppet/hiera/hiera.yaml @@ -0,0 +1,9 @@ +--- +:backends: + - yaml + +:hierarchy: + - common + +:yaml: + :datadir: /vagrant/.puppet/hiera diff --git a/.puppet/manifests/puppet.sh b/.puppet/manifests/puppet.sh new file mode 100644 index 000000000..01463be51 --- /dev/null +++ b/.puppet/manifests/puppet.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e + +if which puppet >/dev/null 2>&1; then + exit 0 +fi + +RELEASEVER=$(rpm -q --qf "%{VERSION}" $(rpm -q --whatprovides redhat-release)) + +case $RELEASEVER in + 6|7) + PUPPET="http://yum.puppetlabs.com/puppetlabs-release-el-${RELEASEVER}.noarch.rpm" + ;; + *) + echo "Unknown release version: $RELEASEVER" >&2 + exit 1 + ;; +esac + +echo "Adding puppet repository.." +rpm --import "https://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs" +rpm -Uvh $PUPPET >/dev/null + +echo "Installing puppet.." +yum install -y puppet >/dev/null diff --git a/.puppet/manifests/site.pp b/.puppet/manifests/site.pp new file mode 100644 index 000000000..043f24dbb --- /dev/null +++ b/.puppet/manifests/site.pp @@ -0,0 +1,17 @@ +stage { 'repositories': + before => Stage['main'], +} + +node default { + class { 'epel': + stage => repositories, + } + include icinga2_dev + include icingaweb2_dev + include motd + file { '/etc/profile.d/env.sh': + source => 'puppet:////vagrant/.puppet/files/etc/profile.d/env.sh' + } + @user { vagrant: ensure => present } + User <| title == vagrant |> { groups +> hiera('icingaweb2::group') } +} diff --git a/.vagrant-puppet/modules/apache/manifests/init.pp b/.puppet/modules/apache/manifests/init.pp similarity index 56% rename from .vagrant-puppet/modules/apache/manifests/init.pp rename to .puppet/modules/apache/manifests/init.pp index e328ff95f..e96150f12 100644 --- a/.vagrant-puppet/modules/apache/manifests/init.pp +++ b/.puppet/modules/apache/manifests/init.pp @@ -13,20 +13,31 @@ # include apache # class apache { - $apache = $::operatingsystem ? { /(Debian|Ubuntu)/ => 'apache2', /(RedHat|CentOS|Fedora)/ => 'httpd' } + $user = $::operatingsystem ? { + /(Debian|Ubuntu)/ => 'www-data', + /(RedHat|CentOS|Fedora)/ => 'apache' + } + package { $apache: - ensure => installed, - alias => 'apache' + ensure => latest, + alias => 'apache', } service { $apache: ensure => running, + enable => true, alias => 'apache', - require => Package['apache'] + require => Package['apache'], } + + @user { $user: + alias => 'apache', + } + + User <| alias == apache |> } diff --git a/.puppet/modules/cmmi_dir/manifests/init.pp b/.puppet/modules/cmmi_dir/manifests/init.pp new file mode 100644 index 000000000..33d2f7eec --- /dev/null +++ b/.puppet/modules/cmmi_dir/manifests/init.pp @@ -0,0 +1,15 @@ +define cmmi_dir ( + $configure='./configure', + $make='make && make install' +) { + Exec { + path => '/usr/bin:/bin', + cwd => "/usr/local/src/${name}", + } + + exec { "configure-${name}": + command => $configure, + } -> exec { "make-${name}": + command => $make, + } +} diff --git a/.vagrant-puppet/modules/epel/manifests/init.pp b/.puppet/modules/epel/manifests/init.pp similarity index 56% rename from .vagrant-puppet/modules/epel/manifests/init.pp rename to .puppet/modules/epel/manifests/init.pp index 65e0a2603..2792ff918 100644 --- a/.vagrant-puppet/modules/epel/manifests/init.pp +++ b/.puppet/modules/epel/manifests/init.pp @@ -15,10 +15,10 @@ class epel { yumrepo { 'epel': - mirrorlist => "http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=${::architecture}", - enabled => '0', + mirrorlist => "http://mirrors.fedoraproject.org/mirrorlist?repo=epel-${::operatingsystemmajrelease}&arch=${::architecture}", + enabled => '1', gpgcheck => '0', - descr => "Extra Packages for Enterprise Linux 6 - ${::architecture}" + descr => "Extra Packages for Enterprise Linux ${::operatingsystemmajrelease} - ${::architecture}" } } diff --git a/.puppet/modules/git/manifests/init.pp b/.puppet/modules/git/manifests/init.pp new file mode 100644 index 000000000..9143e9f69 --- /dev/null +++ b/.puppet/modules/git/manifests/init.pp @@ -0,0 +1,13 @@ +# Class: git +# +# This class installs git. +# +# Sample Usage: +# +# include git +# +class git { + package { 'git': + ensure => latest, + } +} diff --git a/.puppet/modules/git_cmmi/manifests/init.pp b/.puppet/modules/git_cmmi/manifests/init.pp new file mode 100644 index 000000000..2c5302486 --- /dev/null +++ b/.puppet/modules/git_cmmi/manifests/init.pp @@ -0,0 +1,20 @@ +define git_cmmi ( + $url, + $configure='./configure', + $make='make && make install' +) { + include git + + $srcDir = '/usr/local/src' + + exec { "git-clone-${name}": + cwd => $srcDir, + path => '/usr/bin:/bin', + unless => "test -d '${srcDir}/${name}/.git'", + command => "git clone '${url}' '${name}'", + require => Class['git'], + } -> cmmi_dir { $name: + configure => $configure, + make => $make, + } +} diff --git a/.vagrant-puppet/modules/icinga/templates/ido2db-mysql.cfg.erb b/.puppet/modules/icinga/templates/ido2db-mysql.cfg.erb similarity index 100% rename from .vagrant-puppet/modules/icinga/templates/ido2db-mysql.cfg.erb rename to .puppet/modules/icinga/templates/ido2db-mysql.cfg.erb diff --git a/.vagrant-puppet/modules/icinga/templates/ido2db-pgsql.cfg.erb b/.puppet/modules/icinga/templates/ido2db-pgsql.cfg.erb similarity index 100% rename from .vagrant-puppet/modules/icinga/templates/ido2db-pgsql.cfg.erb rename to .puppet/modules/icinga/templates/ido2db-pgsql.cfg.erb diff --git a/.puppet/modules/icinga2/manifests/config.pp b/.puppet/modules/icinga2/manifests/config.pp new file mode 100644 index 000000000..cbdba06c0 --- /dev/null +++ b/.puppet/modules/icinga2/manifests/config.pp @@ -0,0 +1,42 @@ +# Define: icinga2::config +# +# Provide Icinga 2 configuration file +# +# Parameters: +# +# [*source*] - where to take the file from +# +# Requires: +# +# icinga2 +# +# Sample Usage: +# +# icinga2::config { 'constants'; +# source => 'puppet:///modules/icinga2_dev', +# } +# +# Provide configuration file '/etc/icinga2/constants.conf' +# from 'puppet:///modules/icinga2_dev/constants.conf' +# ('/path/to/puppet/modules/icinga2_dev/files/constants.conf') +# +define icinga2::config ($source) { + include icinga2 + + $path = "/etc/icinga2/${name}.conf" + + parent_dirs { $path: + user => 'icinga', + require => [ + User['icinga'], + File['icinga2cfgDir'] + ], + } + -> file { $path: + source => "${source}/${name}.conf", + owner => 'icinga', + group => 'icinga', + notify => Service['icinga2'], + require => User['icinga'], + } +} diff --git a/.puppet/modules/icinga2/manifests/feature.pp b/.puppet/modules/icinga2/manifests/feature.pp new file mode 100644 index 000000000..12c2e6d98 --- /dev/null +++ b/.puppet/modules/icinga2/manifests/feature.pp @@ -0,0 +1,31 @@ +# Define: icinga2::feature +# +# Enable Icinga 2 feature +# +# Requires: +# +# icinga2 +# +# Sample Usage: +# +# icinga2::feature { 'example-feature'; } +# +define icinga2::feature ($ensure = 'present') { + include icinga2 + + $action = $ensure ? { + /(present)/ => 'enable', + /(absent)/ => 'disable', + } + $test = $ensure ? { + /(present)/ => '-e', + /(absent)/ => '! -e', + } + + exec { "icinga2-feature-${action}-${name}": + unless => "/usr/bin/test ${test} /etc/icinga2/features-enabled/${name}.conf", + command => "/usr/sbin/icinga2 feature ${action} ${name}", + require => Package['icinga2'], + notify => Service['icinga2'], + } +} diff --git a/.puppet/modules/icinga2/manifests/init.pp b/.puppet/modules/icinga2/manifests/init.pp new file mode 100644 index 000000000..e5b77ad75 --- /dev/null +++ b/.puppet/modules/icinga2/manifests/init.pp @@ -0,0 +1,42 @@ +# Class: icinga2 +# +# This class installs Icinga 2. +# +# Requires: +# +# icinga_packages +# icinga2::feature +# +# Sample Usage: +# +# include icinga2 +# +class icinga2 { + include icinga_packages + + package { [ + 'icinga2', 'icinga2-doc' + ]: + ensure => latest, + require => Class['icinga_packages'], + } + -> service { 'icinga2': + ensure => running, + enable => true, + require => User['icinga'], + } + + user { 'icinga': + ensure => present, + } + -> file { 'icinga2cfgDir': + path => '/etc/icinga2', + ensure => directory, + links => follow, + owner => 'icinga', + group => 'icinga', + mode => '6750', + } + + icinga2::feature { [ 'statusdata', 'command', 'compatlog' ]: } +} diff --git a/.vagrant-puppet/files/etc/icinga2/features-available/ido-mysql.conf b/.puppet/modules/icinga2_mysql/files/features-available/ido-mysql.conf similarity index 100% rename from .vagrant-puppet/files/etc/icinga2/features-available/ido-mysql.conf rename to .puppet/modules/icinga2_mysql/files/features-available/ido-mysql.conf diff --git a/.puppet/modules/icinga2_mysql/manifests/init.pp b/.puppet/modules/icinga2_mysql/manifests/init.pp new file mode 100644 index 000000000..e076af873 --- /dev/null +++ b/.puppet/modules/icinga2_mysql/manifests/init.pp @@ -0,0 +1,35 @@ +# Class: icinga2_mysql +# +# This class installs Icinga 2 and Icinga-2-IDO-MySQL and set up the database for the last one. +# +# Requires: +# +# icinga_packages +# icinga2 +# icinga2::feature +# icinga2::config +# mysql::database::populate +# +# Sample Usage: +# +# include icinga2_mysql +# +class icinga2_mysql { + include icinga2 + include icinga_packages + + package { 'icinga2-ido-mysql': + ensure => latest, + require => Class['icinga_packages'], + } + -> mysql::database::populate { 'icinga2': + username => 'icinga2', + password => 'icinga2', + privileges => 'SELECT,INSERT,UPDATE,DELETE', + schemafile => '/usr/share/icinga2-ido-mysql/schema/mysql.sql', + } + -> icinga2::config { 'features-available/ido-mysql': + source => 'puppet:///modules/icinga2_mysql', + } + -> icinga2::feature { 'ido-mysql': } +} diff --git a/.vagrant-puppet/files/etc/icinga2/features-available/ido-pgsql.conf b/.puppet/modules/icinga2_pgsql/files/features-available/ido-pgsql.conf similarity index 100% rename from .vagrant-puppet/files/etc/icinga2/features-available/ido-pgsql.conf rename to .puppet/modules/icinga2_pgsql/files/features-available/ido-pgsql.conf diff --git a/.puppet/modules/icinga2_pgsql/manifests/init.pp b/.puppet/modules/icinga2_pgsql/manifests/init.pp new file mode 100644 index 000000000..95bc71034 --- /dev/null +++ b/.puppet/modules/icinga2_pgsql/manifests/init.pp @@ -0,0 +1,18 @@ +class icinga2_pgsql { + include icinga2 + include icinga_packages + + package { 'icinga2-ido-pgsql': + ensure => latest, + require => Class['icinga_packages'], + } + -> pgsql::database::populate { 'icinga2': + username => 'icinga2', + password => 'icinga2', + schemafile => '/usr/share/icinga2-ido-pgsql/schema/pgsql.sql', + } +# Because Icinga 2 does not handle more than one IDO connection properly, The ido-pgsql will not be enabled by default. +# -> icinga2::feature { 'ido-pgsql': +# source => 'puppet:///modules/icinga2_pgsql', +# } +} diff --git a/.puppet/modules/icinga_packages/manifests/init.pp b/.puppet/modules/icinga_packages/manifests/init.pp new file mode 100644 index 000000000..b6e46960a --- /dev/null +++ b/.puppet/modules/icinga_packages/manifests/init.pp @@ -0,0 +1,17 @@ +# Class: icinga_packages +# +# This class adds the YUM repository for the Icinga packages. +# +# Sample Usage: +# +# include icinga_packages +# +class icinga_packages { + yumrepo { 'icinga_packages': + baseurl => "http://packages.icinga.org/epel/${::operatingsystemmajrelease}/snapshot/", + enabled => '1', + gpgcheck => '1', + gpgkey => 'http://packages.icinga.org/icinga.key', + descr => "Icinga Repository - ${::architecture}" + } +} diff --git a/.puppet/modules/icingacli/manifests/init.pp b/.puppet/modules/icingacli/manifests/init.pp new file mode 100644 index 000000000..701cb6e14 --- /dev/null +++ b/.puppet/modules/icingacli/manifests/init.pp @@ -0,0 +1,12 @@ +# TODO(el): This module is not reuseable because it relies on vagrant paths +class icingacli { + file { '/usr/local/bin/icingacli': + ensure => link, + target => '/vagrant/bin/icingacli', + } + + file { '/etc/bash_completion.d/icingacli': + ensure => link, + target => '/vagrant/etc/bash_completion.d/icingacli', + } +} diff --git a/.puppet/modules/icingaweb2/manifests/config.pp b/.puppet/modules/icingaweb2/manifests/config.pp new file mode 100644 index 000000000..625c47b7a --- /dev/null +++ b/.puppet/modules/icingaweb2/manifests/config.pp @@ -0,0 +1,15 @@ +class icingaweb2::config ( + $config = hiera('icingaweb2::config'), + $web_group = hiera('icingaweb2::group') +) { + group { $web_group: + ensure => present, + } + + file { [ "${config}", "${config}/enabledModules", "${config}/modules", "${config}/preferences" ]: + ensure => directory, + owner => 'root', + group => $web_group, + mode => '2770', + } +} diff --git a/.puppet/modules/icingaweb2/manifests/config/general.pp b/.puppet/modules/icingaweb2/manifests/config/general.pp new file mode 100644 index 000000000..2a10ac1c8 --- /dev/null +++ b/.puppet/modules/icingaweb2/manifests/config/general.pp @@ -0,0 +1,21 @@ +define icingaweb2::config::general ( + $source, + $config = hiera('icingaweb2::config'), + $log = hiera('icingaweb2::log'), + $web_path = hiera('icingaweb2::web_path'), + $db_user = hiera('icingaweb2::db_user'), + $db_pass = hiera('icingaweb2::db_pass'), + $db_name = hiera('icingaweb2::db_name'), + $web_group = hiera('icingaweb2::group'), + $replace = true +) { + include icingaweb2::config + + file { "${config}/${name}.ini": + content => template("${source}/${name}.ini.erb"), + owner => 'root', + group => $web_group, + mode => '0660', + replace => $replace, + } +} diff --git a/.puppet/modules/icingaweb2/manifests/config/module.pp b/.puppet/modules/icingaweb2/manifests/config/module.pp new file mode 100644 index 000000000..90d98d083 --- /dev/null +++ b/.puppet/modules/icingaweb2/manifests/config/module.pp @@ -0,0 +1,26 @@ +define icingaweb2::config::module ( + $module, + $source, + $config = hiera('icingaweb2::config'), + $web_group = hiera('icingaweb2::group'), + $replace = true +) { + include icingaweb2::config + + if ! defined(File["${config}/modules/${module}"]) { + file { "${config}/modules/${module}": + ensure => directory, + owner => 'root', + group => $web_group, + mode => '2770', + } + } + + file { "${config}/modules/${module}/${name}.ini": + source => "${source}/modules/${module}/${name}.ini", + owner => 'root', + group => $web_group, + mode => '0660', + replace => $replace, + } +} diff --git a/.vagrant-puppet/modules/monitoring-plugins/manifests/init.pp b/.puppet/modules/monitoring_plugins/manifests/init.pp similarity index 67% rename from .vagrant-puppet/modules/monitoring-plugins/manifests/init.pp rename to .puppet/modules/monitoring_plugins/manifests/init.pp index 6dc7be09d..64d080d11 100644 --- a/.vagrant-puppet/modules/monitoring-plugins/manifests/init.pp +++ b/.puppet/modules/monitoring_plugins/manifests/init.pp @@ -1,9 +1,9 @@ -class monitoring-plugins { +class monitoring_plugins { include epel # nagios plugins from epel package { 'nagios-plugins-all': - ensure => installed, + ensure => latest, require => Class['epel'] } -} \ No newline at end of file +} diff --git a/.puppet/modules/monitoring_test_config/manifests/init.pp b/.puppet/modules/monitoring_test_config/manifests/init.pp new file mode 100644 index 000000000..65ac53ec7 --- /dev/null +++ b/.puppet/modules/monitoring_test_config/manifests/init.pp @@ -0,0 +1,25 @@ +class monitoring_test_config { + package { [ + 'perl', + 'perl-Module-Install', + 'perl-CPAN', + 'perl-File-Which', + 'perl-Time-HiRes' + ]: + ensure => latest, + } + -> git_cmmi { 'Monitoring-Generator-TestConfig': + url => 'https://github.com/sni/Monitoring-Generator-TestConfig.git', + configure => 'perl Makefile.PL', + make => 'make && make test && make install', + } + -> exec { 'create_monitoring_test_config': + path => '/usr/local/bin:/usr/bin:/bin', + command => 'install -o root -g root -d /usr/local/share/misc/ && \ +create_monitoring_test_config.pl -l icinga /usr/local/share/misc/monitoring_test_config', + creates => '/usr/local/share/misc/monitoring_test_config', + } + -> monitoring_test_config::populate_plugins { [ + 'test_hostcheck.pl', 'test_servicecheck.pl' + ]: } +} diff --git a/.puppet/modules/monitoring_test_config/manifests/populate_plugins.pp b/.puppet/modules/monitoring_test_config/manifests/populate_plugins.pp new file mode 100644 index 000000000..347e4b127 --- /dev/null +++ b/.puppet/modules/monitoring_test_config/manifests/populate_plugins.pp @@ -0,0 +1,17 @@ +define monitoring_test_config::populate_plugins { + include icinga2 + include monitoring_plugins + include monitoring_test_config + + file { "/usr/lib64/nagios/plugins/${name}": + owner => 'icinga', + group => 'icinga', + source => "/usr/local/share/misc/monitoring_test_config/plugins/${name}", + require => [ + User['icinga'], + Exec['create_monitoring_test_config'], + Class['monitoring_plugins'] + ], + notify => Service['icinga2'], + } +} diff --git a/.puppet/modules/motd/files/motd b/.puppet/modules/motd/files/motd new file mode 100644 index 000000000..3e32ee517 --- /dev/null +++ b/.puppet/modules/motd/files/motd @@ -0,0 +1,19 @@ +88 88 +88 "" +88 +88 ,adPPYba, 88 8b,dPPYba, ,adPPYb,d8 ,adPPYYba, +88 a8" "" 88 88P' `"8a a8" `Y88 "" `Y8 +88 8b 88 88 88 8b 88 ,adPPPPP88 +88 "8a, ,aa 88 88 88 "8a, ,d88 88, ,88 +88 `"Ybbd8"' 88 88 88 `"YbbdP"Y8 `"8bbdP"Y8 + aa, ,88 + "Y8bbdP" + +I8, 8 ,8I 88 ad888888b, +`8b d8b d8' 88 d8" "88 + "8, ,8"8, ,8" 88 a8P + Y8 8P Y8 8P ,adPPYba, 88,dPPYba, ,d8P" + `8b d8' `8b d8' a8P_____88 88P' "8a a8P" + `8a a8' `8a a8' 8PP""""""" 88 d8 a8P' + `8a8' `8a8' "8b, ,aa 88b, ,a8" d8" + `8' `8' `"Ybbd8"' 8Y"Ybbd8"' 88888888888 diff --git a/.puppet/modules/motd/manifests/init.pp b/.puppet/modules/motd/manifests/init.pp new file mode 100644 index 000000000..acb744daf --- /dev/null +++ b/.puppet/modules/motd/manifests/init.pp @@ -0,0 +1,7 @@ +class motd { + file { '/etc/motd': + source => 'puppet:///modules/motd/motd', + owner => root, + group => root, + } +} diff --git a/.puppet/modules/mysql/manifests/database/create.pp b/.puppet/modules/mysql/manifests/database/create.pp new file mode 100644 index 000000000..fff93c6e6 --- /dev/null +++ b/.puppet/modules/mysql/manifests/database/create.pp @@ -0,0 +1,33 @@ +# Define: mysql::database::create +# +# Create a MySQL database +# +# Parameters: +# +# [*username*] - name of the user the database belongs to +# [*password*] - password of the user the database belongs to +# [*privileges*] - privileges of the user the database belongs to +# +# Requires: +# +# mysql +# +# Sample Usage: +# +# mysql::database::create { 'icinga2': +# username => 'icinga2', +# password => 'icinga2', +# privileges => 'SELECT,INSERT,UPDATE,DELETE', +# } +# +define mysql::database::create ($username, $password, $privileges) { + include mysql + + exec { "create-mysql-${name}-db": + unless => "mysql -u${username} -p${password} ${name}", + command => "mysql -uroot -e \"CREATE DATABASE ${name}; \ +GRANT ${privileges} ON ${name}.* TO ${username}@localhost \ +IDENTIFIED BY '${password}';\"", + require => Class['mysql'] + } +} diff --git a/.puppet/modules/mysql/manifests/database/populate.pp b/.puppet/modules/mysql/manifests/database/populate.pp new file mode 100644 index 000000000..dc54a0800 --- /dev/null +++ b/.puppet/modules/mysql/manifests/database/populate.pp @@ -0,0 +1,39 @@ +# Define: mysql::database::populate +# +# Create and populate a MySQL database +# +# Parameters: +# +# [*username*] - name of the user the database belongs to +# [*password*] - password of the user the database belongs to +# [*privileges*] - privileges of the user the database belongs to +# [*schemafile*] - file with the schema for the database +# +# Requires: +# +# mysql::database::create +# +# Sample Usage: +# +# mysql::database::populate { 'icinga2': +# username => 'icinga2', +# password => 'icinga2', +# privileges => 'SELECT,INSERT,UPDATE,DELETE', +# schemafile => '/usr/share/icinga2-ido-mysql/schema/mysql.sql', +# } +# +define mysql::database::populate ($username, $password, $privileges, $schemafile) { + Exec { path => '/bin:/usr/bin' } + + mysql::database::create { $name: + username => $username, + password => $password, + privileges => $privileges, + } + + exec { "populate-${name}-mysql-db": + onlyif => "mysql -u${username} -p${password} ${name} -e \"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${name}';\" 2>/dev/null |grep -qEe '^ *0 *$'", + command => "mysql -uroot ${name} < ${schemafile}", + require => Mysql::Database::Create[$name], + } +} diff --git a/.puppet/modules/mysql/manifests/init.pp b/.puppet/modules/mysql/manifests/init.pp new file mode 100644 index 000000000..612211755 --- /dev/null +++ b/.puppet/modules/mysql/manifests/init.pp @@ -0,0 +1,54 @@ +# Class: mysql +# +# This class installs the MySQL server and client software on a RHEL or CentOS +# +# Parameters: +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +# include mysql +# +class mysql { + + Exec { path => '/usr/bin' } + + if versioncmp($::operatingsystemmajrelease, '7') >= 0 { + $client_package_name = 'mariadb' + $server_package_name = 'mariadb-server' + $server_service_name = 'mariadb' + $cnf = '/etc/my.cnf.d/server.cnf' + $log_error = '/var/log/mariadb/mariadb.log' + $pid_file = '/var/run/mariadb/mariadb.pid' + } else { + $client_package_name = 'mysql' + $server_package_name = 'mysql-server' + $server_service_name = 'mysqld' + $cnf = '/etc/my.cnf' + $log_error = '/var/log/mysqld.log' + $pid_file = '/var/run/mysqld/mysqld.pid' + } + + package { [ + $client_package_name, $server_package_name, + ]: + ensure => latest, + } + + service { $server_service_name: + alias => 'mysqld', + enable => true, + ensure => running, + require => Package[$server_package_name], + } + + file { $cnf: + content => template('mysql/my.cnf.erb'), + notify => Service['mysqld'], + recurse => true, + require => Package[$server_package_name], + } +} diff --git a/.vagrant-puppet/modules/mysql/templates/my.cnf.erb b/.puppet/modules/mysql/templates/my.cnf.erb similarity index 98% rename from .vagrant-puppet/modules/mysql/templates/my.cnf.erb rename to .puppet/modules/mysql/templates/my.cnf.erb index d26583ee3..7f819af73 100644 --- a/.vagrant-puppet/modules/mysql/templates/my.cnf.erb +++ b/.puppet/modules/mysql/templates/my.cnf.erb @@ -104,8 +104,8 @@ innodb_file_per_table innodb_log_file_size = 64M [mysqld_safe] -log-error=/var/log/mysqld.log -pid-file=/var/run/mysqld/mysqld.pid +log-error=<%= @log_error %> +pid-file=<%= @pid_file %> # Increase the amount of open files allowed per process. Warning: Make # sure you have set the global system limit high enough! The high value diff --git a/.puppet/modules/openldap/manifests/init.pp b/.puppet/modules/openldap/manifests/init.pp new file mode 100644 index 000000000..a0480632e --- /dev/null +++ b/.puppet/modules/openldap/manifests/init.pp @@ -0,0 +1,34 @@ +# Class: openldap +# +# This class installs the openldap servers and clients software. +# +# Parameters: +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +# include openldap +# +class openldap { + + package { ['openldap-servers', 'openldap-clients']: + ensure => latest, + } + + service { 'slapd': + ensure => running, + require => Package['openldap-servers'], + } + + if versioncmp($::operatingsystemmajrelease, '7') >= 0 { + openldap::schema{ 'core': } + -> openldap::schema{ 'cosine': } + -> openldap::schema{ 'inetorgperson': } + -> openldap::schema{ 'nis': } + -> openldap::schema{ 'misc': } + -> openldap::schema{ 'openldap': } + } +} diff --git a/.puppet/modules/openldap/manifests/schema.pp b/.puppet/modules/openldap/manifests/schema.pp new file mode 100644 index 000000000..a98239379 --- /dev/null +++ b/.puppet/modules/openldap/manifests/schema.pp @@ -0,0 +1,24 @@ +# define: openldap::schema +# +# Install a schema. +# +# Parameters: +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +define openldap::schema { + + include openldap + + exec { "openldap-schema-${name}": + command => "ldapadd -Y EXTERNAL -H ldapi:// -f /etc/openldap/schema/${name}.ldif", + group => 'root', + require => Service['slapd'], + unless => "test -n \"$(find /etc/openldap/slapd.d/cn=config/cn=schema/ -name cn={*}${name}.ldif -print -quit)\"", + user => 'root', + } +} diff --git a/.puppet/modules/parent_dirs/manifests/init.pp b/.puppet/modules/parent_dirs/manifests/init.pp new file mode 100644 index 000000000..e5e4e38d7 --- /dev/null +++ b/.puppet/modules/parent_dirs/manifests/init.pp @@ -0,0 +1,8 @@ +# TODO(el): Remove this. It's always executed and hackish +define parent_dirs ($user = 'root') { + exec { "parent_dirs-${name}": + command => "mkdir -p \"\$(dirname \"\$(readlink -m '${name}')\")\"", + path => '/bin:/usr/bin', + user => $user, + } +} diff --git a/.puppet/modules/pgsql/manifests/database/create.pp b/.puppet/modules/pgsql/manifests/database/create.pp new file mode 100644 index 000000000..004b4c077 --- /dev/null +++ b/.puppet/modules/pgsql/manifests/database/create.pp @@ -0,0 +1,32 @@ +# Define: pgsql::database::create +# +# Create a PgSQL database +# +# Parameters: +# +# [*username*] - name of the user the database belongs to +# [*password*] - password of the user the database belongs to +# +# Requires: +# +# pgsql +# +# Sample Usage: +# +# pgsql::database::create { 'icinga2': +# username => 'icinga2', +# password => 'icinga2', +# } +# +define pgsql::database::create ($username, $password) { + include pgsql + + exec { "create-pgsql-${name}-db": + unless => "psql -tAc \"SELECT 1 FROM pg_roles WHERE rolname='${username}'\" | grep -q 1", + command => "psql -c \"CREATE ROLE ${username} WITH LOGIN PASSWORD '${password}';\" && \ +createdb -O ${username} -E UTF8 -T template0 ${name} && \ +(createlang plpgsql ${name} || true)", + user => 'postgres', + require => Class['pgsql'] + } +} diff --git a/.puppet/modules/pgsql/manifests/database/populate.pp b/.puppet/modules/pgsql/manifests/database/populate.pp new file mode 100644 index 000000000..8663ff3f1 --- /dev/null +++ b/.puppet/modules/pgsql/manifests/database/populate.pp @@ -0,0 +1,37 @@ +# Define: pgsql::database::populate +# +# Create and populate a PgSQL database +# +# Parameters: +# +# [*username*] - name of the user the database belongs to +# [*password*] - password of the user the database belongs to +# [*schemafile*] - file with the schema for the database +# +# Requires: +# +# pgsql::database::create +# +# Sample Usage: +# +# pgsql::database::populate { 'icinga2': +# username => 'icinga2', +# password => 'icinga2', +# schemafile => '/usr/share/icinga2-ido-pgsql/schema/pgsql.sql', +# } +# +define pgsql::database::populate ($username, $password, $schemafile) { + Exec { path => '/bin:/usr/bin' } + + pgsql::database::create { $name: + username => $username, + password => $password, + } + + exec { "populate-${name}-pgsql-db": + onlyif => "psql -U ${username} -d ${name} -c \"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${name}';\" 2>/dev/null |grep -qEe '^ *0 *$'", + command => "psql -U ${username} -d ${name} < ${schemafile}", + user => 'postgres', + require => Pgsql::Database::Create[$name], + } +} diff --git a/.vagrant-puppet/modules/pgsql/manifests/init.pp b/.puppet/modules/pgsql/manifests/init.pp similarity index 87% rename from .vagrant-puppet/modules/pgsql/manifests/init.pp rename to .puppet/modules/pgsql/manifests/init.pp index 36e12bb11..4b48cf895 100644 --- a/.vagrant-puppet/modules/pgsql/manifests/init.pp +++ b/.puppet/modules/pgsql/manifests/init.pp @@ -17,11 +17,10 @@ class pgsql { Exec { path => '/sbin:/bin:/usr/bin' } - package { - 'postgresql': - ensure => installed; - 'postgresql-server': - ensure => installed; + package { [ + 'postgresql', 'postgresql-server' + ]: + ensure => latest, } exec { 'initdb': diff --git a/.vagrant-puppet/modules/pgsql/templates/pg_hba.conf.erb b/.puppet/modules/pgsql/templates/pg_hba.conf.erb similarity index 86% rename from .vagrant-puppet/modules/pgsql/templates/pg_hba.conf.erb rename to .puppet/modules/pgsql/templates/pg_hba.conf.erb index f6fb19ebf..ca98bebd8 100644 --- a/.vagrant-puppet/modules/pgsql/templates/pg_hba.conf.erb +++ b/.puppet/modules/pgsql/templates/pg_hba.conf.erb @@ -71,6 +71,11 @@ local icinga icinga trust host icinga icinga 127.0.0.1/32 trust host icinga icinga ::1/128 trust +# icinga2 +local icinga2 icinga2 trust +host icinga2 icinga2 127.0.0.1/32 trust +host icinga2 icinga2 ::1/128 trust + # icinga_unittest local icinga_unittest icinga_unittest trust host icinga_unittest icinga_unittest 127.0.0.1/32 trust @@ -81,6 +86,11 @@ local icingaweb icingaweb trust host icingaweb icingaweb 127.0.0.1/32 trust host icingaweb icingaweb ::1/128 trust +# icingaweb2 +local <%= scope.function_hiera(['icingaweb2::db_user']) %> <%= scope.function_hiera(['icingaweb2::db_user']) %> trust +host <%= scope.function_hiera(['icingaweb2::db_user']) %> <%= scope.function_hiera(['icingaweb2::db_user']) %> 127.0.0.1/32 trust +host <%= scope.function_hiera(['icingaweb2::db_user']) %> <%= scope.function_hiera(['icingaweb2::db_user']) %> ::1/128 trust + # "local" is for Unix domain socket connections only local all all ident # IPv4 local connections: diff --git a/.vagrant-puppet/modules/php/manifests/extension.pp b/.puppet/modules/php/manifests/extension.pp similarity index 100% rename from .vagrant-puppet/modules/php/manifests/extension.pp rename to .puppet/modules/php/manifests/extension.pp diff --git a/.puppet/modules/php/manifests/init.pp b/.puppet/modules/php/manifests/init.pp new file mode 100644 index 000000000..d9d111b71 --- /dev/null +++ b/.puppet/modules/php/manifests/init.pp @@ -0,0 +1,28 @@ +# Class: php +# +# This class installs php. +# +# Parameters: +# +# Actions: +# +# Requires: +# +# apache +# +# Sample Usage: +# +# include php +# +class php { + + include apache + + package { 'php': + ensure => latest, + notify => Service['apache'], + require => Package['apache'], + } + + php::phpd { ['error_reporting', 'timezone', 'xdebug_settings' ]: } +} diff --git a/.puppet/modules/php/manifests/phpd.pp b/.puppet/modules/php/manifests/phpd.pp new file mode 100644 index 000000000..dab28aec1 --- /dev/null +++ b/.puppet/modules/php/manifests/phpd.pp @@ -0,0 +1,22 @@ +# define: php::phpd +# +# Provision php.d config +# +# Parameters: +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +define php::phpd { + + include php + + file { "/etc/php.d/$name.ini": + content => template("php/$name.ini.erb"), + notify => Service['apache'], + require => Package['php'], + } +} diff --git a/.vagrant-puppet/modules/php/templates/error_reporting.ini.erb b/.puppet/modules/php/templates/error_reporting.ini.erb similarity index 100% rename from .vagrant-puppet/modules/php/templates/error_reporting.ini.erb rename to .puppet/modules/php/templates/error_reporting.ini.erb diff --git a/.puppet/modules/php/templates/timezone.ini.erb b/.puppet/modules/php/templates/timezone.ini.erb new file mode 100644 index 000000000..51e82763b --- /dev/null +++ b/.puppet/modules/php/templates/timezone.ini.erb @@ -0,0 +1 @@ +date.timezone = "UTC" diff --git a/.vagrant-puppet/modules/php/templates/xdebug_settings.ini.erb b/.puppet/modules/php/templates/xdebug_settings.ini.erb similarity index 100% rename from .vagrant-puppet/modules/php/templates/xdebug_settings.ini.erb rename to .puppet/modules/php/templates/xdebug_settings.ini.erb diff --git a/.puppet/modules/php_imagick/manifests/init.pp b/.puppet/modules/php_imagick/manifests/init.pp new file mode 100644 index 000000000..5ef0453af --- /dev/null +++ b/.puppet/modules/php_imagick/manifests/init.pp @@ -0,0 +1,29 @@ +# Class: php_imagick +# +# This class installs the ImageMagick PHP module. +# +# Parameters: +# +# Actions: +# +# Requires: +# +# php +# +# Sample Usage: +# +# include php_imagick +# +class php_imagick { + include php + + $php_imagick = $::operatingsystem ? { + /(Debian|Ubuntu)/ => 'php5-imagick', + /(RedHat|CentOS|Fedora)/ => 'php-pecl-imagick', + /(SLES|OpenSuSE)/ => 'php5-imagick', + } + + package { $php_imagick: + ensure => latest, + } +} diff --git a/.puppet/modules/zend_framework/manifests/init.pp b/.puppet/modules/zend_framework/manifests/init.pp new file mode 100644 index 000000000..1e54259b1 --- /dev/null +++ b/.puppet/modules/zend_framework/manifests/init.pp @@ -0,0 +1,24 @@ +# Class: zend_framework +# +# This class installs the Zend Framework. +# +# Requires: +# +# epel +# +# Sample Usage: +# +# include zend_framework +# +class zend_framework { + include epel + + package { [ + 'php-ZendFramework', + 'php-ZendFramework-Db-Adapter-Pdo-Mysql', + 'php-ZendFramework-Db-Adapter-Pdo-Pgsql' + ]: + ensure => latest, + require => Class['epel'], + } +} diff --git a/.vagrant-puppet/files/etc/icinga2/conf.d/commands.conf b/.puppet/profiles/icinga2_dev/files/conf.d/commands.conf similarity index 100% rename from .vagrant-puppet/files/etc/icinga2/conf.d/commands.conf rename to .puppet/profiles/icinga2_dev/files/conf.d/commands.conf diff --git a/.vagrant-puppet/files/etc/icinga2/conf.d/test-config.conf b/.puppet/profiles/icinga2_dev/files/conf.d/test-config.conf similarity index 89% rename from .vagrant-puppet/files/etc/icinga2/conf.d/test-config.conf rename to .puppet/profiles/icinga2_dev/files/conf.d/test-config.conf index 2dde681bf..4e3260b85 100644 --- a/.vagrant-puppet/files/etc/icinga2/conf.d/test-config.conf +++ b/.puppet/profiles/icinga2_dev/files/conf.d/test-config.conf @@ -45,19 +45,19 @@ object HostGroup "all-hosts" { assign where true } -local host_types = ["ok", "random", "down", "up", "unreachable", "pending"] +var host_types = ["ok", "random", "down", "up", "unreachable", "pending"] -__for (host_type in host_types) { +for (host_type in host_types) { object HostGroup "all-" + host_type use (host_type) { display_name = "All " + host_type + " hosts" assign where host.vars.check_type == host_type } } -local service_types = ["ok", "warning", "critical", "unknown", "flapping", "pending"] +var service_types = ["ok", "warning", "critical", "unknown", "flapping", "pending"] // Servicegroups -__for (service_type in service_types) { +for (service_type in service_types) { object ServiceGroup "service-" + service_type use (service_type) { display_name = "All " + service_type + " services" assign where service.vars.check_type == service_type @@ -68,7 +68,7 @@ __for (service_type in service_types) { // Services // --------------------------------------------------------------------------------------------------------------------- -__function createService(service_type, num) { +function createService(service_type, num) { apply Service "service-" + service_type + "-" + string(num + 1) use (service_type) { import "generic-service" @@ -80,8 +80,8 @@ __function createService(service_type, num) { } } -__for (num in range(4)) { - __for (service_type in service_types) { +for (num in range(4)) { + for (service_type in service_types) { createService(service_type, num) } } @@ -90,7 +90,7 @@ __for (num in range(4)) { // Hosts // --------------------------------------------------------------------------------------------------------------------- -__function createHost(checkType, checkConfig, num, checkEnabled) { +function createHost(checkType, checkConfig, num, checkEnabled) { object Host "test-" + checkType + "-" + string(num + 1) use (checkEnabled, checkType, checkConfig) { import "generic-host" address = "127.0.0.1" @@ -101,7 +101,7 @@ __function createHost(checkType, checkConfig, num, checkEnabled) { } } -__for (num in range(10)) { +for (num in range(10)) { createHost("ok", [ "ok" ], num, true) createHost("random", [ "random", "flapping" ], num, true) createHost("down", [ "warning", "critical" ], num, true) diff --git a/.vagrant-puppet/files/etc/icinga2/constants.conf b/.puppet/profiles/icinga2_dev/files/constants.conf similarity index 100% rename from .vagrant-puppet/files/etc/icinga2/constants.conf rename to .puppet/profiles/icinga2_dev/files/constants.conf diff --git a/.puppet/profiles/icinga2_dev/manifests/init.pp b/.puppet/profiles/icinga2_dev/manifests/init.pp new file mode 100644 index 000000000..322480594 --- /dev/null +++ b/.puppet/profiles/icinga2_dev/manifests/init.pp @@ -0,0 +1,35 @@ +# Class: icinga2_dev +# +# This class installs Icinga 2 w/ MySQL and PostgreSQL and provides Icinga 2 test configuration. +# +# Requires: +# +# icinga2_mysql +# icinga2::config +# icinga2::feature +# +# Sample Usage: +# +# include icinga2_dev +# +class icinga2_dev { + include icinga2_mysql + include icinga2_pgsql + include monitoring_plugins + include monitoring_test_config + + icinga2::config { [ + 'conf.d/test-config', 'conf.d/commands', 'constants' + ]: + source => 'puppet:///modules/icinga2_dev', + } + + icinga2::feature { 'api': + ensure => absent, + } + + icinga2::feature { 'ido-pgsql': + ensure => absent, + require => Class['icinga2_pgsql'], + } +} diff --git a/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/backends.ini b/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/backends.ini new file mode 100644 index 000000000..55f8e3115 --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/backends.ini @@ -0,0 +1,12 @@ +[ido-mysql] +type = ido +resource = ido-mysql + +[ido-pgsql] +type = ido +resource = ido-pgsql + +[livestatus] +disabled = 1 +type = livestatus +resource = livestatus diff --git a/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/config.ini b/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/config.ini new file mode 100644 index 000000000..9b69fe86f --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/config.ini @@ -0,0 +1,2 @@ +[security] +protected_customvars = "*pw*,*pass*,community" diff --git a/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/instances.ini b/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/instances.ini new file mode 100644 index 000000000..a47b06629 --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/files/modules/monitoring/instances.ini @@ -0,0 +1,2 @@ +[icinga] +path = "/var/run/icinga2/cmd/icinga2.cmd" diff --git a/.vagrant-puppet/modules/openldap/files/db.ldif b/.puppet/profiles/icingaweb2_dev/files/openldap/db.ldif similarity index 91% rename from .vagrant-puppet/modules/openldap/files/db.ldif rename to .puppet/profiles/icingaweb2_dev/files/openldap/db.ldif index eab9b7bac..03263f9c3 100644 --- a/.vagrant-puppet/modules/openldap/files/db.ldif +++ b/.puppet/profiles/icingaweb2_dev/files/openldap/db.ldif @@ -6,7 +6,7 @@ olcRootPW: {SSHA}N/2WMqT8q7cElh7KUQz+p9TJbjmKv/u9 replace: olcRootDN olcRootDN: cn=admin,cn=config -dn: olcDatabase={2}bdb,cn=config +dn: olcDatabase={2}hdb,cn=config changetype: modify replace: olcRootPW olcRootPW: {SSHA}MxMpLBo2/TSymoIBf/Sb5iQac7Wwiur5 diff --git a/.vagrant-puppet/modules/openldap/files/dit.ldif b/.puppet/profiles/icingaweb2_dev/files/openldap/dit.ldif similarity index 100% rename from .vagrant-puppet/modules/openldap/files/dit.ldif rename to .puppet/profiles/icingaweb2_dev/files/openldap/dit.ldif diff --git a/.vagrant-puppet/modules/openldap/files/users.ldif b/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif similarity index 100% rename from .vagrant-puppet/modules/openldap/files/users.ldif rename to .puppet/profiles/icingaweb2_dev/files/openldap/users.ldif diff --git a/.puppet/profiles/icingaweb2_dev/manifests/init.pp b/.puppet/profiles/icingaweb2_dev/manifests/init.pp new file mode 100644 index 000000000..33f29df3b --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/manifests/init.pp @@ -0,0 +1,142 @@ +class icingaweb2_dev ( + $config = hiera('icingaweb2::config'), + $log = hiera('icingaweb2::log'), + $web_path = hiera('icingaweb2::web_path'), + $db_user = hiera('icingaweb2::db_user'), + $db_pass = hiera('icingaweb2::db_pass'), + $db_name = hiera('icingaweb2::db_name'), + $web_group = hiera('icingaweb2::group'), +) { + include apache + include php + include php_imagick + include icingaweb2::config + include icingacli + include icinga_packages + include openldap + + # TODO(el): Only include zend_framework. Apache does not have to be notified + class { 'zend_framework': + notify => Service['apache'], + } + + # TODO(el): icinga-gui is not a icingaweb2_dev package + package { [ 'php-gd', 'php-intl', 'php-pdo', 'php-ldap', 'php-phpunit-PHPUnit', 'icinga-gui' ]: + ensure => latest, + notify => Service['apache'], + require => Class['icinga_packages'], + } + + Exec { path => '/usr/local/bin:/usr/bin:/bin' } + + # TODO(el): Enabling/disabling modules should be a resource + User <| alias == apache |> { groups +> $web_group } + -> exec { 'enable-monitoring-module': + command => 'icingacli module enable monitoring', + user => 'apache', + require => Class[[ 'icingacli', 'apache' ]], + } + -> exec { 'enable-test-module': + command => 'icingacli module enable test', + user => 'apache' + } + + # TODO(el): 'icingacmd' is NOT a icingaweb2_dev group + group { 'icingacmd': + ensure => present, + } + + User <| alias == apache |> { groups +> 'icingacmd' } + + $log_dir = inline_template('<%= File.dirname(@log) %>') + file { $log_dir: + ensure => directory, + owner => 'root', + group => $web_group, + mode => '2775' + } + + $icingaadminSelect = "as CNT from icingaweb_user where name = \'icingaadmin\'\" |grep -qwe \'cnt=0\'" + $icingaadminInsert = "\"INSERT INTO icingaweb_user (name, active, password_hash) VALUES (\'icingaadmin\', 1, \'\\\$1\\\$JMdnEc9M\\\$FW7yapAjv0atS43NkapGo/\');\"" + + mysql::database::populate { "${db_name}": + username => "${db_user}", + password => "${db_pass}", + privileges => 'ALL', + schemafile => '/vagrant/etc/schema/mysql.schema.sql', + } + -> exec { 'mysql-icingaadmin': + onlyif => "mysql -u${db_user} -p${db_pass} ${db_name} -e \"select CONCAT(\'cnt=\', COUNT(name)) ${icingaadminSelect}", + command => "mysql -u${db_user} -p${db_pass} ${db_name} -e ${icingaadminInsert}", + } + + pgsql::database::populate { "${db_name}": + username => "${db_user}", + password => "${db_pass}", + schemafile => '/vagrant/etc/schema/pgsql.schema.sql', + } + -> exec { 'pgsql-icingaadmin': + onlyif => "psql -U ${db_user} -w -d ${db_name} -c \"select 'cnt=' || COUNT(name) ${icingaadminSelect}", + command => "psql -U ${db_user} -w -d ${db_name} -c ${icingaadminInsert}", + environment => "PGPASSWORD=${db_pass}", + } + + file { '/etc/httpd/conf.d/icingaweb.conf': + content => template("$name/icingaweb.conf.erb"), + notify => Service['apache'], + } + + icingaweb2::config::general { 'authentication': + source => $name, + } + + icingaweb2::config::general { [ 'config', 'resources', 'roles' ]: + source => $name, + replace => false, + } + + icingaweb2::config::module { [ 'backends', 'config', 'instances' ]: + module => 'monitoring', + source => 'puppet:///modules/icingaweb2_dev', + } + + # TODO(el): Should be a resource + package { 'iptables': + ensure => latest + } + -> exec { 'iptables-allow-http': + unless => 'grep -qe "-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT" /etc/sysconfig/iptables', + command => '/sbin/iptables -I INPUT 1 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT && /sbin/iptables-save > /etc/sysconfig/iptables' + } + + # TODO(el): Don't define inside a class + define openldap_file { + file { "openldap/${name}.ldif": + path => "/usr/share/openldap-servers/${name}.ldif", + source => "puppet:///modules/icingaweb2_dev/openldap/${name}.ldif", + require => Class['openldap'], + } + } + + openldap_file { [ 'db', 'dit', 'users' ]: } + + exec { 'populate-openldap': + # TODO(el): Split the command and use unless instead of trying to populate openldap everytime + command => 'sudo ldapadd -c -Y EXTERNAL -H ldapi:/// -f /usr/share/openldap-servers/db.ldif || true && \ + sudo ldapadd -c -D cn=admin,dc=icinga,dc=org -x -w admin -f /usr/share/openldap-servers/dit.ldif || true && \ + sudo ldapadd -c -D cn=admin,dc=icinga,dc=org -x -w admin -f /usr/share/openldap-servers/users.ldif || true', + require => [ + Service['slapd'], + File[[ + 'openldap/db.ldif', + 'openldap/dit.ldif', + 'openldap/users.ldif' + ]] + ], + } + + # TODO(el): Should be a module + package { 'php-deepend-Mockery': + ensure => latest, + } +} diff --git a/.puppet/profiles/icingaweb2_dev/templates/authentication.ini.erb b/.puppet/profiles/icingaweb2_dev/templates/authentication.ini.erb new file mode 100644 index 000000000..87f9f53ed --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/templates/authentication.ini.erb @@ -0,0 +1,16 @@ +[autologin] +backend = external + +[icingaweb-mysql] +backend = db +resource = icingaweb-mysql + +[icingaweb-pgsql] +backend = db +resource = icingaweb-pgsql + +[local-ldap] +backend = ldap +resource = local-ldap +user_class = inetOrgPerson +user_name_attribute = uid diff --git a/.puppet/profiles/icingaweb2_dev/templates/config.ini.erb b/.puppet/profiles/icingaweb2_dev/templates/config.ini.erb new file mode 100644 index 000000000..13ef6710f --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/templates/config.ini.erb @@ -0,0 +1,7 @@ +[logging] +log = "file" +file = "<%= @log %>" +level = DEBUG + +[preferences] +type = "ini" diff --git a/.puppet/profiles/icingaweb2_dev/templates/icingaweb.conf.erb b/.puppet/profiles/icingaweb2_dev/templates/icingaweb.conf.erb new file mode 100644 index 000000000..c8b23e97a --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/templates/icingaweb.conf.erb @@ -0,0 +1,38 @@ +Alias /<%= @web_path %> "/vagrant/public" + + + Options SymLinksIfOwnerMatch + AllowOverride None + + + # Apache 2.4 + + Require all granted + + + + + # Apache 2.2 + Order allow,deny + Allow from all + + + SetEnv ICINGAWEB_CONFIGDIR <%= @config %> + + EnableSendfile Off + + + RewriteEngine on + RewriteBase /<%= @web_path %>/ + RewriteCond %{REQUEST_FILENAME} -s [OR] + RewriteCond %{REQUEST_FILENAME} -l [OR] + RewriteCond %{REQUEST_FILENAME} -d + RewriteRule ^.*$ - [NC,L] + RewriteRule ^.*$ index.php [NC,L] + + + + DirectoryIndex error_norewrite.html + ErrorDocument 404 /error_norewrite.html + + diff --git a/.puppet/profiles/icingaweb2_dev/templates/resources.ini.erb b/.puppet/profiles/icingaweb2_dev/templates/resources.ini.erb new file mode 100644 index 000000000..8066a47a5 --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/templates/resources.ini.erb @@ -0,0 +1,43 @@ +[icingaweb-mysql] +type = db +db = mysql +host = localhost +port = 3306 +username = <%= @db_user %> +password = <%= @db_pass %> +dbname = <%= @db_name %> + +[icingaweb-pgsql] +type = db +db = pgsql +host = localhost +port = 5432 +username = <%= @db_user %> +password = <%= @db_pass %> +dbname = <%= @db_name %> + +[ido-mysql] +type = db +db = mysql +host = localhost +port = 3306 +password = icinga2 +username = icinga2 +dbname = icinga2 + +[ido-pgsql] +type = db +db = pgsql +host = localhost +port = 5432 +password = icinga2 +username = icinga2 +dbname = icinga2 + +[local-ldap] +type = ldap +hostname = localhost +port = 389 +root_dn = "ou=people,dc=icinga,dc=org" +bind_dn = "cn=admin,cn=config" +bind_pw = admin diff --git a/.puppet/profiles/icingaweb2_dev/templates/roles.ini.erb b/.puppet/profiles/icingaweb2_dev/templates/roles.ini.erb new file mode 100644 index 000000000..98a885fd0 --- /dev/null +++ b/.puppet/profiles/icingaweb2_dev/templates/roles.ini.erb @@ -0,0 +1,3 @@ +[admins] +users = icingaadmin +permissions = * diff --git a/.vagrant-puppet/files/etc/httpd/conf.d/icingaweb.conf b/.vagrant-puppet/files/etc/httpd/conf.d/icingaweb.conf deleted file mode 100644 index 0b45bea1f..000000000 --- a/.vagrant-puppet/files/etc/httpd/conf.d/icingaweb.conf +++ /dev/null @@ -1,23 +0,0 @@ -Alias /icingaweb /vagrant/public - - - Options FollowSymLinks - AllowOverride None - Order allow,deny - Allow from all - - # SetEnv ICINGAWEB_CONFIGDIR /etc/icingaweb - - EnableSendfile Off - - RewriteEngine on - RewriteBase /icingaweb/ - RewriteCond %{REQUEST_FILENAME} -s [OR] - RewriteCond %{REQUEST_FILENAME} -l [OR] - RewriteCond %{REQUEST_FILENAME} -d - RewriteRule ^.*$ - [NC,L] - RewriteRule ^.*$ index.php [NC,L] - - php_value xdebug.idekey PHPSTORM - - diff --git a/.vagrant-puppet/files/etc/icingaweb/authentication.ini b/.vagrant-puppet/files/etc/icingaweb/authentication.ini deleted file mode 100644 index 3da806df1..000000000 --- a/.vagrant-puppet/files/etc/icingaweb/authentication.ini +++ /dev/null @@ -1,15 +0,0 @@ -[autologin] -backend = autologin -; -; If you want to strip the domain -; strip_username_regexp = /\@[^$]+$/ - -[internal_ldap_authentication] -backend = ldap -resource = internal_ldap -user_class = inetOrgPerson -user_name_attribute = uid - -[internal_db_authentication] -backend = db -resource = internal_db diff --git a/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/backends.ini b/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/backends.ini deleted file mode 100644 index 6805f2e8b..000000000 --- a/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/backends.ini +++ /dev/null @@ -1,19 +0,0 @@ -[localdb] - -type = ido -resource = "ido" - -[locallive] -disabled = "1" -type = livestatus -resource = livestatus - -[localfile] -disabled = "1" -type = statusdat -resource = statusdat - -;[localfailsafe] -;enabled=false -;type = combo -;backends = localdb, locallive, localfile diff --git a/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/instances.ini b/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/instances.ini deleted file mode 100644 index 037baa8b9..000000000 --- a/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/instances.ini +++ /dev/null @@ -1,2 +0,0 @@ -[icinga] -path = "/var/run/icinga2/cmd/icinga2.cmd" diff --git a/.vagrant-puppet/files/etc/icingaweb/resources.ini b/.vagrant-puppet/files/etc/icingaweb/resources.ini deleted file mode 100644 index 3935906eb..000000000 --- a/.vagrant-puppet/files/etc/icingaweb/resources.ini +++ /dev/null @@ -1,34 +0,0 @@ -[internal_db] -type = db -db = mysql -host = localhost -port = 3306 -password = icingaweb -username = icingaweb -dbname = icingaweb - -[ido] -type = db -db = mysql -host = localhost -port = 3306 -password = icinga2 -username = icinga2 -dbname = icinga2 - -[statusdat] -type = statusdat -status_file = /usr/local/icinga-mysql/var/status.dat -object_file = /usr/local/icinga-mysql/var/objects.cache - -[livestatus] -type = livestatus -socket = /usr/local/icinga-mysql/var/rw/live - -[internal_ldap] -type = ldap -hostname = localhost -port = 389 -root_dn = "ou=people, dc=icinga, dc=org" -bind_dn = "cn=admin,cn=config" -bind_pw = admin diff --git a/.vagrant-puppet/files/etc/init.d/icinga_command_proxy b/.vagrant-puppet/files/etc/init.d/icinga_command_proxy deleted file mode 100644 index 77059981f..000000000 --- a/.vagrant-puppet/files/etc/init.d/icinga_command_proxy +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash -# -# chkconfig: 345 99 01 -# -### BEGIN INIT INFO -# Provides: icinga_command_proxy -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Should-Start: icinga -# Should-Stop: icinga -### END INIT INFO - -# Source function library. -. /etc/rc.d/init.d/functions - -PROG="icinga_command_proxy" -BIN="/usr/local/bin/icinga_command_proxy" - -if [[ -f /etc/sysconfig/$PROG ]]; then - . /etc/sysconfig/$PROG -fi - -ICINGA_CMD=${ICINGA_CMD:-"/usr/local/icinga/var/rw/icinga.cmd"} -ICINGA_MYSQL_CMD=${ICINGA_MYSQL_CMD:-"/usr/local/icinga-mysql/var/rw/icinga.cmd"} -ICINGA_PGSQL_CMD=${ICINGA_PGSQL_CMD:-"/usr/local/icinga-pgsql/var/rw/icinga.cmd"} - -LOCKFILE=${LOCKFILE:-/var/lock/subsys/$PROG} -PIDFILE=${PIDFILE:-/var/lock/subsys/$PROG/$PROG.pid} - -RETVAL=0 - -start() { - echo -n $"Starting $PROG: " - daemon --pidfile="$PIDFILE" "nohup \"$BIN\" \"$ICINGA_CMD\" \"$ICINGA_MYSQL_CMD\" \"$ICINGA_PGSQL_CMD\" >/dev/null 2>&1 &" - RETVAL=$? - echo - [ $RETVAL = 0 ] && touch "$LOCKFILE" - return $RETVAL -} - -stop() { - echo -n $"Stopping $PROG: " - killproc -p "$PIDFILE" "$BIN" - RETVAL=$? - echo - [ $RETVAL = 0 ] && rm -f "$LOCKFILE" "$PIDFILE" -} - -# See how we were called. -case "$1" in - start) - start - ;; - stop) - stop - ;; - status) - status -p "$PIDFILE" "$BIN" - RETVAL=$? - ;; - restart) - stop - start - ;; - *) - echo $"Usage: $PROG {start|stop|restart|status}" - RETVAL=2 -esac - -exit $RETVAL - diff --git a/.vagrant-puppet/files/etc/motd b/.vagrant-puppet/files/etc/motd deleted file mode 100644 index 7e6677c20..000000000 --- a/.vagrant-puppet/files/etc/motd +++ /dev/null @@ -1,16 +0,0 @@ - ______ ___ -/\__ _\ __ /'___`\ -\/_/\ \/ ___ /\_\ ___ __ __ /\_\ /\ \ - \ \ \ /'___\/\ \ /' _ `\ /'_ `\ /'__`\ \/_/// /__ - \_\ \__/\ \__/\ \ \/\ \/\ \/\ \L\ \/\ \L\.\_ // /_\ \ - /\_____\ \____\\ \_\ \_\ \_\ \____ \ \__/.\_\ /\______/ - \/_____/\/____/ \/_/\/_/\/_/\/___L\ \/__/\/_/ \/_____/ - /\____/ - \_/__/ - __ __ __ -/\ \ __/\ \ /\ \ -\ \ \/\ \ \ \ __\ \ \____ - \ \ \ \ \ \ \ /'__`\ \ '__`\ - \ \ \_/ \_\ \/\ __/\ \ \L\ \ - \ `\___x___/\ \____\\ \_,__/ - '\/__//__/ \/____/ \/___/ diff --git a/.vagrant-puppet/files/usr/local/bin/icinga_command_proxy b/.vagrant-puppet/files/usr/local/bin/icinga_command_proxy deleted file mode 100644 index de7cb157c..000000000 --- a/.vagrant-puppet/files/usr/local/bin/icinga_command_proxy +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# -# Redirect commands from pipe A to pipe B and C -# - -set -e -set -u - -ICINGA_CMD=${1:-"/usr/local/icinga/var/rw/icinga.cmd"} -ICINGA_MYSQL_CMD=${2:-"/usr/local/icinga-mysql/var/rw/icinga.cmd"} -ICINGA_PGSQL_CMD=${3:-"/usr/local/icinga-pgsql/var/rw/icinga.cmd"} - -trap 'rm -f "$ICINGA_CMD"; exit' EXIT SIGKILL - -if [[ -p "$ICINGA_CMD" ]]; then - rm -f "$ICINGA_CMD" -fi - -mkfifo -m 660 "$ICINGA_CMD" -chown icinga.icinga-cmd "$ICINGA_CMD" - -while true -do - if read COMMAND - then - if [[ -p "$ICINGA_MYSQL_CMD" ]]; then - echo "$COMMAND" > "$ICINGA_MYSQL_CMD" - else - logger -p local0.err Can\'t distribute command to the Icinga MySQL instance since its command pipe doesn\'t exist - fi - if [[ -p "$ICINGA_PGSQL_CMD" ]]; then - echo "$COMMAND" > "$ICINGA_PGSQL_CMD" - else - logger -p local0.err Can\'t distribute command to the Icinga PostgreSQL instance since its command pipe doesn\'t exist - fi - fi -done < "$ICINGA_CMD" 3> "$ICINGA_CMD" - -# Reset all traps -trap - EXIT SIGKILL - -exit 0 diff --git a/.vagrant-puppet/files/var/www/html/icingaweb/index.php b/.vagrant-puppet/files/var/www/html/icingaweb/index.php deleted file mode 100644 index 9e452ac70..000000000 --- a/.vagrant-puppet/files/var/www/html/icingaweb/index.php +++ /dev/null @@ -1,5 +0,0 @@ - '/bin:/usr/bin:/sbin:/usr/sbin' } - -$icingaVersion = '1.11.5' -$icinga2Version = '2.0.1' -$pluginVersion = '2.0' -$livestatusVersion = '1.2.4p5' -$phantomjsVersion = '1.9.1' -$casperjsVersion = '1.0.2' - -exec { 'create-mysql-icinga-db': - unless => 'mysql -uicinga -picinga icinga', - command => 'mysql -uroot -e "CREATE DATABASE icinga; \ - GRANT SELECT,INSERT,UPDATE,DELETE ON icinga.* TO icinga@localhost \ - IDENTIFIED BY \'icinga\';"', - require => Service['mysqld'] -} - -exec { 'create-mysql-icinga2-db': - unless => 'mysql -uicinga2 -picinga2 icinga2', - command => 'mysql -uroot -e "CREATE DATABASE icinga2; \ - GRANT SELECT,INSERT,UPDATE,DELETE ON icinga2.* to icinga2@localhost \ - IDENTIFIED BY \'icinga2\';"', - require => Service['mysqld'] -} - -exec{ 'create-pgsql-icinga-db': - unless => 'sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname=\'icinga\'" | grep -q 1', - command => 'sudo -u postgres psql -c "CREATE ROLE icinga WITH LOGIN PASSWORD \'icingaweb\';" && \ - sudo -u postgres createdb -O icinga -E UTF8 -T template0 icinga && \ - sudo -u postgres createlang plpgsql icinga', - require => Service['postgresql'] -} - -$icinga_packages = [ 'gcc', 'glibc', 'glibc-common', 'gd', 'gd-devel', - 'libpng', 'libpng-devel', 'net-snmp', 'net-snmp-devel', 'net-snmp-utils', - 'libdbi', 'libdbi-devel', 'libdbi-drivers', - 'libdbi-dbd-mysql', 'libdbi-dbd-pgsql' ] -package { $icinga_packages: ensure => installed } - -php::extension { ['php-mysql', 'php-pgsql', 'php-ldap']: - require => [ Class['mysql'], Class['pgsql'], Class['openldap'] ] -} - -php::extension { 'php-gd': } - -group { 'icinga-cmd': - ensure => present -} - -group { 'icingacmd': - ensure => present, - require => Package['icinga2'] -} - -user { 'icinga': - ensure => present, - groups => 'icinga-cmd', - managehome => false -} - -user { 'apache': - groups => ['icinga-cmd', 'vagrant', 'icingacmd'], - require => [ Class['apache'], Group['icinga-cmd'], Group['icingacmd'] ] -} - -cmmi { 'icinga-mysql': - url => "https://github.com/Icinga/icinga-core/releases/download/v${icingaVersion}/icinga-${icingaVersion}.tar.gz", - output => "icinga-${icingaVersion}.tar.gz", - flags => '--prefix=/usr/local/icinga-mysql --with-command-group=icinga-cmd \ - --enable-idoutils --with-init-dir=/usr/local/icinga-mysql/etc/init.d \ - --with-htmurl=/icinga-mysql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-mysql.conf \ - --with-cgiurl=/icinga-mysql/cgi-bin \ - --with-http-auth-file=/usr/share/icinga/htpasswd.users \ - --with-plugin-dir=/usr/lib64/nagios/plugins', - creates => '/usr/local/icinga-mysql', - make => 'make all && make fullinstall install-config', - require => [ User['icinga'], Class['monitoring-plugins'], Package['apache'] ], - notify => Service['apache'] -} - -file { '/etc/init.d/icinga-mysql': - source => '/usr/local/icinga-mysql/etc/init.d/icinga', - require => Cmmi['icinga-mysql'] -} - -file { '/etc/init.d/ido2db-mysql': - source => '/usr/local/icinga-mysql/etc/init.d/ido2db', - require => Cmmi['icinga-mysql'] -} - -cmmi { 'icinga-pgsql': - url => "https://github.com/Icinga/icinga-core/releases/download/v${icingaVersion}/icinga-${icingaVersion}.tar.gz", - output => "icinga-${icingaVersion}.tar.gz", - flags => '--prefix=/usr/local/icinga-pgsql \ - --with-command-group=icinga-cmd --enable-idoutils \ - --with-init-dir=/usr/local/icinga-pgsql/etc/init.d \ - --with-htmurl=/icinga-pgsql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-pgsql.conf \ - --with-cgiurl=/icinga-pgsql/cgi-bin \ - --with-http-auth-file=/usr/share/icinga/htpasswd.users \ - --with-plugin-dir=/usr/lib64/nagios/plugins', - creates => '/usr/local/icinga-pgsql', - make => 'make all && make fullinstall install-config', - require => [ User['icinga'], Class['monitoring-plugins'], Package['apache'] ], - notify => Service['apache'] -} - -file { '/etc/init.d/icinga-pgsql': - source => '/usr/local/icinga-pgsql/etc/init.d/icinga', - require => Cmmi['icinga-pgsql'] -} - -file { '/etc/init.d/ido2db-pgsql': - source => '/usr/local/icinga-pgsql/etc/init.d/ido2db', - require => Cmmi['icinga-pgsql'] -} - -exec { 'populate-icinga-mysql-db': - unless => 'mysql -uicinga -picinga icinga -e "SELECT * FROM icinga_dbversion;" &> /dev/null', - command => "mysql -uroot icinga < /usr/local/src/icinga-mysql/icinga-${icingaVersion}/module/idoutils/db/mysql/mysql.sql", - require => [ Cmmi['icinga-mysql'], Exec['create-mysql-icinga-db'] ] -} - -exec { 'populate-icinga-pgsql-db': - unless => 'psql -U icinga -d icinga -c "SELECT * FROM icinga_dbversion;" &> /dev/null', - command => "sudo -u postgres psql -U icinga -d icinga < /usr/local/src/icinga-pgsql/icinga-${icingaVersion}/module/idoutils/db/pgsql/pgsql.sql", - require => [ Cmmi['icinga-pgsql'], Exec['create-pgsql-icinga-db'] ] -} - -service { 'icinga-mysql': - ensure => running, - require => File['/etc/init.d/icinga-mysql'] -} - -service { 'ido2db-mysql': - ensure => running, - require => File['/etc/init.d/ido2db-mysql'] -} - -file { '/usr/local/icinga-mysql/etc/ido2db.cfg': - content => template('icinga/ido2db-mysql.cfg.erb'), - owner => 'icinga', - group => 'icinga', - require => Cmmi['icinga-mysql'], - notify => [ Service['icinga-mysql'], Service['ido2db-mysql'] ] -} - -file { '/usr/local/icinga-mysql/etc/idomod.cfg': - source => '/usr/local/icinga-mysql/etc/idomod.cfg-sample', - owner => 'icinga', - group => 'icinga', - require => Cmmi['icinga-mysql'], - notify => [ Service['icinga-mysql'], Service['ido2db-mysql'] ] -} - -file { '/usr/local/icinga-mysql/etc/modules/idoutils.cfg': - source => '/usr/local/icinga-mysql/etc/modules/idoutils.cfg-sample', - owner => 'icinga', - group => 'icinga', - require => Cmmi['icinga-mysql'], - notify => [ Service['icinga-mysql'], Service['ido2db-mysql'] ] -} - -service { 'icinga-pgsql': - ensure => running, - require => Cmmi['icinga-pgsql'] -} - -service { 'ido2db-pgsql': - ensure => running, - require => Cmmi['icinga-pgsql'] -} - -file { '/usr/local/icinga-pgsql/etc/ido2db.cfg': - content => template('icinga/ido2db-pgsql.cfg.erb'), - owner => 'icinga', - group => 'icinga', - require => Cmmi['icinga-pgsql'], - notify => [ Service['icinga-pgsql'], Service['ido2db-pgsql'] ] -} - -file { '/usr/local/icinga-pgsql/etc/idomod.cfg': - source => '/usr/local/icinga-pgsql/etc/idomod.cfg-sample', - owner => 'icinga', - group => 'icinga', - require => Cmmi['icinga-pgsql'], - notify => [ Service['icinga-pgsql'], Service['ido2db-pgsql'] ] -} - -file { '/usr/local/icinga-pgsql/etc/modules/idoutils.cfg': - source => '/usr/local/icinga-pgsql/etc/modules/idoutils.cfg-sample', - owner => 'icinga', - group => 'icinga', - require => Cmmi['icinga-pgsql'], - notify => [ Service['icinga-pgsql'], Service['ido2db-pgsql'] ] -} - -exec { 'iptables-allow-http': - unless => 'grep -Fxqe "-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT" /etc/sysconfig/iptables', - command => 'iptables -I INPUT 5 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT && iptables-save > /etc/sysconfig/iptables' -} - -exec { 'icinga-htpasswd': - creates => '/usr/share/icinga/htpasswd.users', - command => 'mkdir -p /usr/share/icinga && htpasswd -b -c /usr/share/icinga/htpasswd.users icingaadmin icinga', - require => Class['apache'] -} - -include monitoring-plugins - -cmmi { 'mk-livestatus': - url => "http://mathias-kettner.de/download/mk-livestatus-${livestatusVersion}.tar.gz", - output => "mk-livestatus-${livestatusVersion}.tar.gz", - flags => '--prefix=/usr/local/icinga-mysql --exec-prefix=/usr/local/icinga-mysql', - creates => '/usr/local/icinga-mysql/lib/mk-livestatus', - make => 'make && make install', - require => Cmmi['icinga-mysql'] -} - -file { '/usr/local/icinga-mysql/etc/modules/mk-livestatus.cfg': - content => template('mk-livestatus/mk-livestatus.cfg.erb'), - owner => 'icinga', - group => 'icinga', - require => Cmmi['mk-livestatus'], - notify => [ Service['icinga-mysql'], Service['ido2db-mysql'] ] -} - -file { 'openldap/db.ldif': - path => '/usr/share/openldap-servers/db.ldif', - source => 'puppet:///modules/openldap/db.ldif', - require => Class['openldap'] -} - -file { 'openldap/dit.ldif': - path => '/usr/share/openldap-servers/dit.ldif', - source => 'puppet:///modules/openldap/dit.ldif', - require => Class['openldap'] -} - -file { 'openldap/users.ldif': - path => '/usr/share/openldap-servers/users.ldif', - source => 'puppet:///modules/openldap/users.ldif', - require => Class['openldap'] -} - -exec { 'populate-openldap': - # TODO: Split the command and use unless instead of trying to populate openldap everytime - command => 'sudo ldapadd -c -Y EXTERNAL -H ldapi:/// -f /usr/share/openldap-servers/db.ldif || true && \ - sudo ldapadd -c -D cn=admin,dc=icinga,dc=org -x -w admin -f /usr/share/openldap-servers/dit.ldif || true && \ - sudo ldapadd -c -D cn=admin,dc=icinga,dc=org -x -w admin -f /usr/share/openldap-servers/users.ldif || true', - require => [ Service['slapd'], File['openldap/db.ldif'], - File['openldap/dit.ldif'], File['openldap/users.ldif'] ] -} - -class { 'phantomjs': - url => "https://phantomjs.googlecode.com/files/phantomjs-${phantomjsVersion}-linux-x86_64.tar.bz2", - output => "phantomjs-${phantomjsVersion}-linux-x86_64.tar.bz2", - creates => '/usr/local/phantomjs' -} - -class { 'casperjs': - url => "https://github.com/n1k0/casperjs/tarball/${casperjsVersion}", - output => "casperjs-${casperjsVersion}.tar.gz", - creates => '/usr/local/casperjs' -} - -file { '/etc/profile.d/env.sh': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/profile.d/env.sh' -} - -include epel - -exec { 'install PHPUnit': - command => 'yum -d 0 -e 0 -y --enablerepo=epel install php-phpunit-PHPUnit', - unless => 'rpm -qa | grep php-phpunit-PHPUnit', - require => Class['epel'] -} - -exec { 'install PHP CodeSniffer': - command => 'yum -d 0 -e 0 -y --enablerepo=epel install php-pear-PHP-CodeSniffer', - unless => 'rpm -qa | grep php-pear-PHP-CodeSniffer', - require => Class['epel'] -} - -exec { 'install nodejs': - command => 'yum -d 0 -e 0 -y --enablerepo=epel install npm', - unless => 'rpm -qa | grep ^npm', - require => Class['epel'] -} - -exec { 'install npm/mocha': - command => 'npm install -g mocha', - creates => '/usr/lib/node_modules/mocha', - require => Exec['install nodejs'] -} - -exec { 'install npm/mocha-cobertura-reporter': - command => 'npm install -g mocha-cobertura-reporter', - creates => '/usr/lib/node_modules/mocha-cobertura-reporter', - require => Exec['install npm/mocha'] -} - -exec { 'install npm/jshint': - command => 'npm install -g jshint', - creates => '/usr/lib/node_modules/jshint', - require => Exec['install nodejs'] -} - -exec { 'install npm/expect': - command => 'npm install -g expect', - creates => '/usr/lib/node_modules/expect', - require => Exec['install nodejs'] -} - -exec { 'install npm/should': - command => 'npm install -g should', - creates => '/usr/lib/node_modules/should', - require => Exec['install nodejs'] -} - -exec { 'install npm/URIjs': - command => 'npm install -g URIjs', - creates => '/usr/lib/node_modules/URIjs', - require => Exec['install nodejs'] -} - -exec { 'install php-ZendFramework': - command => 'yum -d 0 -e 0 -y --enablerepo=epel install php-ZendFramework', - unless => 'rpm -qa | grep php-ZendFramework', - require => Class['epel'] -} - -package { ['cmake', 'boost-devel', 'bison', 'flex']: - ensure => installed -} - -# icinga 2 -define icinga2::feature ($feature = $title) { - exec { "icinga2-feature-${feature}": - path => '/bin:/usr/bin:/sbin:/usr/sbin', - unless => "readlink /etc/icinga2/features-enabled/${feature}.conf", - command => "icinga2-enable-feature ${feature}", - require => [ Package['icinga2'] ], - notify => Service['icinga2'] - } -} - -yumrepo { 'icinga2-repo': - baseurl => "http://packages.icinga.org/epel/6/snapshot/", - enabled => '1', - gpgcheck => '1', - gpgkey => 'http://packages.icinga.org/icinga.key', - descr => "Icinga Repository - ${::architecture}" -} - -exec { 'install nagios-plugins-all': - command => 'yum -d 0 -e 0 -y --enablerepo=epel install nagios-plugins-all', - unless => 'rpm -qa | grep nagios-plugins-all', - require => [ Class['epel'], Package['icinga2'] ], -} - -package { 'icinga2': - ensure => latest, - require => Yumrepo['icinga2-repo'], - alias => 'icinga2' -} - -package { 'icinga2-bin': - ensure => latest, - require => [ Yumrepo['icinga2-repo'], Package['icinga2'] ], - alias => 'icinga2-bin' -} - -package { 'icinga2-doc': - ensure => latest, - require => Yumrepo['icinga2-repo'], - alias => 'icinga2-doc' -} - -# icinga 2 classic ui -package { 'icinga2-classicui-config': - ensure => latest, - before => Package["icinga-gui"], - require => [ Yumrepo['icinga2-repo'], Package['icinga2'] ], - notify => Service['apache'] -} - -package { 'icinga-gui': - ensure => latest, - require => Yumrepo['icinga2-repo'], - alias => 'icinga-gui' -} - -icinga2::feature { 'statusdata': - require => Package['icinga2-classicui-config'] -} - -icinga2::feature { 'command': - require => Package['icinga2-classicui-config'] -} - -icinga2::feature { 'compatlog': - require => Package['icinga2-classicui-config'] -} - -# icinga 2 ido mysql -package { 'icinga2-ido-mysql': - ensure => latest, - require => Yumrepo['icinga2-repo'], - alias => 'icinga2-ido-mysql' -} - -exec { 'populate-icinga2-mysql-db': - unless => 'mysql -uicinga2 -picinga2 icinga2 -e "SELECT * FROM icinga_dbversion;" &> /dev/null', - command => 'mysql -uroot icinga2 < /usr/share/icinga2-ido-mysql/schema/mysql.sql', - require => [ Exec['create-mysql-icinga2-db'], Package['icinga2-ido-mysql'] ] -} - -file { '/etc/icinga2/features-available/ido-mysql.conf': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icinga2/features-available/ido-mysql.conf', - owner => 'icinga', - group => 'icinga', - require => Package['icinga2'], - notify => Service['icinga2'] -} - -file { '/etc/icinga2/features-enabled/ido-mysql.conf': - ensure => 'link', - target => '/etc/icinga2/features-available/ido-mysql.conf', - owner => 'root', - group => 'root', - require => Package['icinga2-ido-mysql'] -} - -icinga2::feature { 'ido-mysql': - require => Exec['populate-icinga2-mysql-db'] -} - - -# icinga 2 test config -file { '/etc/icinga2/conf.d/test-config.conf': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icinga2/conf.d/test-config.conf', - owner => 'icinga', - group => 'icinga', - require => [ Package['icinga2'], Exec['create_monitoring_test_config'] ] -} - -file { '/etc/icinga2/conf.d/commands.conf': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icinga2/conf.d/commands.conf', - owner => 'icinga', - group => 'icinga', - require => Package['icinga2'] -} - -file { '/etc/icinga2/constants.conf': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icinga2/constants.conf', - owner => 'icinga', - group => 'icinga', - require => Package['icinga2'] -} - -service { 'icinga2': - ensure => running, - require => [ - Package['icinga2'], - File['/etc/icinga2/features-enabled/ido-mysql.conf'], - File['/etc/icinga2/conf.d/test-config.conf'], - File['/etc/icinga2/conf.d/commands.conf'] - ] -} - -exec { 'install php-ZendFramework-Db-Adapter-Pdo-Mysql': - command => 'yum -d 0 -e 0 -y --enablerepo=epel install php-ZendFramework-Db-Adapter-Pdo-Mysql', - unless => 'rpm -qa | grep php-ZendFramework-Db-Adapter-Pdo-Mysql', - require => Exec['install php-ZendFramework'] -} - -file { '/etc/motd': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/motd', - owner => root, - group => root -} - -user { 'vagrant': - groups => 'icinga-cmd', - require => Group['icinga-cmd'] -} - -exec { 'create-mysql-icinga_unittest-db': - unless => 'mysql -uicinga_unittest -picinga_unittest icinga_unittest', - command => 'mysql -uroot -e "CREATE DATABASE icinga_unittest; \ - GRANT ALL ON icinga_unittest.* TO icinga_unittest@localhost \ - IDENTIFIED BY \'icinga_unittest\';"', - require => Service['mysqld'] -} - -exec{ 'create-pgsql-icinga_unittest-db': - unless => 'sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname=\'icinga_unittest\'" | grep -q 1', - command => 'sudo -u postgres psql -c "CREATE ROLE icinga_unittest WITH LOGIN PASSWORD \'icinga_unittest\';" && \ - sudo -u postgres createdb -O icinga_unittest -E UTF8 -T template0 icinga_unittest && \ - sudo -u postgres createlang plpgsql icinga_unittest', - require => Service['postgresql'] -} - -exec { 'install php-ZendFramework-Db-Adapter-Pdo-Pgsql': - command => 'yum -d 0 -e 0 -y --enablerepo=epel install php-ZendFramework-Db-Adapter-Pdo-Pgsql', - unless => 'rpm -qa | grep php-ZendFramework-Db-Adapter-Pdo-Pgsql', - require => Exec['install php-ZendFramework'] -} - - -# -# Following section installs the Perl module Monitoring::Generator::TestConfig in order to create test config to -# */usr/local/share/misc/monitoring_test_config*. Then the config is copied to */etc/conf.d/test_config/* of -# both the MySQL and PostgreSQL Icinga instance -# -cpan { 'Monitoring::Generator::TestConfig': - creates => '/usr/local/share/perl5/Monitoring/Generator/TestConfig.pm', - timeout => 600 -} - -exec { 'create_monitoring_test_config': - command => 'sudo install -o root -g root -d /usr/local/share/misc/ && \ - sudo /usr/local/bin/create_monitoring_test_config.pl -l icinga \ - /usr/local/share/misc/monitoring_test_config', - creates => '/usr/local/share/misc/monitoring_test_config', - require => Cpan['Monitoring::Generator::TestConfig'] -} - -define populate_monitoring_test_config { - file { "/usr/local/icinga-mysql/etc/conf.d/test_config/${name}.cfg": - owner => 'icinga', - group => 'icinga', - source => "/usr/local/share/misc/monitoring_test_config/etc/conf.d/${name}.cfg", - notify => Service['icinga-mysql'] - } - file { "/usr/local/icinga-pgsql/etc/conf.d/test_config/${name}.cfg": - owner => 'icinga', - group => 'icinga', - source => "/usr/local/share/misc/monitoring_test_config/etc/conf.d/${name}.cfg", - notify => Service['icinga-pgsql'] - } -} - -file { '/usr/local/icinga-mysql/etc/conf.d/test_config/': - ensure => directory, - owner => icinga, - group => icinga, - require => Cmmi['icinga-mysql'] -} - -file { '/usr/local/icinga-pgsql/etc/conf.d/test_config/': - ensure => directory, - owner => icinga, - group => icinga, - require => Cmmi['icinga-pgsql'] -} - -populate_monitoring_test_config { ['commands', 'contacts', 'dependencies', - 'hostgroups', 'hosts', 'servicegroups', 'services']: - require => [ Exec['create_monitoring_test_config'], - File['/usr/local/icinga-mysql/etc/conf.d/test_config/'], - File['/usr/local/icinga-pgsql/etc/conf.d/test_config/'] ] -} - -define populate_monitoring_test_config_plugins { - file { "/usr/lib64/nagios/plugins/${name}": - owner => 'icinga', - group => 'icinga', - source => "/usr/local/share/misc/monitoring_test_config/plugins/${name}", - notify => [ Service['icinga-mysql'], Service['icinga-pgsql'] ] - } -} - -populate_monitoring_test_config_plugins{ ['test_hostcheck.pl', 'test_servicecheck.pl']: - require => [ Exec['create_monitoring_test_config'], - Cmmi['icinga-mysql'], - Cmmi['icinga-pgsql'] ] -} - -# -# Following section creates and populates MySQL and PostgreSQL Icinga Web 2 databases -# -exec { 'create-mysql-icingaweb-db': - unless => 'mysql -uicingaweb -picingaweb icingaweb', - command => 'mysql -uroot -e "CREATE DATABASE icingaweb; \ - GRANT ALL ON icingaweb.* TO icingaweb@localhost \ - IDENTIFIED BY \'icingaweb\';"', - require => Service['mysqld'] -} - -exec { 'create-pgsql-icingaweb-db': - unless => 'sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname=\'icingaweb\'" | grep -q 1', - command => 'sudo -u postgres psql -c "CREATE ROLE icingaweb WITH LOGIN PASSWORD \'icinga\';" && \ - sudo -u postgres createdb -O icingaweb -E UTF8 -T template0 icingaweb && \ - sudo -u postgres createlang plpgsql icingaweb', - require => Service['postgresql'] -} - -exec { 'populate-icingaweb-mysql-db-tables': - unless => 'mysql -uicingaweb -picingaweb icingaweb -e "SELECT * FROM icingaweb_group;" &> /dev/null', - command => 'mysql -uicingaweb -picingaweb icingaweb < /vagrant/etc/schema/mysql.schema.sql', - require => [ Exec['create-mysql-icingaweb-db'] ] -} - -exec { 'populate-icingweba-pgsql-db-tables': - unless => 'psql -U icingaweb -d icingaweb -c "SELECT * FROM icingaweb_group;" &> /dev/null', - command => 'sudo -u postgres psql -U icingaweb -d icingaweb -f /vagrant/etc/schema/pgsql.schema.sql', - require => [ Exec['create-pgsql-icingaweb-db'] ] -} - -# -# Following section creates the Icinga command proxy to /usr/local/icinga-mysql/var/rw/icinga.cmd (which is the -# config's default path for the Icinga command pipe) in order to send commands to both the MySQL and PostgreSQL instance -# -file { [ '/usr/local/icinga/', '/usr/local/icinga/var/', '/usr/local/icinga/var/rw/' ]: - ensure => directory, - owner => icinga, - group => icinga, - require => User['icinga'] -} - -file { '/usr/local/bin/icinga_command_proxy': - source => 'puppet:////vagrant/.vagrant-puppet/files/usr/local/bin/icinga_command_proxy', - owner => root, - group => root, - mode => 755 -} - -file { '/etc/init.d/icinga_command_proxy': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/init.d/icinga_command_proxy', - owner => root, - group => root, - mode => 755, - require => File['/usr/local/bin/icinga_command_proxy'] -} - -service { 'icinga_command_proxy': - ensure => running, - require => [ File['/etc/init.d/icinga_command_proxy'], Service['icinga-mysql'], Service['icinga-pgsql'] ] -} - -exec { 'create-mysql-icinga_web-db': - unless => 'mysql -uicinga_web -picinga_web icinga_web', - command => 'mysql -uroot -e "CREATE DATABASE icinga_web; \ - GRANT ALL ON icinga_web.* TO icinga_web@localhost \ - IDENTIFIED BY \'icinga_web\';"', - require => Service['mysqld'] -} - -cmmi { 'icinga-web': - url => 'http://sourceforge.net/projects/icinga/files/icinga-web/1.10.0-beta/icinga-web-1.10.0-beta.tar.gz/download', - output => 'icinga-web-1.10.0-beta.tar.gz', - flags => '--prefix=/usr/local/icinga-web', - creates => '/usr/local/icinga-web', - make => 'make install && make install-apache-config', - require => Service['icinga_command_proxy'], - notify => Service['apache'] -} - -exec { 'populate-icinga_web-mysql-db': - unless => 'mysql -uicinga_web -picinga_web icinga_web -e "SELECT * FROM nsm_user;" &> /dev/null', - command => 'mysql -uicinga_web -picinga_web icinga_web < /usr/local/src/icinga-web/icinga-web-1.10.0-beta/etc/schema/mysql.sql', - require => [ Exec['create-mysql-icinga_web-db'], Cmmi['icinga-web'] ] -} - -file { '/var/www/html/icingaweb': - ensure => absent, -} - -file { '/etc/httpd/conf.d/icingaweb.conf': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/httpd/conf.d/icingaweb.conf', - require => Package['apache'], - notify => Service['apache'] -} - -file { '/etc/icingaweb': - ensure => 'directory', - owner => 'apache', - group => 'apache' -} - -file { '/etc/icingaweb/preferences': - ensure => 'directory', - owner => 'apache', - group => 'apache', - require => File['/etc/icingaweb'] -} - -file { '/etc/icingaweb/authentication.ini': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icingaweb/authentication.ini', - owner => 'apache', - group => 'apache', - require => File['/etc/icingaweb'] -} - -file { '/etc/icingaweb/config.ini': - ensure => file, - owner => 'apache', - group => 'apache', -} - -file { '/etc/icingaweb/resources.ini': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icingaweb/resources.ini', - owner => 'apache', - group => 'apache', - replace => false -} - -file { ['/etc/icingaweb/enabledModules', '/etc/icingaweb/modules', '/etc/icingaweb/modules/monitoring']: - ensure => 'directory', - owner => 'apache', - group => 'apache', -} - -file { '/etc/icingaweb/modules/monitoring/backends.ini': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/backends.ini', - owner => 'apache', - group => 'apache', -} - -file { '/etc/icingaweb/modules/monitoring/config.ini': - source => 'puppet:////vagrant/config/modules/monitoring/config.ini', - owner => 'apache', - group => 'apache', -} - -file { '/etc/icingaweb/modules/monitoring/instances.ini': - source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/instances.ini', - owner => 'apache', - group => 'apache', -} - -# pear::package { 'deepend/Mockery': -# channel => 'pear.survivethedeepend.com' -# } - -# icingacli -file { '/usr/local/bin/icingacli': - ensure => 'link', - target => '/vagrant/bin/icingacli', - owner => 'apache', - group => 'apache', - require => [ File['/etc/icingaweb'], File['/etc/bash_completion.d/icingacli'] ] -} - -exec { 'install bash-completion': - command => 'yum -d 0 -e 0 -y --enablerepo=epel install bash-completion', - unless => 'rpm -qa | grep bash-completion', - require => Class['epel'] -} - -file { '/etc/bash_completion.d/icingacli': - source => 'puppet:////vagrant/etc/bash_completion.d/icingacli', - owner => 'root', - group => 'root', - mode => 755, - require => Exec['install bash-completion'] -} diff --git a/.vagrant-puppet/manifests/finalize.sh b/.vagrant-puppet/manifests/finalize.sh deleted file mode 100644 index 3b1418575..000000000 --- a/.vagrant-puppet/manifests/finalize.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -set -e - -installJquery () { - # The npm module jquery won't install via puppet because of an mysterious error - # when node-gyp rebuilding the dependent contextify module - if [ ! -d /usr/lib/node_modules/jquery ]; then - npm install --silent -g jquery - fi -} - -startServicesWithNonLSBCompliantExitStatusCodes () { - # Unfortunately the ido2db init script is not LSB compliant and hence not started via puppet - service ido2db-mysql start || true - service ido2db-pgsql start || true -} - -mountIcinga2webVarLog () { - if ! $(/bin/mount | /bin/grep -q "/vagrant/var/log"); then - # Remount /vagrant/var/log/ with appropriate permissions since the group apache is missing initially - /bin/mount -t vboxsf -o \ - uid=`id -u vagrant`,gid=`id -g apache`,dmode=775,fmode=664 \ - /vagrant/var/log/ \ - /vagrant/var/log/ - fi -} - -installJquery -startServicesWithNonLSBCompliantExitStatusCodes -mountIcinga2webVarLog - -exit 0 diff --git a/.vagrant-puppet/modules/casperjs/manifests/init.pp b/.vagrant-puppet/modules/casperjs/manifests/init.pp deleted file mode 100644 index fd54e37f9..000000000 --- a/.vagrant-puppet/modules/casperjs/manifests/init.pp +++ /dev/null @@ -1,66 +0,0 @@ -# Class: casperjs -# -# This module downloads, extracts, and installs casperjs tar.gz archives -# using wget and tar. -# -# Parameters: -# [*url*] - fetch archive via wget from this url. -# [*output*] - filename to fetch the archive into. -# [*creates*] - target directory the software will install to. -# -# Actions: -# -# Requires: -# -# Sample Usage: -# -# class {'casperjs': -# url => 'https://github.com/n1k0/casperjs/tarball/1.0.2', -# output => 'casperjs-1.0.2.tar.gz', -# creates => '/usr/local/casperjs' -# } -# -class casperjs( - $url, - $output, - $creates -) { - - Exec { path => '/usr/bin:/bin' } - - $cwd = '/usr/local/src' - - include wget - - exec { 'download-casperjs': - cwd => $cwd, - command => "wget -q ${url} -O ${output}", - creates => "${cwd}/${output}", - timeout => 120, - require => Class['wget'] - } - - $tld = inline_template('<%= File.basename(@output, ".tar.bz2") %>') - $src = "${cwd}/casperjs" - - exec { 'extract-casperjs': - cwd => $cwd, - command => "mkdir -p casperjs && tar --no-same-owner \ - --no-same-permissions -xzf ${output} -C ${src} \ - --strip-components 1", - creates => $src, - require => Exec['download-casperjs'] - } - - file { 'install-casperjs': - path => $creates, - source => $src, - recurse => true, - require => Exec['extract-casperjs'] - } - - file { 'link-casperjs-bin': - ensure => "${creates}/bin/casperjs", - path => '/usr/local/bin/casperjs' - } -} diff --git a/.vagrant-puppet/modules/cmmi/manifests/init.pp b/.vagrant-puppet/modules/cmmi/manifests/init.pp deleted file mode 100644 index e0116fbc9..000000000 --- a/.vagrant-puppet/modules/cmmi/manifests/init.pp +++ /dev/null @@ -1,80 +0,0 @@ -# Define: cmmi -# -# This module downloads, extracts, builds and installs tar.gz archives using -# wget, tar and the autotools stack. Build directory is always /usr/local/src. -# -# *Note* make sure to install build essentials before running cmmi. -# -# Parameters: -# [*url*] - fetch archive via wget from this url. -# [*output*] - filename to fetch the archive into. -# [*flags*] - configure options. -# [*creates*] - target directory the software will install to. -# [*make* ] - command to make and make install the software. -# [*make_timeout* ] - timeout for the make command. -# -# Actions: -# -# Requires: -# -# Sample Usage: -# -# cmmi { 'example-software': -# url => 'http://example-software.com/download/', -# output => 'example-software.tar.gz', -# flags => '--prefix=/opt/example-software', -# creates => '/opt/example-software', -# make => 'make && make install' -# make_timeout => 600 -# } -# -define cmmi( - $url, - $output, - $flags='', - $creates, - $make, - $make_timeout=300, - $configure_command='sh ./configure' -) { - - Exec { path => '/bin:/usr/bin' } - - $cwd = '/usr/local/src' - - include wget - - exec { "download-${name}": - cwd => $cwd, - command => "wget -q \"${url}\" -O ${output}", - creates => "${cwd}/${output}", - require => Class['wget'] - } - - $tld = inline_template('<%= File.basename(@output, ".tar.gz") %>') - $src = "${cwd}/${name}/${tld}" - - exec { "extract-${name}": - cwd => $cwd, - command => "mkdir -p ${name}/${tld} && tar --no-same-owner \ - --no-same-permissions -xzf ${output} -C ${name}/${tld} \ - --strip-components 1", - creates => $src, - require => Exec["download-${name}"] - } - - exec { "configure-${name}": - cwd => $src, - command => "${configure_command} ${flags}", - creates => "${src}/Makefile", - require => Exec["extract-${name}"] - } - - exec { "make-${name}": - cwd => $src, - command => $make, - creates => $creates, - require => Exec["configure-${name}"], - timeout => $make_timeout - } -} diff --git a/.vagrant-puppet/modules/configure/manifests/init.pp b/.vagrant-puppet/modules/configure/manifests/init.pp deleted file mode 100644 index beeb98b17..000000000 --- a/.vagrant-puppet/modules/configure/manifests/init.pp +++ /dev/null @@ -1,17 +0,0 @@ -# Define: configure -# -# Run a gnu configure to prepare software for environment -# -# Parameters: -# [*flags*] - configure options. -# [*path*] - Target and working dir -# -define configure( - $path, - $flags -) { - exec { "configure-${name}": - cwd => $path, - command => "sh ./configure ${flags}" - } -} \ No newline at end of file diff --git a/.vagrant-puppet/modules/cpan/manifests/init.pp b/.vagrant-puppet/modules/cpan/manifests/init.pp deleted file mode 100644 index 9cbdaf8b0..000000000 --- a/.vagrant-puppet/modules/cpan/manifests/init.pp +++ /dev/null @@ -1,49 +0,0 @@ -# Define: cpan -# -# Download and install Perl modules from the Perl Archive Network, the canonical location for Perl code and modules. -# -# Parameters: -# [*creates*] - target directory the software will install to. -# [*timeout* ] - timeout for the CPAN command. -# -# Actions: -# -# Requires: -# -# Perl -# -# Sample Usage: -# -# cpan { 'perl-module': -# creates => '/usr/local/share/perl5/perl-module', -# timeout => 600 -# } -# -define cpan( - $creates, - $timeout -) { - - Exec { path => '/usr/bin' } - - package { 'perl-CPAN': - ensure => installed - } - - file { [ '/root/.cpan/', '/root/.cpan/CPAN/' ]: - ensure => directory - } - - file { '/root/.cpan/CPAN/MyConfig.pm': - content => template('cpan/MyConfig.pm.erb'), - require => [ Package['perl-CPAN'], - File[[ '/root/.cpan/', '/root/.cpan/CPAN/' ]] ] - } - - exec { "cpan-${name}": - command => "sudo perl -MCPAN -e 'install ${name}'", - creates => $creates, - require => File['/root/.cpan/CPAN/MyConfig.pm'], - timeout => $timeout - } -} diff --git a/.vagrant-puppet/modules/cpan/templates/MyConfig.pm.erb b/.vagrant-puppet/modules/cpan/templates/MyConfig.pm.erb deleted file mode 100644 index da410a188..000000000 --- a/.vagrant-puppet/modules/cpan/templates/MyConfig.pm.erb +++ /dev/null @@ -1,68 +0,0 @@ -$CPAN::Config = { - 'applypatch' => q[], - 'auto_commit' => q[0], - 'build_cache' => q[100], - 'build_dir' => q[/root/.cpan/build], - 'build_dir_reuse' => q[0], - 'build_requires_install_policy' => q[ask/yes], - 'bzip2' => q[/usr/bin/bzip2], - 'cache_metadata' => q[1], - 'check_sigs' => q[0], - 'commandnumber_in_prompt' => q[1], - 'connect_to_internet_ok' => q[1], - 'cpan_home' => q[/root/.cpan], - 'curl' => q[/usr/bin/curl], - 'ftp' => q[], - 'ftp_passive' => q[1], - 'ftp_proxy' => q[], - 'getcwd' => q[cwd], - 'gpg' => q[/usr/bin/gpg], - 'gzip' => q[/bin/gzip], - 'halt_on_failure' => q[0], - 'histfile' => q[/root/.cpan/histfile], - 'histsize' => q[100], - 'http_proxy' => q[], - 'inactivity_timeout' => q[0], - 'index_expire' => q[1], - 'inhibit_startup_message' => q[0], - 'keep_source_where' => q[/root/.cpan/sources], - 'load_module_verbosity' => q[v], - 'lynx' => q[], - 'make' => q[/usr/bin/make], - 'make_arg' => q[], - 'make_install_arg' => q[], - 'make_install_make_command' => q[/usr/bin/make], - 'makepl_arg' => q[INSTALLDIRS=site], - 'mbuild_arg' => q[], - 'mbuild_install_arg' => q[], - 'mbuild_install_build_command' => q[./Build], - 'mbuildpl_arg' => q[--installdirs site], - 'ncftp' => q[], - 'ncftpget' => q[], - 'no_proxy' => q[], - 'pager' => q[/usr/bin/less], - 'patch' => q[], - 'perl5lib_verbosity' => q[v], - 'prefer_installer' => q[MB], - 'prefs_dir' => q[/root/.cpan/prefs], - 'prerequisites_policy' => q[follow], - 'scan_cache' => q[atstart], - 'shell' => q[/bin/bash], - 'show_unparsable_versions' => q[0], - 'show_upload_date' => q[0], - 'show_zero_versions' => q[0], - 'tar' => q[/bin/tar], - 'tar_verbosity' => q[v], - 'term_is_latin' => q[1], - 'term_ornaments' => q[1], - 'test_report' => q[0], - 'trust_test_report_history' => q[0], - 'unzip' => q[/usr/bin/unzip], - 'urllist' => [], - 'use_sqlite' => q[0], - 'wget' => q[/usr/bin/wget], - 'yaml_load_code' => q[0], - 'yaml_module' => q[YAML], -}; -1; -__END__ diff --git a/.vagrant-puppet/modules/mk-livestatus/templates/mk-livestatus.cfg.erb b/.vagrant-puppet/modules/mk-livestatus/templates/mk-livestatus.cfg.erb deleted file mode 100644 index f61ffc001..000000000 --- a/.vagrant-puppet/modules/mk-livestatus/templates/mk-livestatus.cfg.erb +++ /dev/null @@ -1,6 +0,0 @@ -define module{ - module_name mklivestatus - path /usr/local/icinga-mysql/lib/mk-livestatus/livestatus.o - module_type neb - args /usr/local/icinga-mysql/var/rw/live - } diff --git a/.vagrant-puppet/modules/mysql/manifests/init.pp b/.vagrant-puppet/modules/mysql/manifests/init.pp deleted file mode 100644 index f0cab3fdb..000000000 --- a/.vagrant-puppet/modules/mysql/manifests/init.pp +++ /dev/null @@ -1,36 +0,0 @@ -# Class: mysql -# -# This class installs the mysql server and client software. -# -# Parameters: -# -# Actions: -# -# Requires: -# -# Sample Usage: -# -# include mysql -# -class mysql { - - Exec { path => '/usr/bin' } - - package { - 'mysql': - ensure => installed; - 'mysql-server': - ensure => installed; - } - - service { 'mysqld': - ensure => running, - require => Package['mysql-server'] - } - - file { '/etc/my.cnf': - content => template('mysql/my.cnf.erb'), - require => Package['mysql-server'], - notify => Service['mysqld'] - } -} diff --git a/.vagrant-puppet/modules/openldap/manifests/init.pp b/.vagrant-puppet/modules/openldap/manifests/init.pp deleted file mode 100644 index e9f3c504b..000000000 --- a/.vagrant-puppet/modules/openldap/manifests/init.pp +++ /dev/null @@ -1,25 +0,0 @@ -# Class: openldap -# -# This class installs the openldap servers and clients software. -# -# Parameters: -# -# Actions: -# -# Requires: -# -# Sample Usage: -# -# include openldap -# -class openldap { - - package { ['openldap-servers', 'openldap-clients']: - ensure => installed - } - - service { 'slapd': - ensure => running, - require => Package['openldap-servers'] - } -} diff --git a/.vagrant-puppet/modules/pear/manifests/init.pp b/.vagrant-puppet/modules/pear/manifests/init.pp deleted file mode 100644 index 0c748f2bc..000000000 --- a/.vagrant-puppet/modules/pear/manifests/init.pp +++ /dev/null @@ -1,43 +0,0 @@ -# Class: pear -# -# This class installs pear. -# -# Parameters: -# -# Actions: -# -# Requires: -# -# php -# -# Sample Usage: -# -# include pear -# -class pear { - - Exec { path => '/usr/bin:/bin' } - - include php - - package { 'php-pear': - ensure => installed, - require => Class['php'] - } - - exec { 'pear upgrade': - command => 'pear upgrade', - require => Package['php-pear'] - } - - exec { 'pear update-channels': - command => 'pear update-channels', - require => Package['php-pear'] - } - - exec { 'pear auto discover channels': - command => 'pear config-set auto_discover 1', - unless => 'pear config-get auto_discover | grep 1', - require => Package['php-pear'] - } -} diff --git a/.vagrant-puppet/modules/pear/manifests/package.pp b/.vagrant-puppet/modules/pear/manifests/package.pp deleted file mode 100644 index 90b807b3d..000000000 --- a/.vagrant-puppet/modules/pear/manifests/package.pp +++ /dev/null @@ -1,50 +0,0 @@ -# Define: pear::package -# -# Install additional PEAR packages -# -# Parameters: -# -# Actions: -# -# Requires: -# -# pear -# -# Sample Usage: -# -# pear::package { 'phpunit': } -# -define pear::package( - $channel -) { - - Exec { path => '/usr/bin' } - - include pear - - if $::require { - $require_ = [Class['pear'], $::require] - } else { - $require_ = Class['pear'] - } - - if $channel { - exec { "pear discover ${channel}": - command => "sudo pear channel-discover ${channel}", - unless => "pear channel-info ${channel}", - require => $require_, - before => Exec["pear install ${name}"] - } - } - - exec { "pear install ${name}": - command => "pear install --alldeps ${name}", - unless => "pear list ${name}", - require => $require_ - } - - exec { "pear upgrade ${name}": - command => "pear upgrade ${name}", - require => Exec["pear install ${name}"] - } -} diff --git a/.vagrant-puppet/modules/phantomjs/manifests/init.pp b/.vagrant-puppet/modules/phantomjs/manifests/init.pp deleted file mode 100644 index ea65b1f26..000000000 --- a/.vagrant-puppet/modules/phantomjs/manifests/init.pp +++ /dev/null @@ -1,65 +0,0 @@ -# Class: phantomjs -# -# This module downloads, extracts, and installs phantomjs tar.bz2 archives -# using wget and tar. -# -# Parameters: -# [*url*] - fetch archive via wget from this url. -# [*output*] - filename to fetch the archive into. -# [*creates*] - target directory the software will install to. -# -# Actions: -# -# Requires: -# -# Sample Usage: -# -# class {'phantomjs': -# url => 'https://phantomjs.googlecode.com/files/phantomjs-1.9.1-linux-x86_64.tar.bz2', -# output => 'phantomjs-1.9.1-linux-x86_64.tar.bz2', -# creates => '/usr/local/phantomjs' -# } -# -class phantomjs( - $url, - $output, - $creates -) { - - Exec { path => '/usr/bin:/bin' } - - $cwd = '/usr/local/src' - - include wget - - exec { 'download-phantomjs': - cwd => $cwd, - command => "wget -q ${url} -O ${output}", - creates => "${cwd}/${output}", - timeout => 120, - require => Class['wget'] - } - - $src = "${cwd}/phantomjs" - - exec { 'extract-phantomjs': - cwd => $cwd, - command => "mkdir -p phantomjs && tar --no-same-owner \ - --no-same-permissions -xjf ${output} -C ${src} \ - --strip-components 1", - creates => $src, - require => Exec['download-phantomjs'] - } - - file { 'install-phantomjs': - path => $creates, - source => $src, - recurse => true, - require => Exec['extract-phantomjs'] - } - - file { 'link-phantomjs-bin': - ensure => "${creates}/bin/phantomjs", - path => '/usr/local/bin/phantomjs' - } -} diff --git a/.vagrant-puppet/modules/php/manifests/init.pp b/.vagrant-puppet/modules/php/manifests/init.pp deleted file mode 100644 index 1a8e31746..000000000 --- a/.vagrant-puppet/modules/php/manifests/init.pp +++ /dev/null @@ -1,38 +0,0 @@ -# Class: php -# -# This class installs php. -# -# Parameters: -# -# Actions: -# -# Requires: -# -# apache -# -# Sample Usage: -# -# include php -# -class php { - - include apache - - package { 'php': - ensure => installed, - require => Package['apache'], - notify => Service['apache'] - } - - file { '/etc/php.d/error_reporting.ini': - content => template('php/error_reporting.ini.erb'), - require => Package['php'], - notify => Service['apache'] - } - - file { '/etc/php.d/xdebug_settings.ini': - content => template('php/xdebug_settings.ini.erb'), - require => Package['php'], - notify => Service['apache'] - } -} diff --git a/.vagrant-puppet/modules/wget/manifests/init.pp b/.vagrant-puppet/modules/wget/manifests/init.pp deleted file mode 100644 index 560e10b2f..000000000 --- a/.vagrant-puppet/modules/wget/manifests/init.pp +++ /dev/null @@ -1,20 +0,0 @@ -# Class: wget -# -# This class installs wget. -# -# Parameters: -# -# Actions: -# -# Requires: -# -# Sample Usage: -# -# include wget -# -class wget { - - package { 'wget': - ensure => installed, - } -} diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..4d58967ff --- /dev/null +++ b/AUTHORS @@ -0,0 +1,32 @@ +Alexander A. Klimov +Alexander Fuhr +ayoubabid +baufrecht +Bence Nagy +Bernd Erk +Boden Garman +Carlos Cesario +Chris Rüll +Daniel Shirley +Davide Demuru +Eric Lippmann +Goran Rakic +Gunnar Beutner +Jannis Moßhammer +Johannes Meyer +Louis Sautier +Marcus Cobden +Marius Hein +Markus Frosch +Matthias Jentsch +Michael Friedrich +Paul Richards +rbelinsky +Rene Moser +rkcpi +Susanne Vestner-Ludwig +Sylph Lin +Thomas Gelf +Tim Helfensdörfer +Tom Ford +Ulf Lange diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..53de85231 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,130 @@ +## What's New in Version 2.0.0-rc1 + +### Changes + +* Improve layout and look and feel in many ways +* Apply host, service and custom variable restrictions to all monitoring objects +* Add fullscreen mode (?showFullscreen) +* User and group management +* Comment and Downtime Detail View +* Show icon_image in host/service views +* Show Icinga program version in monitoring health + +#### Features + +* Feature 4139: Notify monitoring backend availability problems +* Feature 4498: Allow to add columns to monitoring views via URL +* Feature 6392: Resolve Icinga 2 runtime macros in action and notes URLs +* Feature 6729: Fullscreen mode +* Feature 7343: Fetch user groups from LDAP +* Feature 7595: Remote connection resource configuration +* Feature 7614: Right-align icons +* Feature 7651: Add module information (module.info) to all core modules +* Feature 8054: Host Groups should list number of hosts (as well as services) +* Feature 8235: Show host and service notes in the host and service detail view +* Feature 8247: Move notifications to the bottom of the page +* Feature 8281: Improve layout of comments and downtimes in the host and service detail views +* Feature 8310: Improve layout of performance data and check statistics in the host and service detail views +* Feature 8565: Improve look and feel of the monitoring multi-select views +* Feature 8613: IDO queries related to concrete objects should not depend on collations +* Feature 8665: Show icon_image in the host and service detail views +* Feature 8781: Automatically deselect rows when closing the detail area +* Feature 8826: User and group management +* Feature 8849: Show only three (or four) significant digits (e.g. in check execution time) +* Feature 8877: Allow module developers to implement new/custom authentication methods +* Feature 8886: Require mandatory parameters in controller actions and CLI commands +* Feature 8902: Downtime detail view +* Feature 8903: Comment detail view +* Feature 9009: Apply host and service restrictions to related views as well +* Feature 9203: Wizard: Validate that a resource is actually an IDO instance +* Feature 9207: Show icinga program version in Monitoring Health +* Feature 9223: Show the active ido endpoint in the monitoring health view +* Feature 9284: Create a ServiceActionsHook +* Feature 9300: Support icon_image_alt +* Feature 9361: Refine UI for RC1 +* Feature 9377: Permission and restriction documentation +* Feature 9379: Provide an about.md + +#### Bugfixes + +* Bug 6281: ShowController's hostAction() and serviceAction() do not respond with 400 for invalid/missing parameters and with 404 if the host or service wasn't found +* Bug 6778: Duration and history time formatting isn't correct +* Bug 6952: Unauthenticated users are provided helpful error messages +* Bug 7151: Play nice with form-button-double-clickers +* Bug 7165: Invalid host address leads to exception w/ PostgreSQL +* Bug 7447: Commands sent over SSH are missing the -i option when using a ssh user aside from the webserver's user +* Bug 7491: Switching from MySQL to PostgreSQL and vice versa doesn't change the port in the resource configuration +* Bug 7642: Monitoring menu renderers should be moved to the monitoring module +* Bug 7658: MenuItemRenderer is not so easy to extend +* Bug 7876: Not all views can be added to the dashboard w/o breaking the layout +* Bug 7931: Can't acknowledge multiple selected services which are in downtime +* Bug 7997: Service-Detail-View tabs are changing their context when clicking the Host-Tab +* Bug 7998: Navigating to the Services-Tab in the Service-Detail-View displays only the selected service +* Bug 8006: Beautify command transport error exceptions +* Bug 8205: List views should not show more than the five worst pies +* Bug 8241: Take display_name into account when searching for host and service names +* Bug 8334: Perfdata details partially hidden depending on the resolution +* Bug 8339: Lib: SimpleQuery::paginate() must not fetch page and limit from request but use them from parameters +* Bug 8343: Status summary does not respect restrictions +* Bug 8363: Updating dashlets corrupts their URLs +* Bug 8453: The filter column "_dev" is not allowed here +* Bug 8472: Missing support for command line arguments in the format --arg= +* Bug 8474: Improve layout of dictionaries in the host and service detail views +* Bug 8624: Delete multiple downtimes and comments at once +* Bug 8696: Can't search for Icinga 2 custom variables +* Bug 8705: Show all shell commands required to get ready in the setup wizard +* Bug 8706: INI files should end with a newline character and should not contain superfluous newlines +* Bug 8707: Wizard: setup seems to fail with just one DB user +* Bug 8711: JS is logging "ugly" side exceptions +* Bug 8731: Apply host restrictions to service views +* Bug 8744: Performance data metrics with value 0 are not displayed +* Bug 8747: Icinga 2 boolean variables not shown in the host and service detail views +* Bug 8777: Server error: Service not found exception when service name begins or ends with whitespaces +* Bug 8815: Only the first external command is sent over SSH when submitting commands for multiple selected hosts or services +* Bug 8847: Missing indication that nothing was found in the docs when searching +* Bug 8860: Host group view calculates states from service states; but states should be calculated from host states instead +* Bug 8927: Tactical overview does not respect restrictions +* Bug 8928: Host and service groups views do not respect restrictions +* Bug 8929: Setup wizard does not validate whether the PostgreSQL user for creating the database owns the CREATE ROLE system privilege +* Bug 8930: Error message about refused connection to the PostgreSQL database server displayed twice in the setup wizard +* Bug 8934: Status text for ok/up becomes white when hovered +* Bug 8941: Long plugin output makes the whole container horizontally scrollable instead of just the row containing the long plugin output +* Bug 8950: Improve English for "The last one occured %s ago" +* Bug 8953: LDAP encryption settings have no effect +* Bug 8956: Can't login when creating the database connection for the preferences store fails +* Bug 8957: Fall back on syslog if the logger's type directive is misconfigured +* Bug 8958: Switching LDAP encryption to LDAPS doesn't change the port in the resource configuration +* Bug 8960: Remove exclamation mark from the notification "Authentication order updated!" +* Bug 8966: Show custom variables visually separated in the host and service detail views +* Bug 8967: Remove right petrol border from plugin output in the host and service detail views +* Bug 8972: Can't view Icinga Web 2's log file +* Bug 8994: Uncaught exception on empty session.save_path() +* Bug 9000: Only the first line of a stack trace is shown in the applications log view +* Bug 9007: Misspelled host and service names in commands are not accepted by icinga +* Bug 9008: Notification overview does not respect restrictions +* Bug 9022: Browser title does not change in case of an error +* Bug 9023: Toggling feature... +* Bug 9025: A tooltip of the service grid's x-axe makes it difficult to click the title of the currently hovered column +* Bug 9026: Add To Dashboard ... on the dashboard +* Bug 9046: Detail View: Downtimes description misses space between duration and comment text +* Bug 9056: Filter for host/servicegroup search doesn't work anymore +* Bug 9057: contact_notify_host_timeperiod +* Bug 9059: Can't initiate an ascending sort by host or service severity +* Bug 9198: monitoring/command/feature/object does not grant the correct permissions +* Bug 9202: The config\* permission does not permit to navigate to the configuration +* Bug 9211: Empty filters are being rendered to SQL which leads to syntax errors +* Bug 9214: Detect multitple icinga_instances entries and warn the user +* Bug 9220: Centralize submission and apply handling of sort rules +* Bug 9224: Allow anonymous LDAP binding +* Bug 9281: Problem with Icingaweb 2 after PHP Upgrade 5.6.8 -> 5.6.9 +* Bug 9317: Web 2's ListController inherits from the monitoring module's base controller +* Bug 9319: Downtimes overview does not respect restrictions +* Bug 9350: Menu disappears in user group management view +* Bug 9351: Timeline links are broken +* Bug 9352: User list should be sorted +* Bug 9353: Searching for users fails, at least with LDAP backend +* Bug 9355: msldap seems not to be a first-class citizen +* Bug 9378: Rpm calls usermod w/ invalid option on openSUSE +* Bug 9384: Timeline+Role problem +* Bug 9392: Command links seem to be broken + diff --git a/README.md b/README.md index 16eb1444a..55e4e77ee 100644 --- a/README.md +++ b/README.md @@ -2,267 +2,26 @@ ## Table of Contents -0. [General Information](#general information) +0. [About](#about) 1. [Installation](#installation) 2. [Support](#support) -3. [Vagrant - Virtual development environment](#vagrant) -## General Information +## About -`Icinga Web 2` is the next generation monitoring web interface, framework -and CLI tool developed by the [Icinga Project](https://www.icinga.org/community/team/). +**Icinga Web 2** is the next generation open source monitoring web interface, framework +and command-line interface developed by the [Icinga Project](https://www.icinga.org/), supporting Icinga 2, +Icinga Core and any other monitoring backend compatible with the Livestatus Protocol. -Responsive and fast, rewritten from scratch supporting multiple backends and -providing a CLI tool. Compatible with Icinga Core 2.x and 1.x. - -Check the Icinga website for some [insights](https://www.icinga.org/icinga/screenshots/icinga-web-2/). - -> **Note** -> -> `Icinga Web 2` is still in development and not meant for production deployment. -> Watch the [development roadmap](https://dev.icinga.org/projects/icingaweb2/roadmap) -> and [Icinga website](https://www.icinga.org/) for release schedule updates! +![Icinga Web 2](https://www.icinga.org/wp-content/uploads/2014/06/service_detail.png "Icinga Web 2") ## Installation -Please navigate to [doc/installation.md](doc/installation.md) for updated details. +For installing Icinga Web 2 please read [doc/installation.md](doc/installation.md). ## Support -Please head over to the [community support channels](https://www.icinga.org/icinga/faq/get-help/) -in case of questions, bugs, etc. - -Please make sure to provide the following details: - -* OS, distribution, version -* PHP and/or MySQL/PostgreSQL version -* Which browser and its version -* Screenshot and problem description - - -## Vagrant - -### Requirements - -* Vagrant 1.2+ -* Virtualbox 4.2.16+ -* a fairly powerful hardware (quad core, 4gb ram, fast hdd) - -> **Note** -> -> The deployment of the virtual machine is tested against Vagrant starting with version 1.2. -> Unfortunately older versions will not work. - -### General - -The Icinga Web 2 project ships with a Vagrant virtual machine that integrates -the source code with various services and example data in a controlled -environment. This enables developers and users to test Livestatus, status.dat, -MySQL and PostgreSQL backends as well as the LDAP authentication. All you -have to do is install Vagrant and run: - - vagrant up - -> **Note** -> -> The first boot of the vm takes a fairly long time because -> you'll download a plain CentOS base box and Vagrant will automatically -> provision the environment on the first go. - -After you should be able to browse [localhost:8080/icingaweb](http://localhost:8080/icingaweb). - -### Environment - -**Forwarded ports**: - - - - - - - - - - - - - - - - - -
ProctocolLocal port (virtual machine host)Remote port (the virtual machine)
SSH222222
HTTP808080
- -**Installed packages**: - -* Apache2 with PHP enabled -* PHP with MySQL and PostgreSQL libraries -* MySQL server and client software -* PostgreSQL server and client software -* [Icinga prerequisites](http://docs.icinga.org/latest/en/quickstart-idoutils.html#installpackages) -* OpenLDAP servers and clients - -**Installed users and groups**: - -* User icinga with group icinga and icinga-cmd -* Webserver user added to group icinga-cmd - -**Installed software**: - -* Icinga with IDOUtils using a MySQL database -* Icinga with IDOUtils using a PostgreSQL database -* Icinga 2 - -**Installed files**: - -* `/usr/share/icinga/htpasswd.users` account information for logging into the Icinga classic web interface for both icinga instances -* `/usr/lib64/nagios/plugins` Monitoring Plugins for all Icinga instances - -#### Icinga with IDOUtils using a MySQL database - -**Installation path**: `/usr/local/icinga-mysql` - -**Services**: - -* `icinga-mysql` -* `ido2db-mysql` - -Connect to the **icinga mysql database** using the following command: - - mysql -u icinga -p icinga icinga - -Access the **Classic UI** (CGIs) via [localhost:8080/icinga-mysql](http://localhost:8080/icinga-mysql). -For **logging into** the Icinga classic web interface use user *icingaadmin* with password *icinga*. - -#### Icinga with IDOUtils using a PostgreSQL database - -**Installation path**: `/usr/local/icinga-pgsql` - -**Services**: - -* `icinga-pgsql` -* `ido2db-pgsql` - -Connect to the **icinga mysql database** using the following command: - - sudo -u postgres psql -U icinga -d icinga - -Access the **Classic UI** (CGIs) via [localhost:8080/icinga-pgsql](http://localhost:8080/icinga-pgsql). -For **logging into** the Icinga classic web interface use user *icingaadmin* with password *icinga*. - -#### Monitoring Test Config - -Test config is added to both the MySQL and PostgreSQL Icinga instance utilizing the Perl module -**Monitoring::Generator::TestConfig** to generate test config to **/usr/local/share/misc/monitoring_test_config** -which is then copied to **/etc/conf.d/test_config/**. -Configuration can be adjusted and recreated with **/usr/local/share/misc/monitoring_test_config/recreate.pl**. -**Note** that you have to run - - vagrant provision - -in the host after any modification to the script just mentioned. - -#### MK Livestatus - -MK Livestatus is added to the Icinga installation using a MySQL database. - -**Installation path**: - -* `/usr/local/icinga-mysql/bin/unixcat` -* `/usr/local/icinga-mysql/lib/mk-livestatus/livecheck` -* `/usr/local/icinga-mysql/lib/mk-livestatus/livestatus.o` -* `/usr/local/icinga-mysql/etc/modules/mk-livestatus.cfg` -* `/usr/local/icinga-mysql/var/rw/live` - -**Example usage**: - - echo "GET hosts" | /usr/local/icinga-mysql/bin/unixcat /usr/local/icinga-mysql/var/rw/live - -#### LDAP example data - -The environment includes a openldap server with example data. *Domain* suffix is **dc=icinga,dc=org**. -Administrator (*rootDN*) of the slapd configuration database is **cn=admin,cn=config** and the -administrator (*rootDN*) of our database instance is **cn=admin,dc=icinga,dc=org**. Both share -the *password* `admin`. - -Examples to query the slapd configuration database: - - ldapsearch -x -W -LLL -D cn=admin,cn=config -b cn=config dn - ldapsearch -Y EXTERNAL -H ldapi:/// -LLL -b cn=config dn - -Examples to query our database instance: - - ldapsearch -x -W -LLL -D cn=admin,dc=icinga,dc=org -b dc=icinga,dc=org dn - ldapsearch -Y EXTERNAL -H ldapi:/// -LLL -b dc=icinga,dc=org dn - -This is what the **dc=icinga,dc=org** *DIT* looks like: - -> dn: dc=icinga,dc=org -> -> dn: ou=people,dc=icinga,dc=org -> -> dn: ou=groups,dc=icinga,dc=org -> -> dn: cn=Users,ou=groups,dc=icinga,dc=org -> cn: Users -> uniqueMember: cn=Jon Doe,ou=people,dc=icinga,dc=org -> uniqueMember: cn=Jane Smith,ou=people,dc=icinga,dc=org -> uniqueMember: cn=John Q. Public,ou=people,dc=icinga,dc=org -> uniqueMember: cn=Richard Roe,ou=people,dc=icinga,dc=org -> -> dn: cn=John Doe,ou=people,dc=icinga,dc=org -> cn: John Doe -> uid: jdoe -> -> dn: cn=Jane Smith,ou=people,dc=icinga,dc=org -> cn: Jane Smith -> uid: jsmith -> -> dn: cn=John Q. Public,ou=people,dc=icinga,dc=org -> cn: John Q. Public -> uid: jqpublic -> -> dn: cn=Richard Roe,ou=people,dc=icinga,dc=org -> cn: Richard Roe -> uid: rroe - -All users share the password `password`. - -#### Testing the code - -All software required to run tests is installed in the virtual machine. -In order to run all tests you have to execute the following commands: - - vagrant ssh -c /vagrant/test/php/runtests - vagrant ssh -c /vagrant/test/php/checkswag - vagrant ssh -c /vagrant/test/js/runtests - vagrant ssh -c /vagrant/test/js/checkswag - vagrant ssh -c /vagrant/test/frontend/runtests - -`runtests` will execute unit and regression tests and `checkswag` will report -code style issues. - -#### Icinga 2 - -Installed from the Icinga [snapshot package repository](http://packages.icinga.org/epel/). -The configuration is located in `/etc/icinga2`. - -**Example usage**: - - /etc/init.d/icinga2 (start|stop|restart|reload) - - -## Log into Icinga Web 2 - -If you've configure LDAP as authentication backend (which is the default) use the following login credentials: - -> **Username**: jdoe -> **Password**: password - -Have a look at [LDAP example data](#ldap example data) for more accounts. - -Using MySQL as backend: - -> **Username**: icingaadmin -> **Password**: icinga +If you come across problems at some time, the [community support channels](https://support.icinga.org/) +are good places to ask for advice from other users and give some in return. +For status updates check the [Icinga website](https://www.icinga.org/) and the +[Icinga Web 2 development roadmap](https://dev.icinga.org/projects/icingaweb2/roadmap). diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..e8bcfeec0 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,64 @@ +# Quality Assurance + +Review and test the changes and issues for this version. +https://dev.icinga.org/projects/icingaweb2/roadmap + +# Release Workflow + +Update the [.mailmap](.mailmap) and [AUTHORS](AUTHORS) files: + + $ git log --use-mailmap | grep ^Author: | cut -f2- -d' ' | sort | uniq > AUTHORS + +Update the version number in the [icingaweb2.spec] and [VERSION] files. + +Update the [ChangeLog](ChangeLog) file using +the changelog.py script. + +Changelog: + + $ ./changelog.py --version 2.0.0-rc1 + +Wordpress: + + $ ./changelog.py --version 2.0.0-rc1 --html --links + +Commit these changes to the "master" branch: + + $ git commit -v -a -m "Release version " + +For minor releases: Cherry-pick this commit into the "support" branch. + +Create a signed tag (tags/v) on the "master" branch (for major +releases) or the "support" branch (for minor releases). + + $ git tag -m "Version " v + +Push the tag. + + $ git push --tags + +For major releases: Create a new "support" branch: + + $ git checkout master + $ git checkout -b support/2.x + $ git push -u origin support/2.x + +# External Dependencies + +## Build Server + +### Linux + +* Build the newly created git tag for Debian/RHEL/SuSE. +* Provision the vagrant boxes and test the release packages. + +## Github Release + +Create a new release from the newly created git tag. +https://github.com/Icinga/icingaweb2/releases + +## Announcement + +* Create a new blog post on www.icinga.org/blog +* Send announcement mail to icinga-announce@lists.icinga.org +* Social media: [Twitter](https://twitter.com/icinga), [Facebook](https://www.facebook.com/icinga), [G+](http://plus.google.com/+icinga), [Xing](https://www.xing.com/communities/groups/icinga-da4b-1060043), [LinkedIn](https://www.linkedin.com/groups/Icinga-1921830/about) diff --git a/VERSION b/VERSION index 71282c063..1ee76d11b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.0-beta1 +v2.0.0-rc1 diff --git a/Vagrantfile b/Vagrantfile index d73944173..7d026fc83 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,10 +1,11 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -VAGRANTFILE_API_VERSION = "2" -VAGRANT_REQUIRED_VERSION = "1.2.0" +# Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ + +VAGRANTFILE_API_VERSION = "2" +VAGRANT_REQUIRED_VERSION = "1.5.0" -# Require 1.2.x at least if ! defined? Vagrant.require_version if Gem::Version.new(Vagrant::VERSION) < Gem::Version.new(VAGRANT_REQUIRED_VERSION) puts "Vagrant >= " + VAGRANT_REQUIRED_VERSION + " required. Your version is " + Vagrant::VERSION @@ -15,93 +16,39 @@ else end Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - # All Vagrant configuration is done here. The most common configuration - # options are documented and commented below. For a complete reference, - # please see the online documentation at vagrantup.com. - - # Every Vagrant virtual environment requires a box to build off of. - config.vm.box = "centos-6.4-x64-vbox" - - # The url from where the 'config.vm.box' box will be fetched if it - # doesn't already exist on the user's system. - config.vm.box_url = "http://vagrant-boxes.icinga.org/centos-64-x64-vbox4212.box" - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine. In the example below, - # accessing "localhost:8080" will access port 80 on the guest machine. - config.vm.network :forwarded_port, guest: 80, host: 8080, - # Port collision auto-correction must be manually enabled for each forwarded port, - # since it is often surprising when it occurs and can lead the Vagrant user to - # think that the port wasn't properly forwarded. During a vagrant up or vagrant reload, - # Vagrant will output information about any collisions detections and auto corrections made, - # so you can take notice and act accordingly. + config.vm.network "forwarded_port", guest: 80, host: 8080, auto_correct: true - # Create a private network, which allows host-only access to the machine - # using a specific IP. - # config.vm.network :private_network, ip: "192.168.33.10" + config.vm.provision :shell, :path => ".puppet/manifests/puppet.sh" - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - # config.vm.network :public_network + config.vm.provider :virtualbox do |v, override| + override.vm.box = "centos-71-x64-vbox" + override.vm.box_url = "http://boxes.icinga.org/centos-71-x64-vbox.box" - # If true, then any SSH connections made will enable agent forwarding. - # Default value: false - # config.ssh.forward_agent = true - - # Share an additional folder to the guest VM. The first argument is - # the path on the host to the actual folder. The second argument is - # the path on the guest to mount the folder. And the optional third - # argument is a set of non-required options. - config.vm.synced_folder "./var/log", "/vagrant/var/log" - - # Provider-specific configuration so you can fine-tune various - # backing providers for Vagrant. These expose provider-specific options. - # Example for VirtualBox: - # - # config.vm.provider :virtualbox do |vb| - # # Don't boot with headless mode - # vb.gui = true - # - # # Use VBoxManage to customize the VM. For example to change memory: - # vb.customize ["modifyvm", :id, "--memory", "1024"] - # end - # - # View the documentation for the provider you're using for more - # information on available options. - config.vm.provider "virtualbox" do |vb| - vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate//vagrant/config", "1"] - vb.customize ["modifyvm", :id, "--memory", "1024"] + v.customize ["modifyvm", :id, "--memory", "1024"] + v.customize ["modifyvm", :id, "--cpus", "2"] + end + + config.vm.provider :parallels do |p, override| + override.vm.box = "parallels/centos-7.1" + + p.name = "Icinga Web 2 Development" + + # Update Parallels Tools automatically + p.update_guest_tools = true + + # Set power consumption mode to "Better Performance" + p.optimize_power_consumption = false + + p.memory = 1024 + p.cpus = 2 end - # Enable provisioning with Puppet stand alone. Puppet manifests - # are contained in a directory path relative to this Vagrantfile. - # You will need to create the manifests directory and a manifest in - # the file base.pp in the manifests_path directory. - # - # An example Puppet manifest to provision the message of the day: - # - # # group { "puppet": - # # ensure => "present", - # # } - # # - # # File { owner => 0, group => 0, mode => 0644 } - # # - # # file { '/etc/motd': - # # content => "Welcome to your Vagrant-built virtual machine! - # # Managed by Puppet.\n" - # # } - # - # config.vm.provision :puppet do |puppet| - # puppet.manifests_path = "manifests" - # puppet.manifest_file = "init.pp" - # end config.vm.provision :puppet do |puppet| - puppet.module_path = ".vagrant-puppet/modules" - puppet.manifests_path = ".vagrant-puppet/manifests" - # puppet.options = "-v -d" + puppet.hiera_config_path = ".puppet/hiera/hiera.yaml" + puppet.module_path = [ ".puppet/modules", ".puppet/profiles" ] + puppet.manifests_path = ".puppet/manifests" + puppet.manifest_file = "site.pp" + puppet.options = "--parser=future" end - - config.vm.provision :shell, :path => ".vagrant-puppet/manifests/finalize.sh" end diff --git a/application/VERSION b/application/VERSION new file mode 100644 index 000000000..519b667a7 --- /dev/null +++ b/application/VERSION @@ -0,0 +1 @@ +$Format:%H%d %ci$ diff --git a/application/clicommands/AutocompleteCommand.php b/application/clicommands/AutocompleteCommand.php index 5a5a0e0c0..045110af7 100644 --- a/application/clicommands/AutocompleteCommand.php +++ b/application/clicommands/AutocompleteCommand.php @@ -1,6 +1,5 @@ view->version = Version::get(); + } +} diff --git a/application/controllers/AuthenticationController.php b/application/controllers/AuthenticationController.php index 00dae1c6d..9431b7fbc 100644 --- a/application/controllers/AuthenticationController.php +++ b/application/controllers/AuthenticationController.php @@ -1,25 +1,19 @@ requiresSetup()) && $icinga->setupTokenExists()) { $this->redirectNow(Url::fromPath('setup')); } - - $auth = $this->Auth(); - $this->view->form = $form = new LoginForm(); - $this->view->title = $this->translate('Icingaweb Login'); - - try { - $redirectUrl = $this->view->form->getValue('redirect'); - if ($redirectUrl) { - $redirectUrl = Url::fromPath($redirectUrl); - } else { - $redirectUrl = Url::fromPath('dashboard'); - } - - if ($auth->isAuthenticated()) { - $this->rerenderLayout()->redirectNow($redirectUrl); - } - - try { - $config = Config::app('authentication'); - } catch (NotReadableError $e) { - throw new ConfigurationError( - $this->translate('Could not read your authentication.ini, no authentication methods are available.'), - 0, - $e - ); - } - - $chain = new AuthChain($config); - $request = $this->getRequest(); - if ($request->isPost() && $this->view->form->isValid($request->getPost())) { - $user = new User($this->view->form->getValue('username')); - $password = $this->view->form->getValue('password'); - $backendsTried = 0; - $backendsWithError = 0; - - $redirectUrl = $form->getValue('redirect'); - - if ($redirectUrl) { - $redirectUrl = Url::fromPath($redirectUrl); - } else { - $redirectUrl = Url::fromPath('dashboard'); - } - - foreach ($chain as $backend) { - if ($backend instanceof AutoLoginBackend) { - continue; - } - ++$backendsTried; - try { - $authenticated = $backend->authenticate($user, $password); - } catch (AuthenticationException $e) { - Logger::error($e); - ++$backendsWithError; - continue; - } - if ($authenticated === true) { - $auth->setAuthenticated($user); - $this->rerenderLayout()->redirectNow($redirectUrl); - } - } - if ($backendsTried === 0) { - $this->view->form->addError( - $this->translate( - 'No authentication methods available. Did you create' - . ' authentication.ini when setting up Icinga Web 2?' - ) - ); - } else if ($backendsTried === $backendsWithError) { - $this->view->form->addError( - $this->translate( - 'All configured authentication methods failed.' - . ' Please check the system log or Icinga Web 2 log for more information.' - ) - ); - } elseif ($backendsWithError) { - $this->view->form->addError( - $this->translate( - 'Please note that not all authentication methods were available.' - . ' Check the system log or Icinga Web 2 log for more information.' - ) - ); - } - if ($backendsTried > 0 && $backendsTried !== $backendsWithError) { - $this->view->form->getElement('password')->addError($this->translate('Incorrect username or password')); - } - } elseif ($request->isGet()) { - $user = new User(''); - foreach ($chain as $backend) { - if ($backend instanceof AutoLoginBackend) { - $authenticated = $backend->authenticate($user); - if ($authenticated === true) { - $auth->setAuthenticated($user); - $this->rerenderLayout()->redirectNow( - Url::fromPath(Url::fromRequest()->getParam('redirect', 'dashboard')) - ); - } - } - } - } - } catch (Exception $e) { - $this->view->errorInfo = $e->getMessage(); + $form = new LoginForm(); + if ($this->Auth()->isAuthenticated()) { + $this->redirectNow($form->getRedirectUrl()); } - - $this->view->configMissing = is_dir(Config::$configDir) === false; + if (! $requiresSetup) { + $form->handleRequest(); + } + $this->view->form = $form; + $this->view->title = $this->translate('Icinga Web 2 Login'); + $this->view->requiresSetup = $requiresSetup; } /** @@ -151,10 +52,12 @@ class AuthenticationController extends ActionController if (! $auth->isAuthenticated()) { $this->redirectToLogin(); } - $isRemoteUser = $auth->getUser()->isRemoteUser(); + // Get info whether the user is externally authenticated before removing authorization which destroys the + // session and the user object + $isExternalUser = $auth->getUser()->isExternalUser(); $auth->removeAuthorization(); - if ($isRemoteUser === true) { - $this->_response->setHttpResponseCode(401); + if ($isExternalUser) { + $this->getResponse()->setHttpResponseCode(401); } else { $this->redirectToLogin(); } diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php index 031423ff2..30f46db50 100644 --- a/application/controllers/ConfigController.php +++ b/application/controllers/ConfigController.php @@ -1,41 +1,68 @@ view->tabs = Widget::create('tabs')->add('index', array( - 'title' => $this->translate('Application'), - 'url' => 'config' - ))->add('authentication', array( - 'title' => $this->translate('Authentication'), - 'url' => 'config/authentication' - ))->add('resources', array( - 'title' => $this->translate('Resources'), - 'url' => 'config/resource' - ))->add('roles', array( - 'title' => $this->translate('Roles'), - 'url' => 'roles' + $tabs = $this->getTabs(); + $tabs->add('general', array( + 'title' => $this->translate('Adjust the general configuration of Icinga Web 2'), + 'label' => $this->translate('General'), + 'url' => 'config/general', + 'baseTarget' => '_main' )); + $tabs->add('resource', array( + 'title' => $this->translate('Configure which resources are being utilized by Icinga Web 2'), + 'label' => $this->translate('Resources'), + 'url' => 'config/resource', + 'baseTarget' => '_main' + )); + return $tabs; + } + + /** + * Create and return the tabs to display when showing authentication configuration + */ + public function createAuthenticationTabs() + { + $tabs = $this->getTabs(); + $tabs->add('userbackend', array( + 'title' => $this->translate('Configure how users authenticate with and log into Icinga Web 2'), + 'label' => $this->translate('User Backends'), + 'url' => 'config/userbackend', + 'baseTarget' => '_main' + )); + $tabs->add('usergroupbackend', array( + 'title' => $this->translate('Configure how users are associated with groups by Icinga Web 2'), + 'label' => $this->translate('User Group Backends'), + 'url' => 'usergroupbackend/list', + 'baseTarget' => '_main' + )); + return $tabs; } public function devtoolsAction() @@ -44,16 +71,27 @@ class ConfigController extends ActionController } /** - * Index action, entry point for configuration + * Redirect to the general configuration */ public function indexAction() { + $this->redirectNow('config/general'); + } + + /** + * General configuration + * + * @throws SecurityException If the user lacks the permission for configuring the general configuration + */ + public function generalAction() + { + $this->assertPermission('config/application/general'); $form = new GeneralConfigForm(); $form->setIniConfig(Config::app()); $form->handleRequest(); $this->view->form = $form; - $this->view->tabs->activate('index'); + $this->createApplicationTabs()->activate('general'); } /** @@ -61,32 +99,50 @@ class ConfigController extends ActionController */ public function modulesAction() { - $this->view->tabs = Widget::create('tabs')->add('modules', array( - 'title' => 'Modules', - 'url' => 'config/modules' - )); - - $this->view->tabs->activate('modules'); + $this->assertPermission('config/modules'); + // Overwrite tabs created in init + // @TODO(el): This seems not natural to me. Module configuration should have its own controller. + $this->view->tabs = Widget::create('tabs') + ->add('modules', array( + 'label' => $this->translate('Modules'), + 'title' => $this->translate('List intalled modules'), + 'url' => 'config/modules' + )) + ->activate('modules'); $this->view->modules = Icinga::app()->getModuleManager()->select() ->from('modules') ->order('enabled', 'desc') - ->order('name')->paginate(); + ->order('name'); + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->modules); + // TODO: Not working + /*$this->setupSortControl(array( + 'name' => $this->translate('Modulename'), + 'path' => $this->translate('Installation Path'), + 'enabled' => $this->translate('State') + ));*/ } public function moduleAction() { - $name = $this->getParam('name'); + $this->assertPermission('config/modules'); $app = Icinga::app(); $manager = $app->getModuleManager(); + $name = $this->getParam('name'); if ($manager->hasInstalled($name)) { - $this->view->moduleData = Icinga::app()->getModuleManager()->select() - ->from('modules')->where('name', $name)->fetchRow(); - $module = new Module($app, $name, $manager->getModuleDir($name)); + $this->view->moduleData = $manager->select()->from('modules')->where('name', $name)->fetchRow(); + if ($manager->hasLoaded($name)) { + $module = $manager->getModule($name); + } else { + $module = new Module($app, $name, $manager->getModuleDir($name)); + } + $this->view->module = $module; + $this->view->tabs = $module->getConfigTabs()->activate('info'); } else { $this->view->module = false; + $this->view->tabs = null; } - $this->view->tabs = $module->getConfigTabs()->activate('info'); } /** @@ -94,11 +150,11 @@ class ConfigController extends ActionController */ public function moduleenableAction() { + $this->assertPermission('config/modules'); $module = $this->getParam('name'); $manager = Icinga::app()->getModuleManager(); try { $manager->enableModule($module); - $manager->loadModule($module); Notification::success(sprintf($this->translate('Module "%s" enabled'), $module)); $this->rerenderLayout()->reloadCss()->redirectNow('config/modules'); } catch (Exception $e) { @@ -114,6 +170,7 @@ class ConfigController extends ActionController */ public function moduledisableAction() { + $this->assertPermission('config/modules'); $module = $this->getParam('name'); $manager = Icinga::app()->getModuleManager(); try { @@ -129,85 +186,144 @@ class ConfigController extends ActionController } /** - * Action for listing and reordering authentication backends + * Action for listing and reordering user backends */ - public function authenticationAction() + public function userbackendAction() { - $form = new AuthenticationBackendReorderForm(); + $this->assertPermission('config/application/userbackend'); + $form = new UserBackendReorderForm(); $form->setIniConfig(Config::app('authentication')); $form->handleRequest(); $this->view->form = $form; - $this->view->tabs->activate('authentication'); - $this->render('authentication/reorder'); + $this->createAuthenticationTabs()->activate('userbackend'); + $this->render('userbackend/reorder'); } /** - * Action for creating a new authentication backend + * Create a new user backend */ - public function createauthenticationbackendAction() + public function createuserbackendAction() { - $form = new AuthenticationBackendConfigForm(); - $form->setIniConfig(Config::app('authentication')); - $form->setResourceConfig(ResourceFactory::getResourceConfigs()); - $form->setRedirectUrl('config/authentication'); - $form->handleRequest(); - - $this->view->form = $form; - $this->view->tabs->activate('authentication'); - $this->render('authentication/create'); - } - - /** - * Action for editing authentication backends - */ - public function editauthenticationbackendAction() - { - $form = new AuthenticationBackendConfigForm(); - $form->setIniConfig(Config::app('authentication')); - $form->setResourceConfig(ResourceFactory::getResourceConfigs()); - $form->setRedirectUrl('config/authentication'); - $form->handleRequest(); - - $this->view->form = $form; - $this->view->tabs->activate('authentication'); - $this->render('authentication/modify'); - } - - /** - * Action for removing a backend from the authentication list - */ - public function removeauthenticationbackendAction() - { - $form = new ConfirmRemovalForm(array( - 'onSuccess' => function ($form) { - $configForm = new AuthenticationBackendConfigForm(); - $configForm->setIniConfig(Config::app('authentication')); - $authBackend = $form->getRequest()->getQuery('auth_backend'); - - try { - $configForm->remove($authBackend); - } catch (InvalidArgumentException $e) { - Notification::error($e->getMessage()); - return; - } - - if ($configForm->save()) { - Notification::success(sprintf( - t('Authentication backend "%s" has been successfully removed'), - $authBackend - )); - } else { - return false; - } - } + $this->assertPermission('config/application/userbackend'); + $form = new UserBackendConfigForm(); + $form->setRedirectUrl('config/userbackend'); + $form->setTitle($this->translate('Create New User Backend')); + $form->addDescription($this->translate( + 'Create a new backend for authenticating your users. This backend' + . ' will be added at the end of your authentication order.' )); - $form->setRedirectUrl('config/authentication'); + $form->setIniConfig(Config::app('authentication')); + + try { + $form->setResourceConfig(ResourceFactory::getResourceConfigs()); + } catch (ConfigurationError $e) { + if ($this->hasPermission('config/application/resources')) { + Notification::error($e->getMessage()); + $this->redirectNow('config/createresource'); + } + + throw $e; // No permission for resource configuration, show the error + } + + $form->setOnSuccess(function (UserBackendConfigForm $form) { + try { + $form->add(array_filter($form->getValues())); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(t('User backend successfully created')); + return true; + } + + return false; + }); $form->handleRequest(); $this->view->form = $form; - $this->view->tabs->activate('authentication'); - $this->render('authentication/remove'); + $this->render('form'); + } + + /** + * Edit a user backend + */ + public function edituserbackendAction() + { + $this->assertPermission('config/application/userbackend'); + $backendName = $this->params->getRequired('backend'); + + $form = new UserBackendConfigForm(); + $form->setRedirectUrl('config/userbackend'); + $form->setTitle(sprintf($this->translate('Edit User Backend %s'), $backendName)); + $form->setIniConfig(Config::app('authentication')); + $form->setOnSuccess(function (UserBackendConfigForm $form) use ($backendName) { + try { + $form->edit($backendName, array_map( + function ($v) { + return $v !== '' ? $v : null; + }, + $form->getValues() + )); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(sprintf(t('User backend "%s" successfully updated'), $backendName)); + return true; + } + + return false; + }); + + try { + $form->load($backendName); + $form->setResourceConfig(ResourceFactory::getResourceConfigs()); + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('User backend "%s" not found'), $backendName)); + } + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Display a confirmation form to remove the backend identified by the 'backend' parameter + */ + public function removeuserbackendAction() + { + $this->assertPermission('config/application/userbackend'); + $backendName = $this->params->getRequired('backend'); + + $backendForm = new UserBackendConfigForm(); + $backendForm->setIniConfig(Config::app('authentication')); + $form = new ConfirmRemovalForm(); + $form->setRedirectUrl('config/userbackend'); + $form->setTitle(sprintf($this->translate('Remove User Backend %s'), $backendName)); + $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($backendName, $backendForm) { + try { + $backendForm->delete($backendName); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($backendForm->save()) { + Notification::success(sprintf(t('User backend "%s" successfully removed'), $backendName)); + return true; + } + + return false; + }); + $form->handleRequest(); + + $this->view->form = $form; + $this->render('form'); } /** @@ -215,8 +331,9 @@ class ConfigController extends ActionController */ public function resourceAction() { + $this->assertPermission('config/application/resources'); $this->view->resources = Config::app('resources', true)->keys(); - $this->view->tabs->activate('resources'); + $this->createApplicationTabs()->activate('resource'); } /** @@ -224,7 +341,10 @@ class ConfigController extends ActionController */ public function createresourceAction() { + $this->assertPermission('config/application/resources'); $form = new ResourceConfigForm(); + $form->setTitle($this->translate('Create A New Resource')); + $form->addDescription($this->translate('Resources are entities that provide data to Icinga Web 2.')); $form->setIniConfig(Config::app('resources')); $form->setRedirectUrl('config/resource'); $form->handleRequest(); @@ -238,7 +358,9 @@ class ConfigController extends ActionController */ public function editresourceAction() { + $this->assertPermission('config/application/resources'); $form = new ResourceConfigForm(); + $form->setTitle($this->translate('Edit Existing Resource')); $form->setIniConfig(Config::app('resources')); $form->setRedirectUrl('config/resource'); $form->handleRequest(); @@ -252,6 +374,7 @@ class ConfigController extends ActionController */ public function removeresourceAction() { + $this->assertPermission('config/application/resources'); $form = new ConfirmRemovalForm(array( 'onSuccess' => function ($form) { $configForm = new ResourceConfigForm(); @@ -262,7 +385,7 @@ class ConfigController extends ActionController $configForm->remove($resource); } catch (InvalidArgumentException $e) { Notification::error($e->getMessage()); - return; + return false; } if ($configForm->save()) { @@ -272,6 +395,7 @@ class ConfigController extends ActionController } } )); + $form->setTitle($this->translate('Remove Existing Resource')); $form->setRedirectUrl('config/resource'); $form->handleRequest(); @@ -280,9 +404,9 @@ class ConfigController extends ActionController $authConfig = Config::app('authentication'); foreach ($authConfig as $backendName => $config) { if ($config->get('resource') === $resource) { - $form->addError(sprintf( + $form->addDescription(sprintf( $this->translate( - 'The resource "%s" is currently in use by the authentication backend "%s". ' . + 'The resource "%s" is currently utilized for authentication by user backend "%s". ' . 'Removing the resource can result in noone being able to log in any longer.' ), $resource, diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php index ba2125e12..b00ddf2b8 100644 --- a/application/controllers/DashboardController.php +++ b/application/controllers/DashboardController.php @@ -1,15 +1,12 @@ dashboard = new Dashboard(); - $this->dashboard->setUser($this->getRequest()->getUser()); + $this->dashboard->setUser($this->Auth()->getUser()); $this->dashboard->load(); } @@ -56,17 +53,19 @@ class DashboardController extends ActionController $dashlet = new Dashboard\Dashlet($form->getValue('dashlet'), $form->getValue('url'), $pane); $dashlet->setUserWidget(); $pane->addDashlet($dashlet); + $dashboardConfig = $dashboard->getConfig(); try { - $dashboard->write(); - } catch (\Zend_Config_Exception $e) { + $dashboardConfig->saveIni(); + } catch (Exception $e) { $action->view->error = $e; - $action->view->config = $dashboard->createWriter(); + $action->view->config = $dashboardConfig; $action->render('error'); return false; } Notification::success(t('Dashlet created')); return true; }); + $form->setTitle($this->translate('Add Dashlet To Dashboard')); $form->setRedirectUrl('dashboard'); $form->handleRequest(); $this->view->form = $form; @@ -78,7 +77,7 @@ class DashboardController extends ActionController $dashboard = $this->dashboard; $form = new DashletForm(); $form->setDashboard($dashboard); - $form->setSubmitLabel(t('Update Dashlet')); + $form->setSubmitLabel($this->translate('Update Dashlet')); if (! $this->_request->getParam('pane')) { throw new Zend_Controller_Action_Exception( 'Missing parameter "pane"', @@ -117,17 +116,19 @@ class DashboardController extends ActionController $oldPane = $dashboard->getPane($form->getValue('org_pane')); $oldPane->removeDashlet($dashlet->getTitle()); } + $dashboardConfig = $dashboard->getConfig(); try { - $dashboard->write(); - } catch (\Zend_Config_Exception $e) { + $dashboardConfig->saveIni(); + } catch (Exception $e) { $action->view->error = $e; - $action->view->config = $dashboard->createWriter(); + $action->view->config = $dashboardConfig; $action->render('error'); return false; } Notification::success(t('Dashlet updated')); return true; }); + $form->setTitle($this->translate('Edit Dashlet')); $form->setRedirectUrl('dashboard/settings'); $form->handleRequest(); $pane = $dashboard->getPane($this->getParam('pane')); @@ -158,15 +159,16 @@ class DashboardController extends ActionController $dashlet = $this->_request->getParam('dashlet'); $action = $this; $form->setOnSuccess(function (Form $form) use ($dashboard, $dashlet, $pane, $action) { + $pane = $dashboard->getPane($pane); + $pane->removeDashlet($dashlet); + $dashboardConfig = $dashboard->getConfig(); try { - $pane = $dashboard->getPane($pane); - $pane->removeDashlet($dashlet); - $dashboard->write(); + $dashboardConfig->saveIni(); Notification::success(t('Dashlet has been removed from') . ' ' . $pane->getTitle()); return true; - } catch (\Zend_Config_Exception $e) { + } catch (Exception $e) { $action->view->error = $e; - $action->view->config = $dashboard->createWriter(); + $action->view->config = $dashboardConfig; $action->render('error'); return false; } catch (ProgrammingError $e) { @@ -175,6 +177,7 @@ class DashboardController extends ActionController } return false; }); + $form->setTitle($this->translate('Remove Dashlet From Dashboard')); $form->setRedirectUrl('dashboard/settings'); $form->handleRequest(); $this->view->pane = $pane; @@ -196,15 +199,16 @@ class DashboardController extends ActionController $pane = $this->_request->getParam('pane'); $action = $this; $form->setOnSuccess(function (Form $form) use ($dashboard, $pane, $action) { + $pane = $dashboard->getPane($pane); + $dashboard->removePane($pane->getTitle()); + $dashboardConfig = $dashboard->getConfig(); try { - $pane = $dashboard->getPane($pane); - $dashboard->removePane($pane->getTitle()); - $dashboard->write(); + $dashboardConfig->saveIni(); Notification::success(t('Dashboard has been removed') . ': ' . $pane->getTitle()); return true; - } catch (\Zend_Config_Exception $e) { + } catch (Exception $e) { $action->view->error = $e; - $action->view->config = $dashboard->createWriter(); + $action->view->config = $dashboardConfig; $action->render('error'); return false; } catch (ProgrammingError $e) { @@ -213,6 +217,7 @@ class DashboardController extends ActionController } return false; }); + $form->setTitle($this->translate('Remove Dashboard')); $form->setRedirectUrl('dashboard/settings'); $form->handleRequest(); $this->view->pane = $pane; @@ -241,14 +246,15 @@ class DashboardController extends ActionController $this->view->title = $this->dashboard->getActivePane()->getTitle() . ' :: Dashboard'; if ($this->hasParam('remove')) { $this->dashboard->getActivePane()->removeDashlet($this->getParam('remove')); - $this->dashboard->write(); + $this->dashboard->getConfig()->saveIni(); $this->redirectNow(URL::fromRequest()->remove('remove')); } $this->view->tabs->add( 'Add', array( - 'title' => '+', - 'url' => Url::fromPath('dashboard/new-dashlet') + 'label' => '+', + 'title' => 'Add a dashlet to an existing or new dashboard', + 'url' => Url::fromPath('dashboard/new-dashlet') ) ); $this->view->dashboard = $this->dashboard; diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php index 23051bd9a..1c01b1672 100644 --- a/application/controllers/ErrorController.php +++ b/application/controllers/ErrorController.php @@ -1,12 +1,13 @@ getResponse()->setHttpResponseCode(404); $this->view->message = $this->translate('Page not found.'); - if ($modules->hasInstalled($path) && ! $modules->hasEnabled($path)) { + if ($this->Auth()->isAuthenticated() && $modules->hasInstalled($path) && ! $modules->hasEnabled($path)) { $this->view->message .= ' ' . sprintf( $this->translate('Enabling the "%s" module might help!'), $path @@ -45,11 +46,30 @@ class ErrorController extends ActionController break; default: - $title = preg_replace('/\r?\n.*$/s', '', $exception->getMessage()); - $this->getResponse()->setHttpResponseCode(500); - $this->view->title = 'Server error: ' . $title; + 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); + break; + case $exception instanceof MissingParameterException: + $this->getResponse()->setHttpResponseCode(400); + $this->getResponse()->setHeader( + 'X-Status-Reason', + 'Missing parameter ' . $exception->getParameter() + ); + break; + case $exception instanceof SecurityException: + $this->getResponse()->setHttpResponseCode(403); + break; + default: + $this->getResponse()->setHttpResponseCode(500); + break; + } $this->view->message = $exception->getMessage(); - if ($this->getInvokeArg('displayExceptions') == true) { + if ($this->getInvokeArg('displayExceptions')) { $this->view->stackTrace = $exception->getTraceAsString(); } break; diff --git a/application/controllers/FilterController.php b/application/controllers/FilterController.php index 09e8f0368..461977c7a 100644 --- a/application/controllers/FilterController.php +++ b/application/controllers/FilterController.php @@ -1,6 +1,5 @@ assertPermission('config/authentication/groups/show'); + $this->createListTabs()->activate('group/list'); + $backendNames = array_map( + function ($b) { return $b->getName(); }, + $this->loadUserGroupBackends('Icinga\Data\Selectable') + ); + if (empty($backendNames)) { + return; + } + + $this->view->backendSelection = new Form(); + $this->view->backendSelection->setAttrib('class', 'backend-selection'); + $this->view->backendSelection->setUidDisabled(); + $this->view->backendSelection->setMethod('GET'); + $this->view->backendSelection->setTokenDisabled(); + $this->view->backendSelection->addElement( + 'select', + 'backend', + array( + 'autosubmit' => true, + 'label' => $this->translate('Usergroup Backend'), + 'multiOptions' => array_combine($backendNames, $backendNames), + 'value' => $this->params->get('backend') + ) + ); + + $backend = $this->getUserGroupBackend($this->params->get('backend')); + if ($backend === null) { + $this->view->backend = null; + return; + } + + $query = $backend->select(array('group_name')); + $filterEditor = Widget::create('filterEditor') + ->setQuery($query) + ->setSearchColumns(array('group', 'user')) + ->preserveParams('limit', 'sort', 'dir', 'view', 'backend') + ->ignoreParams('page') + ->handleRequest($this->getRequest()); + $query->applyFilter($filterEditor->getFilter()); + $this->setupFilterControl($filterEditor); + + $this->view->groups = $query; + $this->view->backend = $backend; + + $this->setupPaginationControl($query); + $this->setupLimitControl(); + $this->setupSortControl( + array( + 'group_name' => $this->translate('Group'), + 'created_at' => $this->translate('Created at'), + 'last_modified' => $this->translate('Last modified') + ), + $query + ); + } + + /** + * Show a group + */ + public function showAction() + { + $this->assertPermission('config/authentication/groups/show'); + $groupName = $this->params->getRequired('group'); + $backend = $this->getUserGroupBackend($this->params->getRequired('backend')); + + $group = $backend->select(array( + 'group_name', + 'created_at', + 'last_modified' + ))->where('group_name', $groupName)->fetchRow(); + if ($group === false) { + $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName)); + } + + $members = $backend + ->select() + ->from('group_membership', array('user_name')) + ->where('group_name', $groupName); + + $filterEditor = Widget::create('filterEditor') + ->setQuery($members) + ->setSearchColumns(array('user')) + ->preserveParams('limit', 'sort', 'dir', 'view', 'backend', 'group') + ->ignoreParams('page') + ->handleRequest($this->getRequest()); + $members->applyFilter($filterEditor->getFilter()); + + $this->setupFilterControl($filterEditor); + $this->setupPaginationControl($members); + $this->setupLimitControl(); + $this->setupSortControl( + array( + 'user_name' => $this->translate('Username'), + 'created_at' => $this->translate('Created at'), + 'last_modified' => $this->translate('Last modified') + ), + $members + ); + + $this->view->group = $group; + $this->view->backend = $backend; + $this->view->members = $members; + $this->createShowTabs($backend->getName(), $groupName)->activate('group/show'); + + if ($this->hasPermission('config/authentication/groups/edit') && $backend instanceof Reducible) { + $removeForm = new Form(); + $removeForm->setUidDisabled(); + $removeForm->setAction( + Url::fromPath('group/removemember', array('backend' => $backend->getName(), 'group' => $groupName)) + ); + $removeForm->addElement('hidden', 'user_name', array( + 'isArray' => true, + 'decorators' => array('ViewHelper') + )); + $removeForm->addElement('hidden', 'redirect', array( + 'value' => Url::fromPath('group/show', array( + 'backend' => $backend->getName(), + 'group' => $groupName + )), + 'decorators' => array('ViewHelper') + )); + $removeForm->addElement('button', 'btn_submit', array( + 'escape' => false, + 'type' => 'submit', + 'class' => 'link-like', + 'value' => 'btn_submit', + 'decorators' => array('ViewHelper'), + 'label' => $this->view->icon('trash'), + 'title' => $this->translate('Remove this member') + )); + $this->view->removeForm = $removeForm; + } + } + + /** + * Add a group + */ + public function addAction() + { + $this->assertPermission('config/authentication/groups/add'); + $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Extensible'); + $form = new UserGroupForm(); + $form->setRedirectUrl(Url::fromPath('group/list', array('backend' => $backend->getName()))); + $form->setRepository($backend); + $form->add()->handleRequest(); + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Edit a group + */ + public function editAction() + { + $this->assertPermission('config/authentication/groups/edit'); + $groupName = $this->params->getRequired('group'); + $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Updatable'); + + $form = new UserGroupForm(); + $form->setRedirectUrl( + Url::fromPath('group/show', array('backend' => $backend->getName(), 'group' => $groupName)) + ); + $form->setRepository($backend); + + try { + $form->edit($groupName)->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName)); + } + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Remove a group + */ + public function removeAction() + { + $this->assertPermission('config/authentication/groups/remove'); + $groupName = $this->params->getRequired('group'); + $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Reducible'); + + $form = new UserGroupForm(); + $form->setRedirectUrl(Url::fromPath('group/list', array('backend' => $backend->getName()))); + $form->setRepository($backend); + + try { + $form->remove($groupName)->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName)); + } + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Add a group member + */ + public function addmemberAction() + { + $this->assertPermission('config/authentication/groups/edit'); + $groupName = $this->params->getRequired('group'); + $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Extensible'); + + $form = new AddMemberForm(); + $form->setDataSource($this->fetchUsers()) + ->setBackend($backend) + ->setGroupName($groupName) + ->setRedirectUrl( + Url::fromPath('group/show', array('backend' => $backend->getName(), 'group' => $groupName)) + ) + ->setUidDisabled(); + + try { + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName)); + } + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Remove a group member + */ + public function removememberAction() + { + $this->assertPermission('config/authentication/groups/edit'); + $this->assertHttpMethod('POST'); + $groupName = $this->params->getRequired('group'); + $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Reducible'); + + $form = new Form(array( + 'onSuccess' => function ($form) use ($groupName, $backend) { + foreach ($form->getValue('user_name') as $userName) { + try { + $backend->delete( + 'group_membership', + Filter::matchAll( + Filter::where('group_name', $groupName), + Filter::where('user_name', $userName) + ) + ); + Notification::success(sprintf( + t('User "%s" has been removed from group "%s"'), + $userName, + $groupName + )); + } catch (NotFoundError $e) { + throw $e; + } catch (Exception $e) { + Notification::error($e->getMessage()); + } + } + + $redirect = $form->getValue('redirect'); + if (! empty($redirect)) { + $form->setRedirectUrl(htmlspecialchars_decode($redirect)); + } + + return true; + } + )); + $form->setUidDisabled(); + $form->setSubmitLabel('btn_submit'); // Required to ensure that isSubmitted() is called + $form->addElement('hidden', 'user_name', array('required' => true, 'isArray' => true)); + $form->addElement('hidden', 'redirect'); + + try { + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName)); + } + } + + /** + * Fetch and return all users from all user backends + * + * @return ArrayDatasource + */ + protected function fetchUsers() + { + $users = array(); + foreach ($this->loadUserBackends('Icinga\Data\Selectable') as $backend) { + try { + foreach ($backend->select(array('user_name')) as $row) { + $users[] = $row; + } + } catch (Exception $e) { + Logger::error($e); + Notification::warning(sprintf( + $this->translate('Failed to fetch any users from backend %s. Please check your log'), + $backend->getName() + )); + } + } + + return new ArrayDatasource($users); + } + + /** + * Create the tabs to display when showing a group + * + * @param string $backendName + * @param string $groupName + */ + protected function createShowTabs($backendName, $groupName) + { + $tabs = $this->getTabs(); + $tabs->add( + 'group/show', + array( + 'title' => sprintf($this->translate('Show group %s'), $groupName), + 'label' => $this->translate('Group'), + 'icon' => 'users', + 'url' => Url::fromPath('group/show', array('backend' => $backendName, 'group' => $groupName)) + ) + ); + + return $tabs; + } + + /** + * Create the tabs to display when listing groups + */ + protected function createListTabs() + { + $tabs = $this->getTabs(); + $tabs->add( + 'group/list', + array( + 'title' => $this->translate('List groups of user group backends'), + 'label' => $this->translate('Usergroups'), + 'icon' => 'users', + 'url' => 'group/list' + ) + ); + + return $tabs; + } +} diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php index 05e66504b..5ffd1970a 100644 --- a/application/controllers/IndexController.php +++ b/application/controllers/IndexController.php @@ -1,6 +1,5 @@ getRelativeUrl()); $this->view->menuRenderer = $menu->useCustomRenderer(); } - - /** - * Render the top bar - */ - public function topbarAction() - { - $topbarHtmlParts = array(); - - /** @var Hook\TopBarHook $hook */ - $hook = null; - - foreach (Hook::all('TopBar') as $hook) { - $topbarHtmlParts[] = $hook->getHtml($this->getRequest()); - } - - $this->view->topbarHtmlParts = $topbarHtmlParts; - - - $this->renderScript('parts/topbar.phtml'); - } } diff --git a/application/controllers/ListController.php b/application/controllers/ListController.php index f8b0ed1d6..9a20755c9 100644 --- a/application/controllers/ListController.php +++ b/application/controllers/ListController.php @@ -1,13 +1,14 @@ getTabs()->add($action, array( - 'title' => ucfirst($action), + 'label' => ucfirst($action), 'url' => Url::fromPath( 'list/' . str_replace(' ', '', $action) ) - ))->activate($action); + ))->extend(new OutputFormat())->extend(new DashboardAction())->activate($action); } /** @@ -38,20 +39,20 @@ class ListController extends Controller public function applicationlogAction() { if (! Logger::writesToFile()) { - throw new ActionError('Site not found', 404); + $this->httpNotFound('Page not found'); } $this->addTitleTab('application log'); - $pattern = '/^(?[0-9]{4}(-[0-9]{2}){2}' // date - . 'T[0-9]{2}(:[0-9]{2}){2}([\\+\\-][0-9]{2}:[0-9]{2})?)' // time - . ' - (?[A-Za-z]+)' // loglevel - . ' - (?.*)$/'; // message - $loggerWriter = Logger::getInstance()->getWriter(); $resource = new FileReader(new ConfigObject(array( - 'filename' => $loggerWriter->getPath(), - 'fields' => $pattern + 'filename' => Config::app()->get('logging', 'file'), + 'fields' => '/(?[0-9]{4}(?:-[0-9]{2}){2}' // date + . 'T[0-9]{2}(?::[0-9]{2}){2}(?:[\+\-][0-9]{2}:[0-9]{2})?)' // time + . ' - (?[A-Za-z]+) - (?.*)(?!.)/msS' // loglevel, message ))); - $this->view->logData = $resource->select()->order('DESC')->paginate(); + $this->view->logData = $resource->select()->order('DESC'); + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->logData); } } diff --git a/application/controllers/PreferenceController.php b/application/controllers/PreferenceController.php index 7acab7c25..a466ed48f 100644 --- a/application/controllers/PreferenceController.php +++ b/application/controllers/PreferenceController.php @@ -1,13 +1,12 @@ new Tab( array( - 'title' => t('Preferences'), - 'url' => Url::fromPath('/preference') + 'title' => t('Adjust the preferences of Icinga Web 2 according to your needs'), + 'label' => t('Preferences'), + 'url' => Url::fromPath('/preference') ) ) ); @@ -39,15 +39,17 @@ class PreferenceController extends BasePreferenceController */ public function indexAction() { - $storeConfig = Config::app()->getSection('preferences'); - if ($storeConfig->isEmpty()) { - throw new ConfigurationError(t('You need to configure how to store preferences first.')); - } - + $config = Config::app()->getSection('global'); $user = $this->getRequest()->getUser(); + $form = new PreferenceForm(); $form->setPreferences($user->getPreferences()); - $form->setStore(PreferencesStore::create($storeConfig, $user)); + if ($config->get('config_backend', 'ini') !== 'none') { + $form->setStore(PreferencesStore::create(new ConfigObject(array( + 'store' => $config->get('config_backend', 'ini'), + 'resource' => $config->config_resource + )), $user)); + } $form->handleRequest(); $this->view->form = $form; diff --git a/application/controllers/RolesController.php b/application/controllers/RoleController.php similarity index 66% rename from application/controllers/RolesController.php rename to application/controllers/RoleController.php index 90163812e..971a7efc9 100644 --- a/application/controllers/RolesController.php +++ b/application/controllers/RoleController.php @@ -1,41 +1,30 @@ view->tabs = Widget::create('tabs')->add('index', array( - 'title' => $this->translate('Application'), - 'url' => 'config' - ))->add('authentication', array( - 'title' => $this->translate('Authentication'), - 'url' => 'config/authentication' - ))->add('resources', array( - 'title' => $this->translate('Resources'), - 'url' => 'config/resource' - ))->add('roles', array( - 'title' => $this->translate('Roles'), - 'url' => 'roles' - )); - } - - public function indexAction() - { - $this->view->tabs->activate('roles'); + $this->assertPermission('config/authentication/roles/show'); + $this->createListTabs()->activate('role/list'); $this->view->roles = Config::app('roles', true); } - public function newAction() + /** + * Create a new role + */ + public function addAction() { + $this->assertPermission('config/authentication/roles/add'); $role = new RoleForm(array( 'onSuccess' => function (RoleForm $role) { $name = $role->getElement('name')->getValue(); @@ -54,15 +43,23 @@ class RolesController extends ActionController } )); $role + ->setTitle($this->translate('New Role')) ->setSubmitLabel($this->translate('Create Role')) ->setIniConfig(Config::app('roles', true)) - ->setRedirectUrl('roles') + ->setRedirectUrl('role/list') ->handleRequest(); $this->view->form = $role; + $this->render('form'); } - public function updateAction() + /** + * Update a role + * + * @throws Zend_Controller_Action_Exception If the required parameter 'role' is missing or the role does not exist + */ + public function editAction() { + $this->assertPermission('config/authentication/roles/edit'); $name = $this->_request->getParam('role'); if (empty($name)) { throw new Zend_Controller_Action_Exception( @@ -71,6 +68,7 @@ class RolesController extends ActionController ); } $role = new RoleForm(); + $role->setTitle(sprintf($this->translate('Update Role %s'), $name)); $role->setSubmitLabel($this->translate('Update Role')); try { $role @@ -99,14 +97,20 @@ class RolesController extends ActionController } return false; }) - ->setRedirectUrl('roles') + ->setRedirectUrl('role/list') ->handleRequest(); - $this->view->name = $name; $this->view->form = $role; + $this->render('form'); } + /** + * Remove a role + * + * @throws Zend_Controller_Action_Exception If the required parameter 'role' is missing or the role does not exist + */ public function removeAction() { + $this->assertPermission('config/authentication/roles/remove'); $name = $this->_request->getParam('role'); if (empty($name)) { throw new Zend_Controller_Action_Exception( @@ -141,10 +145,32 @@ class RolesController extends ActionController } )); $confirmation + ->setTitle(sprintf($this->translate('Remove Role %s'), $name)) ->setSubmitLabel($this->translate('Remove Role')) - ->setRedirectUrl('roles') + ->setRedirectUrl('role/list') ->handleRequest(); - $this->view->name = $name; $this->view->form = $confirmation; + $this->render('form'); + } + + /** + * Create the tabs to display when listing roles + */ + protected function createListTabs() + { + $tabs = $this->getTabs(); + $tabs->add( + 'role/list', + array( + 'title' => $this->translate( + 'Configure roles to permit or restrict users and groups accessing Icinga Web 2' + ), + 'label' => $this->translate('Roles'), + 'url' => 'role/list', + 'baseTarget' => '_main' + ) + ); + + return $tabs; } } diff --git a/application/controllers/SearchController.php b/application/controllers/SearchController.php index 68fd5dd3d..aa5cfaf9d 100644 --- a/application/controllers/SearchController.php +++ b/application/controllers/SearchController.php @@ -1,6 +1,5 @@ view->dashboard = SearchDashboard::search($this->params->get('q')); + $searchDashboard = new SearchDashboard(); + $searchDashboard->setUser($this->Auth()->getUser()); + $this->view->dashboard = $searchDashboard->search($this->params->get('q')); // NOTE: This renders the dashboard twice. Remove this once we can catch exceptions thrown in view scripts. $this->view->dashboard->render(); diff --git a/application/controllers/StaticController.php b/application/controllers/StaticController.php index fc5f1354f..9f461838c 100644 --- a/application/controllers/StaticController.php +++ b/application/controllers/StaticController.php @@ -1,6 +1,5 @@ assertPermission('config/authentication/users/show'); + $this->createListTabs()->activate('user/list'); + $backendNames = array_map( + function ($b) { return $b->getName(); }, + $this->loadUserBackends('Icinga\Data\Selectable') + ); + if (empty($backendNames)) { + return; + } + + $this->view->backendSelection = new Form(); + $this->view->backendSelection->setAttrib('class', 'backend-selection'); + $this->view->backendSelection->setUidDisabled(); + $this->view->backendSelection->setMethod('GET'); + $this->view->backendSelection->setTokenDisabled(); + $this->view->backendSelection->addElement( + 'select', + 'backend', + array( + 'autosubmit' => true, + 'label' => $this->translate('Authentication Backend'), + 'multiOptions' => array_combine($backendNames, $backendNames), + 'value' => $this->params->get('backend') + ) + ); + + $backend = $this->getUserBackend($this->params->get('backend')); + if ($backend === null) { + $this->view->backend = null; + return; + } + + $query = $backend->select(array('user_name')); + $filterEditor = Widget::create('filterEditor') + ->setQuery($query) + ->setSearchColumns(array('user')) + ->preserveParams('limit', 'sort', 'dir', 'view', 'backend') + ->ignoreParams('page') + ->handleRequest($this->getRequest()); + $query->applyFilter($filterEditor->getFilter()); + $this->setupFilterControl($filterEditor); + + $this->view->users = $query; + $this->view->backend = $backend; + + $this->setupPaginationControl($query); + $this->setupLimitControl(); + $this->setupSortControl( + array( + 'user_name' => $this->translate('Username'), + 'is_active' => $this->translate('Active'), + 'created_at' => $this->translate('Created at'), + 'last_modified' => $this->translate('Last modified') + ), + $query + ); + } + + /** + * Show a user + */ + public function showAction() + { + $this->assertPermission('config/authentication/users/show'); + $userName = $this->params->getRequired('user'); + $backend = $this->getUserBackend($this->params->getRequired('backend')); + + $user = $backend->select(array( + 'user_name', + 'is_active', + 'created_at', + 'last_modified' + ))->where('user_name', $userName)->fetchRow(); + if ($user === false) { + $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName)); + } + + $memberships = $this->loadMemberships(new User($userName))->select(); + + $filterEditor = Widget::create('filterEditor') + ->setQuery($memberships) + ->setSearchColumns(array('group_name')) + ->preserveParams('limit', 'sort', 'dir', 'view', 'backend', 'user') + ->ignoreParams('page') + ->handleRequest($this->getRequest()); + $memberships->applyFilter($filterEditor->getFilter()); + + $this->setupFilterControl($filterEditor); + $this->setupPaginationControl($memberships); + $this->setupLimitControl(); + $this->setupSortControl( + array( + 'group_name' => $this->translate('Group') + ), + $memberships + ); + + if ($this->hasPermission('config/authentication/groups/edit')) { + $extensibleBackends = $this->loadUserGroupBackends('Icinga\Data\Extensible'); + $this->view->showCreateMembershipLink = ! empty($extensibleBackends); + } else { + $this->view->showCreateMembershipLink = false; + } + + $this->view->user = $user; + $this->view->backend = $backend; + $this->view->memberships = $memberships; + $this->createShowTabs($backend->getName(), $userName)->activate('user/show'); + + if ($this->hasPermission('config/authentication/groups/edit')) { + $removeForm = new Form(); + $removeForm->setUidDisabled(); + $removeForm->addElement('hidden', 'user_name', array( + 'isArray' => true, + 'value' => $userName, + 'decorators' => array('ViewHelper') + )); + $removeForm->addElement('hidden', 'redirect', array( + 'value' => Url::fromPath('user/show', array( + 'backend' => $backend->getName(), + 'user' => $userName + )), + 'decorators' => array('ViewHelper') + )); + $removeForm->addElement('button', 'btn_submit', array( + 'escape' => false, + 'type' => 'submit', + 'class' => 'link-like', + 'value' => 'btn_submit', + 'decorators' => array('ViewHelper'), + 'label' => $this->view->icon('trash'), + 'title' => $this->translate('Cancel this membership') + )); + $this->view->removeForm = $removeForm; + } + } + + /** + * Add a user + */ + public function addAction() + { + $this->assertPermission('config/authentication/users/add'); + $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Extensible'); + $form = new UserForm(); + $form->setRedirectUrl(Url::fromPath('user/list', array('backend' => $backend->getName()))); + $form->setRepository($backend); + $form->add()->handleRequest(); + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Edit a user + */ + public function editAction() + { + $this->assertPermission('config/authentication/users/edit'); + $userName = $this->params->getRequired('user'); + $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Updatable'); + + $form = new UserForm(); + $form->setRedirectUrl(Url::fromPath('user/show', array('backend' => $backend->getName(), 'user' => $userName))); + $form->setRepository($backend); + + try { + $form->edit($userName)->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName)); + } + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Remove a user + */ + public function removeAction() + { + $this->assertPermission('config/authentication/users/remove'); + $userName = $this->params->getRequired('user'); + $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Reducible'); + + $form = new UserForm(); + $form->setRedirectUrl(Url::fromPath('user/list', array('backend' => $backend->getName()))); + $form->setRepository($backend); + + try { + $form->remove($userName)->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName)); + } + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Create a membership for a user + */ + public function createmembershipAction() + { + $this->assertPermission('config/authentication/groups/edit'); + $userName = $this->params->getRequired('user'); + $backend = $this->getUserBackend($this->params->getRequired('backend')); + + if ($backend->select()->where('user_name', $userName)->count() === 0) { + $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName)); + } + + $backends = $this->loadUserGroupBackends('Icinga\Data\Extensible'); + if (empty($backends)) { + throw new ConfigurationError($this->translate( + 'You\'ll need to configure at least one user group backend first that allows to create new memberships' + )); + } + + $form = new CreateMembershipForm(); + $form->setBackends($backends) + ->setUsername($userName) + ->setRedirectUrl(Url::fromPath('user/show', array('backend' => $backend->getName(), 'user' => $userName))) + ->handleRequest(); + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Fetch and return the given user's groups from all user group backends + * + * @param User $user + * + * @return ArrayDatasource + */ + protected function loadMemberships(User $user) + { + $groups = $alreadySeen = array(); + foreach ($this->loadUserGroupBackends() as $backend) { + try { + foreach ($backend->getMemberships($user) as $groupName) { + if (array_key_exists($groupName, $alreadySeen)) { + continue; // Ignore duplicate memberships + } + + $alreadySeen[$groupName] = null; + $groups[] = (object) array( + 'group_name' => $groupName, + 'backend' => $backend + ); + } + } catch (Exception $e) { + Logger::error($e); + Notification::warning(sprintf( + $this->translate('Failed to fetch memberships from backend %s. Please check your log'), + $backend->getName() + )); + } + } + + return new ArrayDatasource($groups); + } + + /** + * Create the tabs to display when showing a user + * + * @param string $backendName + * @param string $userName + */ + protected function createShowTabs($backendName, $userName) + { + $tabs = $this->getTabs(); + $tabs->add( + 'user/show', + array( + 'title' => sprintf($this->translate('Show user %s'), $userName), + 'label' => $this->translate('User'), + 'icon' => 'user', + 'url' => Url::fromPath('user/show', array('backend' => $backendName, 'user' => $userName)) + ) + ); + + return $tabs; + } + + /** + * Create the tabs to display when listing users + */ + protected function createListTabs() + { + $tabs = $this->getTabs(); + $tabs->add( + 'user/list', + array( + 'title' => $this->translate('List users of authentication backends'), + 'label' => $this->translate('Users'), + 'icon' => 'user', + 'url' => 'user/list' + ) + ); + + return $tabs; + } +} diff --git a/application/controllers/UsergroupbackendController.php b/application/controllers/UsergroupbackendController.php new file mode 100644 index 000000000..4c641586c --- /dev/null +++ b/application/controllers/UsergroupbackendController.php @@ -0,0 +1,167 @@ +assertPermission('config/application/usergroupbackend'); + } + + /** + * Redirect to this controller's list action + */ + public function indexAction() + { + $this->redirectNow('usergroupbackend/list'); + } + + /** + * Show a list of all user group backends + */ + public function listAction() + { + $this->view->backendNames = Config::app('groups')->keys(); + $this->createListTabs()->activate('usergroupbackend'); + } + + /** + * Create a new user group backend + */ + public function createAction() + { + $form = new UserGroupBackendForm(); + $form->setRedirectUrl('usergroupbackend/list'); + $form->setTitle($this->translate('Create New User Group Backend')); + $form->addDescription($this->translate('Create a new backend to associate users and groups with.')); + $form->setIniConfig(Config::app('groups')); + $form->setOnSuccess(function (UserGroupBackendForm $form) { + try { + $form->add(array_filter($form->getValues())); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(t('User group backend successfully created')); + return true; + } + + return false; + }); + $form->handleRequest(); + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Edit an user group backend + */ + public function editAction() + { + $backendName = $this->params->getRequired('backend'); + + $form = new UserGroupBackendForm(); + $form->setRedirectUrl('usergroupbackend/list'); + $form->setTitle(sprintf($this->translate('Edit User Group Backend %s'), $backendName)); + $form->setIniConfig(Config::app('groups')); + $form->setOnSuccess(function (UserGroupBackendForm $form) use ($backendName) { + try { + $form->edit($backendName, array_map( + function ($v) { + return $v !== '' ? $v : null; + }, + $form->getValues() + )); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(sprintf(t('User group backend "%s" successfully updated'), $backendName)); + return true; + } + + return false; + }); + + try { + $form->load($backendName); + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('User group backend "%s" not found'), $backendName)); + } + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Remove a user group backend + */ + public function removeAction() + { + $backendName = $this->params->getRequired('backend'); + + $backendForm = new UserGroupBackendForm(); + $backendForm->setIniConfig(Config::app('groups')); + $form = new ConfirmRemovalForm(); + $form->setRedirectUrl('usergroupbackend/list'); + $form->setTitle(sprintf($this->translate('Remove User Group Backend %s'), $backendName)); + $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($backendName, $backendForm) { + try { + $backendForm->delete($backendName); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($backendForm->save()) { + Notification::success(sprintf(t('User group backend "%s" successfully removed'), $backendName)); + return true; + } + + return false; + }); + $form->handleRequest(); + + $this->view->form = $form; + $this->render('form'); + } + + /** + * Create the tabs for the application configuration + */ + protected function createListTabs() + { + $tabs = $this->getTabs(); + $tabs->add('userbackend', array( + 'title' => $this->translate('Configure how users authenticate with and log into Icinga Web 2'), + 'label' => $this->translate('User Backends'), + 'url' => 'config/userbackend' + )); + $tabs->add('usergroupbackend', array( + 'title' => $this->translate('Configure how users are associated with groups by Icinga Web 2'), + 'label' => $this->translate('User Group Backends'), + 'url' => 'usergroupbackend/list' + )); + return $tabs; + } +} diff --git a/application/fonts/fontello-ifont/LICENSE.txt b/application/fonts/fontello-ifont/LICENSE.txt index 29b99c574..7e9af2aa8 100644 --- a/application/fonts/fontello-ifont/LICENSE.txt +++ b/application/fonts/fontello-ifont/LICENSE.txt @@ -37,3 +37,21 @@ Font license info Homepage: http://www.entypo.com +## Fontelico + + Copyright (C) 2012 by Fontello project + + Author: Crowdsourced, for Fontello project + License: SIL (http://scripts.sil.org/OFL) + Homepage: http://fontello.com + + +## Typicons + + (c) Stephen Hutchings 2012 + + Author: Stephen Hutchings + License: SIL (http://scripts.sil.org/OFL) + Homepage: http://typicons.com/ + + diff --git a/application/fonts/fontello-ifont/README.txt b/application/fonts/fontello-ifont/README.txt index 43e23f283..a91438a9a 100644 --- a/application/fonts/fontello-ifont/README.txt +++ b/application/fonts/fontello-ifont/README.txt @@ -11,7 +11,7 @@ webfont pack. Details available in LICENSE.txt file. - If your project is open-source, usually, it will be ok to make LICENSE.txt file publically available in your repository. -- Fonts, used in Fontello, don't require to make clickable links on your site. +- Fonts, used in Fontello, don't require a clickable link on your site. But any kind of additional authors crediting is welcome. ================================================================================ @@ -29,8 +29,8 @@ Comments on archive content - LICENSE.txt - license info about source fonts, used to build your one. -- config.json - keeps your settings. You can import it back to fontello anytime, - to continue your work +- config.json - keeps your settings. You can import it back into fontello + anytime, to continue your work Why so many CSS files ? @@ -38,17 +38,17 @@ Why so many CSS files ? Because we like to fit all your needs :) -- basic file, .css - is usually enougth, in contains @font-face - and character codes definition +- basic file, .css - is usually enough, it contains @font-face + and character code definitions - *-ie7.css - if you need IE7 support, but still don't wish to put char codes directly into html - *-codes.css and *-ie7-codes.css - if you like to use your own @font-face - rules, but still wish to benefit of css generation. That can be very - convenient for automated assets build systems. When you need to update font - - no needs to manually edit files, just override old version with archive - content. See fontello source codes for example. + rules, but still wish to benefit from css generation. That can be very + convenient for automated asset build systems. When you need to update font - + no need to manually edit files, just override old version with archive + content. See fontello source code for examples. - *-embedded.css - basic css file, but with embedded WOFF font, to avoid CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain. @@ -63,11 +63,11 @@ Because we like to fit all your needs :) Attention for server setup -------------------------- -You MUST setup server to reply with proper `mime-types` for font files. In other -case, some browsers will fail to show fonts. +You MUST setup server to reply with proper `mime-types` for font files - +otherwise some browsers will fail to show fonts. Usually, `apache` already has necessary settings, but `nginx` and other -webservers should be tuned. Here is list of mime types for our file extentions: +webservers should be tuned. Here is list of mime types for our file extensions: - `application/vnd.ms-fontobject` - eot - `application/x-font-woff` - woff diff --git a/application/fonts/fontello-ifont/config.json b/application/fonts/fontello-ifont/config.json index 6e2d2af3b..3a7894589 100644 --- a/application/fonts/fontello-ifont/config.json +++ b/application/fonts/fontello-ifont/config.json @@ -6,6 +6,12 @@ "units_per_em": 1000, "ascent": 850, "glyphs": [ + { + "uid": "9bc2902722abb366a213a052ade360bc", + "css": "spin6", + "code": 59508, + "src": "fontelico" + }, { "uid": "9dd9e835aebe1060ba7190ad2b2ed951", "css": "search", @@ -666,6 +672,30 @@ "code": 59492, "src": "entypo" }, + { + "uid": "c16a63e911bc47b46dc2a7129d2f0c46", + "css": "down-small", + "code": 59509, + "src": "typicons" + }, + { + "uid": "58b78b6ca784d5c3db5beefcd9e18061", + "css": "left-small", + "code": 59510, + "src": "typicons" + }, + { + "uid": "877a233d7fdca8a1d82615b96ed0d7a2", + "css": "right-small", + "code": 59511, + "src": "typicons" + }, + { + "uid": "62bc6fe2a82e4864e2b94d4c0985ee0c", + "css": "up-small", + "code": 59512, + "src": "typicons" + }, { "uid": "b90d80c250a9bbdd6cd3fe00e6351710", "css": "ok", diff --git a/application/fonts/fontello-ifont/css/ifont-codes.css b/application/fonts/fontello-ifont/css/ifont-codes.css index 7bd25fd38..78c477ddb 100644 --- a/application/fonts/fontello-ifont/css/ifont-codes.css +++ b/application/fonts/fontello-ifont/css/ifont-codes.css @@ -114,4 +114,9 @@ .icon-chart-area:before { content: '\e870'; } /* '' */ .icon-chart-bar:before { content: '\e871'; } /* '' */ .icon-beaker:before { content: '\e872'; } /* '' */ -.icon-magic:before { content: '\e873'; } /* '' */ \ No newline at end of file +.icon-magic:before { content: '\e873'; } /* '' */ +.icon-spin6:before { content: '\e874'; } /* '' */ +.icon-down-small:before { content: '\e875'; } /* '' */ +.icon-left-small:before { content: '\e876'; } /* '' */ +.icon-right-small:before { content: '\e877'; } /* '' */ +.icon-up-small:before { content: '\e878'; } /* '' */ \ No newline at end of file diff --git a/application/fonts/fontello-ifont/css/ifont-embedded.css b/application/fonts/fontello-ifont/css/ifont-embedded.css index 92bcfdff9..c069e28e6 100644 --- a/application/fonts/fontello-ifont/css/ifont-embedded.css +++ b/application/fonts/fontello-ifont/css/ifont-embedded.css @@ -1,15 +1,15 @@ @font-face { font-family: 'ifont'; - src: url('../font/ifont.eot?75097146'); - src: url('../font/ifont.eot?75097146#iefix') format('embedded-opentype'), - url('../font/ifont.svg?75097146#ifont') format('svg'); + src: url('../font/ifont.eot?12843713'); + src: url('../font/ifont.eot?12843713#iefix') format('embedded-opentype'), + url('../font/ifont.svg?12843713#ifont') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'ifont'; - src: url('data:application/octet-stream;base64,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') format('woff'), - url('data:application/octet-stream;base64,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BjYGFQ4BFS4BNx4BFxYHBgcGFxQGFgGtdMZycsboyG4GerwBEgEIAwECBAMRFRMKAQwEDAMBBwYEBAoFBgQBCAEGAQQEBAIEBgEGAggJBQQFBQMBCAwBBRwHAgIBCAEOAQIHCQMEBAEEAgMBBwoCBAUNBAIUDhMECAYBAgECBQkCARMJAgQGBQYKAwgEBwUDAgYJBAYBBQkEBQMDAgUEAQ4HCw8EEAMDAQgECAEIAwEIBAQEAwMEAgQSBQMMDAEDAwIMGRsDAwgFEwUDCwQNCwEEAgYECAQJBFEyBAUCBgUDARgKAQIHBQQDBAQEAQIBAQECCgcHEgQHCQQDCAQCDgEBAgIOAgQCAg8IAwQDAgMFAQQKCgEECAQFDAcCAwgDCQcWBgYFCAgQBBQKAQIEAgYDDgMEAQoFCBEKAgICAgEFAgQBCgIDDAMCCAECCAMBAwIHCwQBAgIIFAMICgECAQQCAwUCAQIBBAECAgQYAwkDAQEBAw0CDgQCAwEEAwUCBggEAgIBCAQEBwgFBwwEBAICAgYBBQQDAgMFBwQDAhIBBAICBQwCCQICCggFCQIIBAIKCQ0JaXJRAQwBDQEEAxUBAwUCAwICAQUMCAMEBQEKAQMBAQQIBAoBBwYCCgIEAQwBAQICBAsPAQIJCgEDC3TE6sR0dMTqxHT+3QEIAgYGAQQIAwULAQwCAgQMAQoHAgMEAgQBAgYMBQYDCgEGBAEBAgICAQMDAgEDCAQCBgIDAwQFBAYHBAYICgcEBQYFDAMBAgQCAQMMCQ4DBAUHCAUDEQIDDgcGDAMBAwkCBwoDBgEOBAoEAQIFAgIGCgQHBwcBCQUIBwgDAgcDAgQCBgIEBQoDAw4CBQEBAgUEBwIBCggPAQMCAgcEAw4DAgQDBwMGBAQBAS1PBAEIBAMEBg8KAgYEBQQFDgkUCwIBBhoCARcFBAYDBRQDAxAFAgEECAUIBAELFw4FDAICBAQMCA4EDgEKCxQHCAEFAw0CAQIBEgMKBAQJBQYCAwoDAgMFDAIQCRMDAwQEBgIECgcOAQUCBAEEAgIQBQ8FAgUDAgsCCAQEAgIEGA4JDgUJAQQGAQIDAQEBBAMGBwYFAg8KAQQBAgMBAgMIBRcEAggIAwQPAgoKBQECAwQLCQUCAgICBgIKBwYFBAQEAwEECgQGAQcCAQcGBQMEAQEBBQQC/g0VVQIC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aQBmAG8AbgB0AEcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAAcwB2AGcAMgB0AHQAZgAgAGYAcgBvAG0AIABGAG8AbgB0AGUAbABsAG8AIABwAHIAbwBqAGUAYwB0AC4AaAB0AHQAcAA6AC8ALwBmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQAAAAACAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHUAAAECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgEjASQBJQEmAScBKAEpASoBKwEsAS0BLgEvATABMQEyATMBNAE1ATYBNwE4ATkBOgE7ATwBPQE+AT8BQAFBAUIBQwFEAUUBRgFHAUgBSQFKAUsBTAFNAU4BTwFQAVEBUgFTAVQBVQFWAVcBWAFZAVoBWwFcAV0BXgFfAWABYQFiAWMBZAFlAWYBZwFoAWkBagFrAWwBbQFuAW8BcAFxAXIBcwF0AXUJZGFzaGJvYXJkBHVzZXIFdXNlcnMCb2sGY2FuY2VsBHBsdXMFbWludXMMZm9sZGVyLWVtcHR5CGRvd25sb2FkBnVwbG9hZANnaXQFY3ViZXMIZGF0YWJhc2UFZ2F1Z2UHc2l0ZW1hcAxzb3J0LW5hbWUtdXAOc29ydC1uYW1lLWRvd24JbWVnYXBob25lA2J1ZwV0YXNrcwZmaWx0ZXIDb2ZmBGJvb2sFcGFzdGUIc2Npc3NvcnMFZ2xvYmUFY2xvdWQFZmxhc2gIYmFyY2hhcnQIZG93bi1kaXIGdXAtZGlyCGxlZnQtZGlyCXJpZ2h0LWRpcglkb3duLW9wZW4KcmlnaHQtb3Blbgd1cC1vcGVuCWxlZnQtb3BlbgZ1cC1iaWcJcmlnaHQtYmlnCGxlZnQtYmlnCGRvd24tYmlnD3Jlc2l6ZS1mdWxsLWFsdAtyZXNpemUtZnVsbAxyZXNpemUtc21hbGwEbW92ZRFyZXNpemUtaG9yaXpvbnRhbA9yZXNpemUtdmVydGljYWwHem9vbS1pbgVibG9jawh6b29tLW91dAlsaWdodGJ1bGIFY2xvY2sJdm9sdW1lLXVwC3ZvbHVtZS1kb3duCnZvbHVtZS1vZmYEbXV0ZQNtaWMHZW5kdGltZQlzdGFydHRp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JSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbErARQrLbBCLLA1Ky6xKwEUKy2wQyywNishIyAgPLAEI0IjOLErARQrsARDLrArKy2wRCywABUgR7AAI0KyAAEBFRQTLrAxKi2wRSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKxAAAqsQAFQrEACCqxAAVCsQAIKrEABUK5AAAACSqxAAVCuQAAAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbEADCq4Af+FsASNsQIARAA=') format('truetype'); + src: url('data:application/octet-stream;base64,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') format('woff'), + url('data:application/octet-stream;base64,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BjYGFQ4BFS4BNx4BFxYHBgcGFxQGFgGtdMZycsboyG4GerwBEgEIAwECBAMRFRMKAQwEDAMBBwYEBAoFBgQBCAEGAQQEBAIEBgEGAggJBQQFBQMBCAwBBRwHAgIBCAEOAQIHCQMEBAEEAgMBBwoCBAUNBAIUDhMECAYBAgECBQkCARMJAgQGBQYKAwgEBwUDAgYJBAYBBQkEBQMDAgUEAQ4HCw8EEAMDAQgECAEIAwEIBAQEAwMEAgQSBQMMDAEDAwIMGRsDAwgFEwUDCwQNCwEEAgYECAQJBFEyBAUCBgUDARgKAQIHBQQDBAQEAQIBAQECCgcHEgQHCQQDCAQCDgEBAgIOAgQCAg8IAwQDAgMFAQQKCgEECAQFDAcCAwgDCQcWBgYFCAgQBBQKAQIEAgYDDgMEAQoFCBEKAgICAgEFAgQBCgIDDAMCCAECCAMBAwIHCwQBAgIIFAMICgECAQQCAwUCAQIBBAECAgQYAwkDAQEBAw0CDgQCAwEEAwUCBggEAgIBCAQEBwgFBwwEBAICAgYBBQQDAgMFBwQDAhIBBAICBQwCCQICCggFCQIIBAIKCQ0JaXJRAQwBDQEEAxUBAwUCAwICAQUMCAMEBQEKAQMBAQQIBAoBBwYCCgIEAQwBAQICBAsPAQIJCgEDC3TE6sR0dMTqxHT+3QEIAgYGAQQIAwULAQwCAgQMAQoHAgMEAgQBAgYMBQYDCgEGBAEBAgICAQMDAgEDCAQCBgIDAwQFBAYHBAYICgcEBQYFDAMBAgQCAQMMCQ4DBAUHCAUDEQIDDgcGDAMBAwkCBwoDBgEOBAoEAQIFAgIGCgQHBwcBCQUIBwgDAgcDAgQCBgIEBQoDAw4CBQEBAgUEBwIBCggPAQMCAgcEAw4DAgQDBwMGBAQBAS1PBAEIBAMEBg8KAgYEBQQFDgkUCwIBBhoCARcFBAYDBRQDAxAFAgEECAUIBAELFw4FDAICBAQMCA4EDgEKCxQHCAEFAw0CAQIBEgMKBAQJBQYCAwoDAgMFDAIQCRMDAwQEBgIECgcOAQUCBAEEAgIQBQ8FAgUDAgsCCAQEAgIEGA4JDgUJAQQGAQIDAQEBBAMGBwYFAg8KAQQBAgMBAgMIBRcEAggIAwQPAgoKBQECAwQLCQUCAgICBgIKBwYFBAQEAwEECgQGAQcCAQcGBQMEAQEBBQQC/g0VVQICBQQGAg8BAQIBAgEBAwIKAwMEAQIDAgYHAw4GAgEFBAIIAQIIAwMCAgUcCBEJDgkMAgQQBwAB////+QQwAwsAGwAGsw4DAS0rJRQGByEiJjc0NjcmNTQ2MzIWFzYzMhYVFAceAQQvfFr9oWeUAVBAAah2WI4iJzY7VBdIXs9ZfAGSaEp6Hg8JdqhkTiNUOyojEXQAAAAB//7/agH4AwsAIAAGsxQEAS0rARYHAQYjJy4BNxMHBiMiJyY3Ez4BOwEyFhUUBwM3NjMyAe4KBv7SBxAICQoCbuICBQoHCgNwAg4ItwsOAmDdBQILAhYLDf16DgEDEAgBwzgBBwgNAc0ICg4KBAb+/jYCAAUAAP+xA+gDCwAPAB8ALwA/AE8AD0AMS0M7MysjGxMLAwUtKzcVFAYrASImPQE0NjsBMhY3FRQGKwEiJj0BNDY7ATIWNxEUBisBIiY1ETQ2OwEyFjcRFAYrASImNRE0NjsBMhYTERQGKwEiJjURNDY7ATIWjwoIawgKCghrCArWCghrCAoKCGsICtYKB2wHCgoHbAcK1woIawgKCghrCArWCghrCAoKCGsICi5rCAoKCGsICgpAswgKCgizCAoKh/6+CAoKCAFCCAoKzv3oCAoKCAIYCAoKARb8yggKCggDNggKCgAAAAABAAAAAAI8Ae0ADgAGswoEAS0rARQPAQYiLwEmNDYzITIWAjsK+gscC/oLFg4B9A4WAckOC/oLC/oLHBYWAAAAAf//AAACOwHJAA4ABrMKAgEtKyUUBichIi4BPwE2Mh8BFgI7FA/+DA8UAgz6Ch4K+gqrDhYBFB4L+goK+gsAAAEAAAAAAWcCfAANAAazCwMBLSsBERQGIi8BJjQ/ATYyFgFlFCAJ+goK+gscGAJY/gwOFgv6CxwL+gsWAAEAAAAAAUECfQAOAAazCwQBLSsBFA8BBiImNRE0PgEfARYBQQr6CxwWFhwL+goBXg4L+gsWDgH0DxQCDPoKAAABAAD/5wO2AikAFAAGswoCAS0rCQEGIicBJjQ/ATYyFwkBNjIfARYUA6v+YgoeCv5iCwtcCx4KASgBKAscDFwLAY/+YwsLAZ0LHgpcCwv+2AEoCwtcCxwAAQAA/8ACdANDABQABrMPAgEtKwkBBiIvASY0NwkBJjQ/ATYyFwEWFAJq/mILHAxcCwsBKP7YCwtcCx4KAZ4KAWn+YQoKXQscCwEpASgLHAtdCgr+YgscAAEAAAAAA7YCRgAUAAazDwIBLSslBwYiJwkBBiIvASY0NwE2MhcBFhQDq1wLHgr+2P7YCxwMXAsLAZ4LHAsBngtrXAoKASn+1woKXAseCgGeCgr+YgscAAABAAD/wAKYA0MAFAAGsw8HAS0rCQIWFA8BBiInASY0NwE2Mh8BFhQCjf7YASgLC1wLHAv+YgsLAZ4KHgpcCwKq/tj+1woeCl0KCgGfCh4KAZ4KCl0KHgAAAQAA/7EDgwLnAB4ABrMaCwEtKwEUDwEGIi8BERQGByMiJjURBwYiLwEmNDcBNjIXARYDgxUqFTsVpCgfRx4qpBQ8FCoVFQFrFDwVAWsVATQcFioVFaT+dx0kASYcAYmkFRUqFTsVAWsVFf6VFgAAAAEAAP+IAzUC7QAeAAazGgQBLSsBFAcBBiIvASY0PwEhIiY9ATQ2FyEnJjQ/ATYyFwEWAzUU/pUWOhUqFhaj/ncdJCQdAYmjFhYqFToWAWsUAToeFP6UFBQqFTwVoyoeRx4qAaQVPBQqFRX+lRQAAAABAAD/iANZAu0AHQAGsxMLAS0rARUUBiMhFxYUDwEGIicBJjQ3ATYyHwEWFA8BITIWA1kkHf53pBUVKhU7Ff6UFBQBbBU6FioVFaQBiR0kAV5HHiqkFDwUKxQUAWwVOhYBaxUVKhU6FqQoAAABAAD/zwODAwsAHgAGsxMEAS0rARQHAQYiJwEmND8BNjIfARE0NjczMhYVETc2Mh8BFgODFf6VFjoV/pUVFSkWOhWkKh5HHSqkFTsVKhUBgh4U/pQVFQFsFDsWKRUVpAGJHSoBLBz+d6QVFSoVAAAAAQAA/7EDWgMLAEMABrMsCQEtKwEHFzc2Fh0BFAYrASInJj8BJwcXFgcGKwEiJic1NDYfATcnBwYjIicmPQE0NjsBMhYPARc3JyY2OwEyFgcVFAcGIyInAszGxlAQLRQQ+hcJChFRxsZQEQkKF/oPFAEsEVDGxlALDgcHFhYO+hcTEVDGxlERExf6DxYBFgcHDgsCJMbGUBITGPoOFhcVEVHGxlERFRcWDvoYExJQxsZQCwMJGPoOFi0QUcbGURAtFg76GAkDCwACAAD/sQNaAwsAGAAwAAi1LSEUCAItKwEUDwEXFhQGByMiJic1ND4BHwE3NjIfARYBFRQOAS8BBwYiLwEmND8BJyY0NjczMhYBpQW5UAoUD/oPFAEWHAtQuQYOBkAFAbQUIAlQuQYOBkAFBbpRChQP+g8WAQUIBblRCh4UARYO+g8UAgxQuQYGPwYB2/oPFAIMULkGBj8GDga5UQoeFAEWAAAAAAIAAP+5A1IDAwAXADAACLUsHxMIAi0rARUUBiYvAQcGIi8BJjQ/AScmNDY7ATIWARQPARcWFAYrASImNzU0NhYfATc2Mh8BFgGtFhwLUbkFEAU/Bga5UAsWDvoOFgGlBrlQCxYO+g4WARQeClG5Bg4GPwYBOvoOFgIJUboFBUAFEAW5UAscFhYBaQcGuVALHBYWDvoOFgIJUboFBUAFAAABAAD/agPoA1IARAAGszMRAS0rARQPAQYiJj0BIxUzMhYUDwEGIi8BJjQ2OwE1IxUUBiIvASY0PwE2MhYdATM1IyImND8BNjIfARYUBisBFTM1NDYyHwEWA+gLjgseFNdIDhYLjwoeCo8LFg5I1xQeC44LC44LHhTXSA4WC48LHAuPCxYOSNcUHguOCwFeDguPCxYOSNcUHguOCwuOCx4U10gOFguPCxwLjwsWDkjXFB4LjgsLjgseFNdIDhYLjwoAAAAAAQAAAAAD6AIRACAABrMUBAEtKwEUDwEGIiY9ASEVFAYiLwEmND8BNjIWHQEhNTQ2Mh8BFgPoC44LHhT9xBQeCo8LC48KHhQCPBQeC44LAV4OC48LFg5ISA4WC48LHAuPCxYOSEgOFguPCgAAAQAA/2oBigNSACAABrMcDAEtKwEUBicjETMyHgEPAQYiLwEmNDY7AREjIiY2PwE2Mh8BFgGJFg5HRw8UAgyPCh4KjwoUD0hIDhYCCY8LHAuPCwKfDhYB/cQUHguOCwuOCx4UAjwUHguOCwuOCwAAAAP///9qA6EDDQAjACwARQAKtz0vKicaCAMtKwEVFAYnIxUUBicjIiY3NSMiJic1NDY7ATU0NjsBMhYXFTMyFhc0LgEGHgE+AQEUBiIvAQYjIi4CPgQeAhcUBxcWAjsKB30MBiQHDAF9BwoBDAZ9CggkBwoBfQcKSJTMlgSO1IwBIio8FL9ke1CSaEACPGyOpIxwOANFvxUBlCQHDAF9BwwBCgh9CggkBwp9CAoKCH0KGWeSApbKmAaM/podKhW/RT5qkKKObjoEQmaWTXtkvxUAAAAAA////7ADWQMQAAkAEgAjAAq3IBcMCgQCAy0rATQnARYzMj4CBQEmIyIOAQcUJRQOAi4DPgQeAgLcMP5bTFo+cFAy/dIBpUtcU4xQAQLcRHKgrKJwRgJCdJ6wnHZAAWBaSv5cMjJQcmkBpTJQkFBbW1igckYCQnactJp4PgZKbKYAAAAAA////2oDoQMNAA8AGAAxAAq3KRsWEwsDAy0rARUUBichIiYnNTQ2MyEyFhc0LgEGHgE+AQEUBiIvAQYjIi4CPgQeAhcUBxcWAjsKB/6+BwoBDAYBQgcKSJTMlgSO1IwBIio8FL9ke1CSaEACPGyOpIxwOANFvxUBlCQHDAEKCCQHCgoZZ5IClsqYBoz+mh0qFb9FPmqQoo5uOgRCZpZNe2S/FQADAAD/sAI+AwwAEAAnAFsACrdYPiAVDAIDLSsBFAYiJjc0JiMiJjQ2MzIeARc0LgIiDgIHFB8CFhczNjc+ATc2NxQHDgIHFhUUBxYVFAcWFRQGIw4CJiciJjc0NyY1NDcmNTQ3LgInJjU0PgMeAgGbDAwOAjwdCAoKCBw2LFgmPkxMTD4mASYREUgHfwhHBhYGJkc5GSIgAxoODhkIJBkLLjIwCRokAQcZDg4aAiIgGToyUGhoaE42AhEICgoIGRwKEAoSKh0oRC4YGC5EKDksEhNVUVFVBhoFLDlXPxssPh0PHxQPDxUdEA0NGhwZHAIgFxwaDQ0QHRUPDxQfDxxAKhw/VzdgPiQCKDpkAAAAAAP//f+xA18DCwAUACEALgAKtyslHxgQAwMtKwEVFAYrASImPQE0NjsBNTQ2OwEyFhc0LgEOAx4CPgE3FA4BIi4CPgEyHgEB9AoIsggKCgh9CgckCAroUoqmjFACVIiqhlZ7csboyG4Gerz0un4CIvoHCgoHJAgKxAgKCsxTilQCUI6ijlACVIpTdcR0dMTqxHR0xAAEAAD/0QOhAusAEwAvAEwAbQANQApoUUc0KhgRAwQtKwERFAYmLwEjIiYnNTQ2NzM3NjIWExQGBwYjIiY3ND4DLgIvASY3NDYXMhceARcUBgcGIyImNzQ3Njc+ATQmJyYnJjU0NjMyFx4BFxQGBwYjIiY3ND8BNjc+AS4BJyYnLgEnJjU0NjMyFx4BAa0WHAu6kg8UARYOkroKHhTXMCcFCQ4WAQwWEBAECBgHEQoEFA8JBScwj2BNCAYPFgEVIAspLi4pCyAVFA8HCE5ekI52BwcPFgEWGRkURU4CSkcUGQQSAxYUEAcHdo4Cjv2gDhYCCboWDtYPFAG6ChT+wSpKDwMUEAwQDAwcJBwMBg4IDA8WAQMPSipVkiADFg4WCxAJHlpoWh4JEAsWDhYDIZBWgNgyAxYOFA0MDg4zmKqYMw4OAwYDDRQOFgMz1gAAAAACAAAAAAKDArEAEwAvAAi1KhgRAwItKwERFAYmLwEjIiYnNTQ2NzM3NjIWExQGBwYjIiY3ND4DLgIvASY3NDYXMhceAQGtFhwLupIPFAEWDpK6Ch4U1zAnBQkOFgEMFhAQBAgYBxEKBBQPCQUnMAKO/aAOFgIJuhYO1g8UAboKFP7BKkoPAxQQDBAMDBwkHAwGDggMDxYBAw9KAAABAAAAAAGtArEAEwAGsxEDAS0rAREUBiYvASMiJic1NDY3Mzc2MhYBrRYcC7qSDxQBFg6SugoeFAKO/aAOFgIJuhYO1g8UAboKFAAAAwAA/7EDCgNTAAsAQwBLAAq3SEU+KQcBAy0rEwcmPQE0PgEWHQEUAQcVFAYHIicHFjMyNjc1ND4BFhcVFAYHFTMyFg4BIyEiJj4BOwE1JicHBiIvASY0NwE2Mh8BFhQnARE0NhcyFpc4GBYcFgJ2ymhKHx42NzxnkgEUIBIBpHmODxYCEhH+mw4WAhIQj0Y9jgUQBC4GBgKwBg4GLgXZ/qVqSTlcAUM5Oj5HDxQCGA1HHgEvykdKaAELNhySaEcPFAIYDUd8tg1KFhwWFhwWSgcmjgYGLgUQBAKxBgYuBRBF/qYBHUpqAUIAAAAC////sQKDA1MAJwAzAAi1MSwaCgItKwEVFAYHFTMyHgEGIyEiLgE2OwE1LgE3NTQ+ARYHFRQWMjYnNTQ+ARYnERQOASYnETQ2HgECg6R6jw8UAhgN/psPFAIYDY95pgEWHBYBlMyWAhYcFo9olmYBaJRqAclHfLYNShYcFhYcFkoNtnxHDxQCGA1HaJKSaEcPFAIYyf7jSmgCbEgBHUpqAmYAAAIAAP/5A1kCxAAYAEAACLU8HBQEAi0rARQHAQYiJj0BIyImJzU0NjczNTQ2FhcBFjcRFAYrASImNycmPwE+ARczMjY3ETQmJyMiNCY2LwEmPwE+ARczMhYClQv+0QseFPoPFAEWDvoUHgsBLwvEXkOyBwwBAQEBAgEICLIlNAE2JLQGCgICAQEBAgEICLJDXgFeDgv+0AoUD6EWDtYPFAGhDhYCCf7QCrX+eENeCggLCQYNBwgBNiQBiCU0AQQCCAQLCQYNBwgBXgAAAAIAAP/5A2sCwwAnAEAACLU8LA4HAi0rJRQWDwEOAQcjIiY1ETQ2OwEyFhUXFg8BDgEnIyIGBxEUFhczMh4CARQHAQYiJj0BIyImPQE0NjczNTQ2FhcBFgFlAgECAQgIskNeXkOyCAoBAQECAQgIsiU0ATYktAYCBgICBgv+0QscFvoOFhYO+hYcCwEvCy4CEgUOCQQBXkMBiENeCggLCQYNBwgBNiT+eCU0AQQCCAEsDgv+0AoUD6EWDtYPFAGhDhYCCf7QCgAABAAA/2oDoQNTAAMAEwAjAEcADUAKNCcfFw8HAgAELSsXIREhNzU0JisBIgYdARQWOwEyNiU1NCYrASIGHQEUFjsBMjY3ERQGIyEiJjURNDY7ATU0NhczMhYdATM1NDYXMzIWFxUzMhZHAxL87tcKCCQICgoIJAgKAawKCCMICgoIIwgK1ywc/O4dKiodSDQlJCU01jYkIyU0AUcdKk8CPGuhCAoKCKEICgoIoQgKCgihCAoKLP01HSoqHQLLHSo2JDYBNCU2NiQ2ATQlNioAAA8AAP9qA6EDUwADAAcACwAPABMAFwAbAB8AIwAzADcAOwA/AE8AcwAjQCBgU0tEPjw6ODY0LygiIB4cGhgWFBIQDgwKCAYEAgAPLSsXMzUjFzM1IyczNSMXMzUjJzM1IwEzNSMnMzUjATM1IyczNSMDNTQmJyMiBgcVFBY3MzI2ATM1IyczNSMXMzUjNzU0JicjIgYdARQWNzMyNjcRFAYjISImNRE0NjsBNTQ2FzMyFh0BMzU0NhczMhYXFTMyFkehocWyssWhocWyssWhoQGbs7PWsrIBrKGh1rOzxAwGJAcKAQwGJAcKAZuhodazs9ahoRIKCCMICgoIIwgK1ywc/O4dKiodSDQlJCU01jYkIyU0AUcdKk+hoaEksrKyJKH9xKH6of3EoSSyATChBwoBDAahBwwBCv4msiShoaFroQcKAQwGoQcMAQos/TUdKiodAssdKjYkNgE0JTY2JDYBNCU2KgAAAAMAAP92A6ADCwAIABQALgAKtx8ZEgsGAgMtKzc0Jg4BHgEyNiUBBiIvASY0NwEeASUUBw4BJyImNDY3MhYXFhQPARUXNj8BNjIW1hQeFgISIhABav6DFToWOxUVAXwWVAGYDBuCT2iSkmggRhkJCaNsAipLIQ8KHQ4WAhIgFBT6/oMUFD0UOxYBfDdU3RYlS14BktCQAhQQBhIHXn08AhktFAoACQAA/7EDWQLEAAMAEwAXABsAHwAvAD8AQwBHABdAFEVEQUA+Ny4mHRwZGBUUCgQBAAktKzcVIzUlMhYdARQGKwEiJj0BNDY/ARUhNRMVIzUBFSE1AzIWBxUUBicjIiY3NTQ2FwEyFgcVFAYHIyImJzU0NhcFFSM1ExUhNcTEAYkOFhYOjw4WFg7o/h59fQNZ/mV9DxYBFBCODxYBFBAB9A4WARQPjw8UARYOAUF9ff4eQEdHSBYOjw4WFg6PDxQB1kdHAR5ISP3ER0cCgxQQjg8WARQQjg8WAf7iFA+PDxQBFg6PDhYBR0dHAR5ISAAABgAA/3IELwNJAAgAEgAbAHsAtwDzABFADuDCpYZjMxkVEAsGAgYtKwE0JiIGFBYyNgU0Jg4BFxQWMjYDNCYiBh4BMjYHFRQGDwEGBxYXFhQHDgEjIi8BBgcGBwYrASImNScmJwcGIicmNTQ3PgE3Ji8BLgE9ATQ2PwE2NyYnJjQ3PgEzMh8BNjc2NzY7ATIWHwEWFzc2MhcWFRQPAQYHFh8BHgEBFRQHBgcWFRQHBiMiLwEGIicOAQciJyY1NDcmJyY9ATQ3NjcmNTQ/ATYzMhYXNxc2PwEyFxYVFAcWFxYRFRQHBgcWFRQHBiMiJicGIicOASInJjU0NyYnJj0BNDc2NyY1ND8BNjMyFhc2Mhc2PwEyFxYVFAcWFxYB9FR2VFR2VAGtLDgsASo6LAEsOCwBKjos2AgFVgYMEx8EBA1CCwYFQBUWBgcEDWgGCg0TF0IEDQZQBAUkCA0HVQUICAVWBwsTHwQEDEQKBgZAExgGBwMNaAYKAQ0TFkIFDQVRBBgRCA0GVQUIAWVTBgocAkQBBRUdCwwLBywDAUQDHQoHU1MHCh0DNBABBCoIEREcFwQCQwIcCQdTUwYKHAJEAQUqCAsMCwcsBEQDHQoHU1MHCh0DNBABBCoIDAoMHBcEAkMCHAkHUwFeO1RUdlRU4x0sAigfHSoqAlkdKio7KirNZwYKAQ4TFxslBgwEEUIEMgsGPBsNCAZVBgwyBARLDwUFCCwMGBYNAQgHZwYKAQ4TFxslBgwEEUIEMgoIPBoNCAZVBgsxBARLDwUFHhUNGxMMAgj+z04JCA8OPw4CAigbJQEBCzQBKAICDj8ODwgJTgkJEA0/DgICHgk0DAEBKBcBJwICDj8NEAkCM04JCQ8OPw4CAic0DAEBDDQnAgIOPw4PCQlOCQgQDT8OAgIeCTQMAgIoFwEnAgIOPw0QCAACAAD/sQNaAwoACABoAAi1USAGAgItKwE0JiIOARYyNiUVFAYPAQYHFhcWFAcOASciLwEGBwYHBisBIiY1JyYnBwYiJyYnJjQ3PgE3Ji8BLgEnNTQ2PwE2NyYnJjQ3PgEzMh8BNjc2NzY7ATIWHwEWFzc2MhcWFxYUDwEWHwEeAQI7UnhSAlZ0VgEcCAdoCgsTKAYFD1ANBwdNGRoJBwQQfAgMEBsXTwYQBkYWBAUIKAoPCGYHCAEKBWgIDhclBgUPUA0HCE0YGgkIAxF8BwwBDxwWUAUPB0gUBAQ7DglmBwoBXjtUVHZUVHh8BwwBEB4VGzIGDgYVUAEFPA0ITBwQCgdnCQw8BQZAHgUOBgwyDxwbDwEMB3wHDAEQGRogLQcMBxRQBTwNCEwcDwgIZwkMPAUFQxwFDgZNHBsPAQwAAAH////5AxIDCwBQAAazIAYBLSslFAYHBgcGIyIuAS8BJicuAScmLwEuAS8BJjc0NzY3PgEzMhcWFx4CFx4CFRQOAgcUHwEeATUeARcyFh8BFjcyPgI3Mh4BHwEWFxYXFgMSDAYLOTQzEBwkCDs2K0iYLBsTCggIBAcDAR0fHA4wDwgEChQGFBQHAhAIICYeAQMEAQ4qbkwBEgULBgcKHh4gDAcQGAJBEwwnAwKeDzAOHCAcBAoDFRQbLJhIKzYcFxASIA4PNDQ4DAYMAgMoCigeDwIYEAgLIhoiCAUICwMWAU1uKgwCBQMBHigeAQgQAiULBhMKBAAACAAA/2oDWQNSABMAGgAjAFoAXwBuAHkAgAAVQBJ9e3ZvbmJdW043IRsVFA8HCC0rAR4BFREUBgchIiYnETQ2NyEyFhcHFTMmLwEmExEjIiYnNSERARYXNjMyFxYHFCMHBiMiJicGBwYjIi8BNCcmNz4BNzYXFhU2NzY3LgE3NjsBMhcWBwYHFQYHFgE2Nw4BEwYXNjc0NzY3Ij0BJzQnAzY3Ii8BJicGBwYFJiMWMzI3AzMQFh4X/RIXHgEgFgH0FjYPStIFB68GxugXHgH+UwGsEh0hIFIRCQgBAQMkG0wie2BVMggHDgMGAgU2LggFAR0fJhQNCAgGEQwNBwoFAQEBBx/+8R4vHSjXCQcBAwQBAgEBB0ZMUwEGCSscDyAQAWAOQCobCAICfhA0GP1+Fx4BIBYDfBceARYQJtIQB68H/LACPB4X6fymAUsOEQQbDRABAhUWEg0hkgQGAQIGDhc4GgUIAQEvP0xGLlYcFggMGgMBFkQnW/7xDUsWMgHxFzIEFAIWAwIBAQEMCP6NHg8FCCU9MD4fBw4QAQAAAAAEAAD/agNZA1IAEwAaACMAUQANQAonJCEbFRQPBwQtKwEeARURFAYHISImJxE0NjchMhYXBxUzJi8BJhMRIyImJzUhERMVMxMzEzY3NjUzFx4BFxMzEzM1IxUzBwYPASMnLgEnAyMDBwYPASMnJi8BMzUDMxAWHhf9EhceASAWAfQWNg9K0gUHrwbG6BceAf5TOydcWEgEAQEDAQECAkhZWyenMjcDAQEDAQECAlE/UQIBAQICAgEDNzICfhA0GP1+Fx4BIBYDfBceARYQJtIQB68H/LACPB4X6fymAfQ7/o8BDwsOCQUOARQE/vEBcTs79QsODAwCEgUBMP7QDQgEDAwOC/U7AAAEAAD/agNZA1IAEwAaACMAUQANQAo9JSEbFRQPBwQtKwEeARURFAYHISImJxE0NjchMhYXBxUzJi8BJhMRIyImJzUhETcVMzUjNz4CBzMUHgIfASMVMzUjJzczNSMVMwcOAQ8BIycmLwEzNSMVMxcHAzMQFh4X/RIXHgEgFgH0FjYPStIFB68GxugXHgH+U6idKjoDBAYBAQQCBAI8K6Mma2wmnCk5AggBAQEDAwY7KqImam0CfhA0GP1+Fx4BIBYDfBceARYQJtIQB68H/LACPB4X6fymgzs7WgQKBgECBgQEA1o7O5ieOztZBAoDAQUGB1k7O5ieAAYAAP9qA1kDUgATABoAIwAzAEMAUwARQA5KRDo0LiYhGxUUDwcGLSsBHgEVERQGByEiJicRNDY3ITIWFwcVMyYvASYTESMiJic1IRETNDYzITIWHQEUBiMhIiY1BTIWHQEUBiMhIiY9ATQ2MwUyFh0BFAYjISImPQE0NjMDMxAWHhf9EhceASAWAfQWNg9K0gUHrwbG6BceAf5TjwoIAYkICgoI/ncICgGbCAoKCP53CAoKCAGJCAoKCP53CAoKCAJ+EDQY/X4XHgEgFgN8Fx4BFhAm0hAHrwf8sAI8Hhfp/KYB4wcKCgckCAoKCFkKCCQICgoIJAgKjwoIJAgKCggkCAoABgAA/7EDEgMLAA8AHwAvADsAQwBnABFADl9MQDw2MSsjGxMLAwYtKwERFAYrASImNRE0NjsBMhYXERQGKwEiJjURNDY7ATIWFxEUBisBIiY1ETQ2OwEyFhMRIREUHgEzITI+AQEzJyYnIwYHBRUUBisBERQGIyEiJicRIyImPQE0NjsBNz4BNzMyFh8BMzIWAR4KCCQICgoIJAgKjwoIJAgKCggkCAqOCgckCAoKCCQHCkj+DAgIAgHQAggI/on6GwQFsQYEAesKCDY0Jf4wJTQBNQgKCgisJwksFrIWLAgnrQgKAbf+vwgKCggBQQgKCgj+vwgKCggBQQgKCgj+vwgKCggBQQgKCv5kAhH97wwUCgoUAmVBBQEBBVMkCAr97y5EQi4CEwoIJAgKXRUcAR4UXQoAAAAAAgAA/2oD6ALDABcAPQAItToiCwACLSsBIg4BBxQWHwEHBgc2PwEXFjMyPgIuAQEUDgEjIicGBwYHIyImJzUmNiI/ATY/AT4CPwEuASc0PgEgHgEB9HLGdAFQSTAPDRpVRRgfJyJyxnQCeMIBgIbmiCcqbpMbJAMIDgICBAIDDAQNFAcUEAcPWGQBhuYBEOaGAnxOhEw+cikcNjItIzwVAwVOhJiETv7iYaRgBGEmBwUMCQECCgUPBQ4WCBwcEyoyklRhpGBgpAABAAD/aQPoAsMAJgAGsyILAS0rARQOASMiJwYHBgcGJic1JjYmPwE2PwE+Aj8BLgEnND4CMzIeAQPohuaIJypukxskCg4DAgQCAwwEDRQHFBAHD1hkAVCEvGSI5oYBXmGkYARhJggEAQwKAQIIBAMPBQ4WCBwcEyoyklRJhGA4YKQAAAACAAD/sAPoAsMAJQBLAAi1STYiCgItKwEUDgEjIicGBwYHIyImNSY0NjU/AjYHNz4CNy4BJzQ+ATIeARcUBgceAR8BFh8DFAcOAScmJyYnBiMiJxYzMjY3PgEnNCceAQMSarRrMDJGVRUbAgYMAQIBBAMDARwFDg4ERU4BarTWtGrWUEQFDAgbCQQFBAMBAgoIGxVVRjIwl3AgEVqkQkVMAQ1IVAGlTYRMCTEXBQQKBwEEBAEDBgMDAR4FGBIQKHRDToRMTITcQ3YnDhYKIQsDBQYKAQIICgEEBRcxCUoDMi80hkorKid4AAAAAAMAAP+wA+gCwwAVADsAYAAKt1xJIxYJAAMtKwEiDgEHFBYfAQc2PwEXFjMyPgE0LgEnMh4CDgEnIicGBwYHIyImNSY0NjU/AjYHNz4CNy4BJzQ+AQEeAR8BFh8DFAcOAScmJyYnBiMiJxYzMjY3PgEnNCceARQGAYlVllYBPDU2ExMPGR4rKlWUWFiUVWq2aAJssmwwMkZVFRsCBgwBAgEEAwMBHAUODgRFTgFqtAI2BQwIGwkEBQQDAQIKCBsVVUYyMJdwIBFapEJFTAENSFRQAnw6ZDktVh4gLgsKEgYIOmRwZjhITIScgk4BCTEXBQQKBwEEBAEDBgMDAR4FGBIQKHRDToRM/XQOFgohCwMFBgoBAggKAQQFFzEJSgMyLzSGSisqJ3iHdgAAAAMAAP9qA8QDUwAMABoAQgAKtzYhFA0KBgMtKwU0IyImNzQiFRQWNzIlISYRNC4CIg4CFRAFFAYrARQGIiY1IyImNT4ENzQ2NyY1ND4BFhUUBx4BFxQeAwH9CSEwARI6KAn+jALWlRo0UmxSNBoCpiod+lR2VPodKhwuMCQSAoRpBSAsIAVqggEWIDQqYAgwIQkJKToBqagBKRw8OCIiODwc/teoHSo7VFQ7Kh0YMlRehk9UkhAKCxceAiIVCwoQklROiFxWMAAAAgAA/2oDxANTAAwANAAItSgTCgYCLSsFNCMiJjc0IhUUFjcyJRQGKwEUBiImNSMiJjU+BDc0NjcmNTQ+ARYVFAceARcUHgMB/QkhMAESOigJAccqHfpUdlT6HSocLjAkEgKEaQUgLCAFaoIBFiA0KmAIMCEJCSk6AakdKjtUVDsqHRgyVF6GT1SSEAoLFx4CIhULChCSVE6IXFYwAAAAAAIAAP/5ATADCwAPAB8ACLUbEwsEAi0rJRUUBgcjIiY9ATQ2FzMyFhMDDgEnIyImJwMmNjsBMhYBHhYOjw4WFg6PDxQRDwEWDo8OFgEPARQPsw4Wmn0PFAEWDn0OFgEUAj7+Uw4WARQPAa0OFhYAAAAE////sQOhAwsAAwAMABUAPQANQAowHhMQCwQCAAQtKxchNSE1ITUjIiY9ASEBNC4BDgEWPgE3FRQGByMVFAYjISImJzUjIiY3NTQ2FzMRNDYzITIWHwEeAQcVMzIW1gH0/gwB9FkWIP6bAoMUIBICFhwYRgwGfSAW/egWHgF9BwwBQCskIBUBdxc2D1UPGAEjLT4Hj9bWIBZZ/ncPFAIYGhgEEBHoBwoBWRYgIBZZDAboLEABATAWIBgOVRA2Fo8+AAAABQAA//kD5AMLAAYADwA5AD4ASAAPQAxDQDw6HBMMCAIABS0rJTcnBxUzFQEmDwEGFj8BNhMVFAYjISImNRE0NjchMhceAQ8BBicmIyEiBgcRFBYXITI2PQE0PwE2FgMXASM1AQcnNzYyHwEWFAHwQFVANQEVCQnECRIJxAkkXkP+MENeXkMB0CMeCQMHGwgKDQz+MCU0ATYkAdAlNAUkCBg3of6JoQJvM6EzECwQVRC9QVVBHzYBkgkJxAkSCcQJ/r5qQ15eQwHQQl4BDgQTBhwIBAM0Jf4wJTQBNiRGBwUkCAgBj6D+iaABLjShMxAQVBAsAAEAAP+xA+gDLwAsAAazKBgBLSsBFAcBBiImNzUjIg4FFRQXFBYHFAYiJy4CJyY1NDc2ITM1NDYWFwEWA+gL/uMLHBgCfTdWVj44IhQDBAEKEQYECAYDRx5aAY59FCAJAR0LAe0PCv7iCxYOjwYSHjBAWjgfJgQSBggMCgUOFAOfXW9L4Y8OFgIJ/uILAAAAAAEAAP+xA+gDLgArAAazIwcBLSslFA8CBgcGIiYnNDY3NjU0LgUrARUUBiInASY0NwE2MhYHFTMgFxYD6EcGBwMFBhIIAQIBAxQiOD5WVjd9FCAJ/uMLCwEdCxwYAn0Bjloe4V2fDREIBAoMCAUUAyYfOFpAMB4SBo8OFgsBHgoeCgEeChQPj+FLAAAAAgAA/7ED6AM1ABQAOgAItTMcDQQCLSslFRQHBiMiJwEmNDcBNhYdAQcGFBcFFA4CDwEGIyInJjc2Jy4BJxUUBwYjIicBJjQ3ATYXFh0BFhcWAWUWBwcPCv7jCwsBHREs3QsLA2ASGhwIDAQLAwIOARhTJHZbFQgGDwr+4gsLAR4QFxXmaV72JxcKAwsBHgoeCgEeERMXJ94LHAvzIFRGRhAWCgEED99cKCwHjBcKAwsBHgoeCgEeEQoJF5MPbGEAAwAA//kD6AJ9ABEAIgAzAAq3MCcbFA8CAy0rASYnFhUUBiImNTQ3BgceASA2ATQmByIGFRQeATY1NDYzMjYFFAcGBCAkJyY0NzYsAQQXFgOhVYAiktCSIoBVS+ABBOD+uRALRmQQFhBEMAsQAdkLTv74/tr++E4LC04BCAEmAQhOCwE6hEE6Q2iSkmhDOkGEcoiIAUkLEAFkRQwOAhIKMEQQzBMTgZqagRMmFICaAp5+FAAAAgAA/70DTQMLAAgAHQAItRcNBwICLSsTNCYOAR4CNgEUBwEGIicBLgE9ATQ2NzMyFhcBFvoqOiwCKD4mAlUU/u4WOxT+cRUeKh3pHUgVAY8UAlgeKgImQCQGMP7ZHhX+7hUVAY8VSB3oHSoBHhX+cRUAAAADAAD/vQQkAwsACAAdADQACrctIhcNBwIDLSsTNCYOAR4CNgEUBwEGIicBLgE9ATQ2NzMyFhcBFhcUBwEGIyImJwE2NCcBLgEjMzIWFwEW+io6LAIoPiYCVRT+7hY7FP5xFR4qHekdSBUBjxTXFf7uFh0UGhABBhUV/nEVSB19HUgVAY8VAlgeKgImQCQGMP7ZHhX+7hUVAY8VSB3oHSoBHhX+cRUdHhX+7hUQEQEGFTsVAY8VHh4V/nEVAAEAAP/5AoMDUwAjAAazEwcBLSsBMhYXERQGByEiJicRNDYXMzU0Nh4BBxQGByMiJjU0JiIGFxUCTRceASAW/ekXHgEgFhGUzJYCFA8kDhZUdlQBAaUeF/6+Fh4BIBUBQhYgAbNnlAKQaQ8UARYOO1RUO7MAAQAA//kDoQMMACUABrMkFwEtKwEVFAYHIyImPQE0Jg4BBxUzMhYXERQGByEiJicRNDYXITU0PgEWA6EWDiQOFlJ4UgE1Fx4BIBb96RceASAWAXeS0JACEY8PFAEWDo87VAJQPWweF/6+Fh4BIBUBQhYgAWxnkgKWAAAAAAIAAP/5AoMDCwAHAB8ACLUYDAQAAi0rEyE1NCYOARcFERQGByEiJicRNDYXMzU0NjIWBxUzMhazAR1UdlQBAdAgFv3pFx4BIBYRlMyWAhIXHgGlbDtUAlA9of6+Fh4BIBUBQhYgAWxmlJRmbB4AAAACAAD/+AOTAsUAEAAyAAi1IxoOAwItKwERFAYnIzUjFSMiJicRCQEWNwcGByMiJwkBBiMmLwEmNjcBNjIfATU0NjsBMhYdARceAQMSFg7Wj9YPFAEBQQFBAXwiBQcCBwX+fv5+BwYHBSMEAgUBkRIwE4gKCGsICnoFAgEo/vUPFgHW1hQQAQ8BCP74ASQpBQEDAUL+vgQCBSkFEAQBTg8Pcm0ICgoI42YFDgAAAgAA//kBZgMLAB4ALgAItSojFgQCLSslFRQGByEiJic1NDY3MzUjIiYnNTQ2NzMyFhcRMzIWAxUUBgcjIiY9ATQ2OwEyFgFlFBD+4w8UARYOIyMPFAEWDtYPFAEjDxZIFg6PDhYWDo8PFGRHDxQBFg5HDxQB1hYORw8UARYO/r8WAnVrDxQBFg5rDhYWAAAAAgAA//gCOQLDAA8AOgAItTUcCwMCLSslFRQGJyMiJj0BNDYXMzIWExQOAwcOARUUBgcjIiY9ATQ2Nz4BNCYiBwYHBiMiLwEuATc2MzIeAgGJDgiGCQ4OCYYIDrAQGCYaFRceDgmGCAxKKiEcNEYYFCgHCgcHWwgCBFmqLVpILpWGCQ4BDAqGCQ4BDAFFHjQiIBIKDTANChABFAsaLlITDyIwJBAOMgkERgYQCJQiOlYAAAAAAv///2oDoQMNAAgAIQAItRkLBgMCLSsBNC4BBh4BPgEBFAYiLwEGIyIuAj4EHgIXFAcXFgKDlMyWBI7UjAEiLDoUv2R7UJJoQAI8bI6kjHA4A0W/FQGCZ5IClsqYBoz+mh0qFb9FPmqQoo5uOgRCZpZNe2S/FQAAAwAA/9IEHgLqAAgAMABLAAq3QTchCgQAAy0rPQEzMjcWFwYjAzUzMh4CFRQWOwE1NDYfARYVFAYPAgYmJzUHBjUjIi4CNTQmIyU2OwE1NDYfARYVFAYPAgYmJzUjIhUjIgcm5RwYH0NHT+XlQXRWMlI8XBQM6ggEAgLqDRIBBANVQHZUMlQ7ATVEUlwUDOoIBAIC6g0SAQQDVRoZICKtClQ9JgHKrTJUdkA6VDQQDAmQBQkDCAECjwkMDzYBAQEyVHY/O1SIJTYPDAmQBggEBgICkAgMDzUBClUAAAEAAP+sA6wC4AAXAAazBAABLSsBMhYQBiMiJzcWMzI2ECYiBgczByczPgECFKru7qqObkZUYn60tPq0Ao64uHwC8ALg8P6s8FhKPLQBALSufMzMpuoAAAACAAD/sQR3AwsABQAfAAi1GxEDAQItKwUVIREzEQEVFAYvAQEGIi8BBycBNjIfAQEnJjY7ATIWBHf7iUcD6BQKRP6fBg4GguhrAUYGDgaCAQNDCQgN8wcKB0gDWvzuArjyDAoJRP6fBgaC6WwBRgYGggEDQwkWCgADAAD/agRvA1MACwAXAD8ACrc0HRcTCAADLSsBFhcUBisBFAYiJicXMjQHIiY1NCIVFBYBFhQHAQYmLwEmND8BJjU+BDc0NjcmNTQ+ARYXFAceARc3NhYXA2UihSwc+lR2UgGOCQkgMBI6AlgEBvvrBRAELwQGaAscLjAkFAGCagQeLh4BBEVqHeoFEAQBd8dwHSo7VFQ6YRIBMCEJCSk6A30FEAT8dwUCBTUGEARZEhMYMlRehk9UkhAKCxceAiIVCwoKSDTKBQIFAAAEAAD/agRvA1MADAAXACcATwANQApFLiYeEQ0KBgQtKwU0IyImNTQiFRQWNzIJAS4BJyIOAgcUBRQGKwEUBiImJzchJic3FhMXFhQHAQYmLwEmND8BJjU+BDc0NjcmNTQ+ARYXFAceARc3NhYCRAkgMBI6KAn+1QHpF2ZKM1YyGgECpywc+lR2UgFTAaZcIz4itS8EBvvrBRAELwQGaAscLjAkFAGCagQeLh4BBEVqHeoFEGAIMCEJCSk6AQESAagxQAEiODwc1/odKjtUVDpIaZc3xwKZNgUQBPx3BQIFNQYQBFkSExgyVF6GT1SSEAoLFx4CIhULCgpINMoFAgAAAQAA/2oD6ANSAB0ABrMUCgEtKwEWFA8BFwcOAScHIzU3JjY/ARc3NjIeAQ8BFzc2MgPTFRXfU1lb/GjKZcpFGltZVN8VPCgCFt+D3xY6AlUUPBXfVFlbGkXKZcpn/lpZU98VKjoW4ILfFQAABQAA/8MD6AKxAAkAGgA+AEQAVwAPQAxTS0NCNiITDAYABS0rJTcuATc0NwYHFgE0JgciBhUUHgE2NTQ2MzI2NxQVBgIPAQYjIicmNTQ3LgEnJjQ3PgEzMhc3NjMyFh8BFgcWExQGBxMWFxQHBgcOASM3PgE3Jic3HgEXFgE2KzA4ASKAVV4BahALRmQQFhBEMAsQyjvqOxwFCgdECRlQhjILC1b8lzIyHwUKAw4LJAsBCRVYSZ0E+gsWJ1TcfCl3yEVBXSM1YiALaU8jaj1DOkGEkAFnCxABZEUMDgISCjBEEHUEAWn+WmkyCScGCgcqJHhNESoSg5gKNgkGBhQGAQX+/U6AHAEZGl0TEyQtYGpKCoRpZEA/JGQ0EwAC//7/xAM2AvgADgAdAAi1Fg8JAgItKz8BESU3JhI3NjcXBgcOAQEFBxYCBwYHJzY3PgEnB7p0/uxYdAR2ZIwEZEhYBAGiARRYdAR2YJACYkhYBFZyjHT+3BBWegFQeGQQZhBIWPoB+hBWev6weGIUaBBIWPpcdAABAAD/xAOsAvgAFwAGsxIAAS0rATIWFzMHJzMuASIGFBYzMjcXBiMiJhA2AZio7gR6uLiQBLT6tLR+aE5Gbo6o8PAC+Oimzs58rLT+tDxMWPABVPAAAAAABP////kELwLDAA8AHwAqADIADUAKLSslIBwTBgAELSs3IiY1ETQ2MyEyFhcRFAYjAREUFjchMjY1ETQmJyEiBgEzFRQGByEiJjc1BTI0KwEiFDPoJTQ0JQJfJTQBNiT9jwwGAl8ICgoI/aEHCgL/WTQl/IMkNgECRAkJWQkJiDQlAYklNDQl/nclNAHi/ncHDAEKCAGJBwoBDP30NhYeASAVNjYSEgAAAwAA/7EDWgNSAAgAPgBuAAq3ZEstEwYDAy0rNzQuAQYUFj4BATQmJyM0Nic0JicOAgcGDwEOAg8BDgEnIxEzMh4EFxY7ATI1NCc+ATQnNjU0Jic+ATcUBxYVFAcWFRQHFAYrASImJyYrASImNRE0NjsBNjc2Nz4CNzYzMh4BFRQHMzIWjxYcFhYcFgKDLBzENgEiNw4OFBcNHg0LDhgKFgwUChISBxYOHAwcAnZJQ2sCEBQKHQoJEhhHGwUVASFgTkg2aEVBDKEdKiodmRQ5IBwNDBYYFhwvSigbYjpWZA8UAhgaGAIUAVAdKgEgciA3NAEPQkoYDSYRDhAgCRMKDAH+mwIGBggGAildDxAJKigSHCcNJAgBMhUyKRIUKyYMDDgrTloaFxcqHQFlHioNSSoeDkJMFhUkTkEzOFQAAAADAAD/agNZAwsACAA/AHEACrdjSTUZBgMDLSsTNC4BBhQWPgEBNCYjPgEnNCc2NCYnNjU0JisBIg8BBg8CBicjETMyHgUXFhceAhcyNic0JiczMjY1MxQGJyMWFRQOASMiJy4DJyYnJicjIiY1ETQ2OwEyNz4BNzMyFh0BFhUUBxYVFAcWjxYcFhYcFgKDGBIIDAEdChQQAjYxR0l2EA0HKRIKCBISCRYWFhYQFAMeDRcUDg42JAE0AcQcLEdUO2IbJ0wuHBYTFgYOChshORSZHSoqHaEMQUhqOj9OYCEBFQUbAlgPFAIYGhgCFP7OEzQKIg0nHBIoKgkQDy8uKQYFAgwEAgH+mgoUEiAQHgEmDRhKQg82NiByICwbOVYBNzRCTSQVEjYwLg0cK0kNKh4BZR0qFhkYAVpLAys4DQsmKxQSKQAIAAD/jgPEA1IACAARABoAIwAsADUAPgBIABVAEkZBPDgzMComIR0YFQ8LBgIILSslFAYiJjQ2MhYFFAYiLgE2HgEBFA4BLgE2HgEBFAYiJjQ2HgEBFAYiJjQ2MhYBFA4BJj4BHgEBFAYiJjQ2MhYFFAYuATc0NjIWASYqOyoqOiwBFCg+JgQuNjD+dCo8KAIsOC4CnCo7Kio8KP3nNEo0NEo0Ao0qOiwCKD4m/p0+Wj4+Wj4BKEpnSgFIaEpIHSoqOyoqkR0qKjosAigBah4oAiw4LgYi/sgdKio6LAIoAg0lNDRKNDT+xR4oAiw4LgYiAWctPj5aPj6gNEoBSDUzSkoAAAEAAP+0Aw8DCAA2AAazCQIBLSslFAYjIicBJjQ2MhcBFhQGIicBJiIGFhcBFjMyNjc0JwEmIyIGFB8BFhQGIi8BJjU0NjMyFwEWAw9YQUs4/k4/fLBAAVIFIhAG/q4sdFIBKgGxIy4kLgEk/rwOExAWDuUGJA8F5SNALTEiAUU3TUFYNwGyQK98P/6uBRAiBQFTK1R1K/5PIy4kLiMBRA4WIg/kBhAiBeUiMS5AJP68NgAAAA8AAP/5BC8CfAALABcAIwAvADsARwBTAF8AawB3AIMAjwCfAKMAswAjQCCvp6GgnJKMhoB6dG5oYlxWUEpEPjgyLCYgGhQOCAIPLSs3FRQrASI9ATQ7ATI3FRQrASI9ATQ7ATInFRQrASI9ATQ7ATIBFRQjISI9ATQzITIlFRQrASI9ATQ7ATInFRQrASI9ATQ7ATIXFRQrASI9ATQ7ATInFRQrASI9ATQ7ATIXFRQrASI9ATQ7ATIXFRQrASI9ATQ7ATIBFRQrASI9ATQ7ATIXFRQrASI9ATQ7ATIXFRQrASI9ATQ7ATU0OwEyExEhEQERFAYjISImNRE0NjMhMhbWCTUJCTUJSAl9CQl9CUgJNQkJNQkCPAn+HgkJAeIJ/psJNgkJNglICTUJCTUJ1gg2CQk2CEcJNQkJNQnWCTUJCTUJ1wk2CQk2Cf7iCTYJCTYJjwk2CQk2CY8JfQkJPgk2CUf8XwPoKh38Xx0qKh0DoR4oxjUJCTUJhjUJCTUJhjYJCTYJ/tk1CQk1CYY1CQk1CYY2CQk2CZg1CQk1CYY2CQk2CZg1CQk1CZg1CQk1CQEVNgkJNgkJNgkJNgkJxAkJNQmGCf5TAfT+DAH0/gwdKiodAfQeKioAAAAAAwAA//kDWgLEAA8AHwAvAAq3KyQbEwwEAy0rJRUUBgchIiYnNTQ2NyEyFgMVFAYnISImJzU0NhchMhYDFRQGByEiJic1NDYXITIWA1kUEPzvDxQBFg4DEQ8WARQQ/O8PFAEWDgMRDxYBFBD87w8UARYOAxEPFmRHDxQBFg5HDxQBFgEQSA4WARQPSA4WARQBDkcPFAEWDkcPFgEUAAAAAAQAAAAABF8DCwAKACAAOgBSAA1ACks7MyEaCwYABC0rISImJzQ+ARYHFAY3Ii4BIgYPASImJzQ3PgIWFxYVFAY3IicuAQciDgMjIiY1NDc+AR4BFxYVFAY3IicuASQGBwYjIiYnNDc2JAwBFxYVFAYCOwtQAUYsSAFSjAEqSEhGFhYKVAEGLIKCgi0FVI4GBkyCVS9gRjggAglUBknS1tJKBlSOBgdk1v8A1GUHBglUAQZoASABLAEiZwVUUgsSGAIcEAtSlxwcHA4OVAoHBiswAjQpBgcKVJgFOjgBGCIkGFQKBwVKUgJOTAUHClSXBVhYAlxWBVQKBwZocgJuagYHClQAAv/+/7EDNgMLABIAMAAItSEVDwgCLSslBiMiLgE3NDcOAQcUHgI3MjY3DgEjIi4CNzQ+Ajc2FgcOAQcUHgE3Mjc2Fx4BAsAeH2asZgE6cI4BOl6GSFCQpTXUfFegcEgCQG6aVBkTEjAyAVKMUkI9FxEIBHsFZK5la1whvndJhF48AkRtcYhEdJ5XVZxyRgMBLhErdEBTilQBHQoRCBYAAAP//v+xA8QDUgALABAAFgAKtxMREAwKBAMtKwkBDgEHIi4CPgEzEyEUBgcTIREyHgEBrQEwO55XdcZwBHi+eWgBr0I9XP5TdcR0AWH+0D1CAXTE6sR0/lNYnjsBeAGtcsYAAAACAAD/sQR3AwsABQALAAi1CgcDAQItKwUVIREzEQETIRETAQR3+4lHA1qO/GD6AUEHSANa/O4CO/4MAUIBQf6/AAAAAAUAAP+xBHcDCwADAAcADQARABUAD0AMExIPDgsJBQQBAAUtKwERIxEBESMRARUhETMRAREjESURIxEBZY8BZY4CyvuJRwLLjwFljwFe/uIBHgEe/cQCPP19SANa/O4B9P5TAa3W/X0CgwAAAAIAAP+xA3MDCwAXAB4ACLUcGQ4DAi0rJRYGByEiJjcBNSMiJj4BMyEyHgEGKwEVDwEhAzUjFQNUHyY7/X07KCABGSQOFgISEAEeDxQCGA0kmpcBjaNHKjJGAUgxAbrfFhwWFhwW3yXwAQHz8wAAAAAGAAD/wAOhA1IAAwAUABwAJAAsADQAEUAONDAsKCQgHBgQCAIABi0rATcnByUUBwEGIi8BJjQ3ATYyHwEWJRcPAS8BPwEfAQ8BLwE/AQEXDwEvAT8BARcPAS8BPwECmKQ8pAE1Cv0zCh4KbwoKAs4KHgpuCv0PNjYRETc3EdRtbSIhbW0hAik3NxERNjYR/qw2NhERNjYRAg6jPKRoDwr9MgoKbwoeCgLOCgpvClsQETc3ERA3kSIhbW0hIm3+iBEQNzcQETcBLhARNzcREDcAAf/5/3sD+ANYACUABrMfAQEtKyUGJCcmAjc+ATc2FhceAQcGBwYCFxYkNz4BJyYkBzU2BBcWAgcGA1eX/mqUjw6BCBEKHEAZFggOBgppBmd6AThsUC0wQ/7kn7cBR040KVMQCY0OjJUBhJ4KEgYRBxcYPBwMCnb+3mxxHXZe73aWejIBO4qtf/78ahYAAAAAAQAAAAACCAKhABUABrMOBAEtKwEWFA8BJyY0NjIfARE0NjIWFRE3NjIB+Q8P9fUPHiwPeB4qIHgPKgFaDywP9fUPLB4PdwGLFR4eFf51dw8AAAAAAQAAAAAChgJiABQABrMPCgEtKwEyFhQGJyEXFhQGIi8BNzYyFhQPAQJTFR4eFf51dw8eLA/19Q8sHg93AZMgKiABdw8sHg/19Q8eLA92AAAB//8AAAKGAmIAFQAGswYBAS0rATYyHwEHBiImND8BISIuATY3IScmNAFIDyoQ9fUPKx4PeP51Fh4CIhQBi3gPAlMPD/X1Dx4sD3ceLB4Bdg8sAAABAAAAAAIIAqEAFAAGswoAAS0rARcWFAYiLwERFAYuATURBwYiJjQ3AQT1Dx4qD3ggKh54DyweDwKh9Q8sHg94/nUVIAIcFwGLeA8eLA8AAAEAAAABAAB+LVc9Xw889QALA+gAAAAA0dfGTwAAAADR15wf//n/aQS/A1gAAAAIAAIAAAAAAAAAAQAAA1L/agBaBQUAAP/pBL8AAQAAAAAAAAAAAAAAAAAAAHoD6AAAA+gAAAMRAAAELwAAA6AAAAMxAAADoAAAA6AAAAOgAAADoAAAA6AAAAPoAAAFBQAAA1kAAAPoAAAD6AAAA6AAAAOgAAAD6AAAA6AAAAPoAAADEQAAA1kAAAOgAAAD6AAAA+gAAANZAAAELwAAAfQAAAPoAAACOwAAAjsAAAFlAAABZQAAA+gAAALKAAAD6AAAAsoAAAOgAAADWQAAA1kAAAOgAAADWQAAA1kAAANZAAAD6AAAA+gAAAGsAAADoAAAA1kAAAOgAAACOwAAA1kAAAOgAAACggAAAawAAAMRAAACggAAA1kAAAOgAAADoAAAA6AAAAOgAAADWQAABC8AAANZAAADEQAAA1kAAANZAAADWQAAA1kAAAMRAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAABZQAAA6AAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA1kAAAQvAAACggAAA6AAAAKCAAADoAAAAWUAAAI7AAADoAAABB4AAAOsAAAEdgAABHYAAAR2AAAD6AAAA+gAAAM0AAADrAAABC8AAANZAAADWQAAA+gAAAMRAAAELwAAA1kAAAR2AAADWQAAA+gAAAR2AAAEdgAAA6AAAAOgAAAD6AAAAggAAAKGAAAChgAAAggAAAAAAAAA0gESAagBvgHcAfgCCAI2Ap4DBAOqBCAEhgUQBYIF/AZ2BswHOgegB8oIIAjUCTIKDgziDRINTA3ADeAOAA4eDj4Oag6WDsIO7g8mD14PlA/MEDAQghDSETQRahGiEgwSThKgEygTchQaFGgUjhUEFVYVvBYgFogXPBeOGAYZZhoGGoIbUhvWHFIc1B1wHdQeFh6MHyIfhh/WIA4gcCDqITIheCHYIjIibCLKIwQjQiN6I9AkGCRyJK4lHCVIJYQl6iZqJqAnLidqJ5Yn6iiKKSwprioIKvYrRivILBgsSixsLKIs2i1CLYottC3cLgYuLgAAAAEAAAB6AfgADwAAAAAAAgAAABAAcwAAADQLcAAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAFADUAAQAAAAAAAgAHADoAAQAAAAAAAwAFAEEAAQAAAAAABAAFAEYAAQAAAAAABQALAEsAAQAAAAAABgAFAFYAAQAAAAAACgArAFsAAQAAAAAACwATAIYAAwABBAkAAABqAJkAAwABBAkAAQAKAQMAAwABBAkAAgAOAQ0AAwABBAkAAwAKARsAAwABBAkABAAKASUAAwABBAkABQAWAS8AAwABBAkABgAKAUUAAwABBAkACgBWAU8AAwABBAkACwAmAaVDb3B5cmlnaHQgKEMpIDIwMTUgYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWlmb250UmVndWxhcmlmb250aWZvbnRWZXJzaW9uIDEuMGlmb250R2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADUAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGkAZgBvAG4AdABSAGUAZwB1AGwAYQByAGkAZgBvAG4AdABpAGYAbwBuAHQAVgBlAHIAcwBpAG8AbgAgADEALgAwAGkAZgBvAG4AdABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6AAABAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkBGgEbARwBHQEeAR8BIAEhASIBIwEkASUBJgEnASgBKQEqASsBLAEtAS4BLwEwATEBMgEzATQBNQE2ATcBOAE5AToBOwE8AT0BPgE/AUABQQFCAUMBRAFFAUYBRwFIAUkBSgFLAUwBTQFOAU8BUAFRAVIBUwFUAVUBVgFXAVgBWQFaAVsBXAFdAV4BXwFgAWEBYgFjAWQBZQFmAWcBaAFpAWoBawFsAW0BbgFvAXABcQFyAXMBdAF1AXYBdwF4AXkBeglkYXNoYm9hcmQEdXNlcgV1c2VycwJvawZjYW5jZWwEcGx1cwVtaW51cwxmb2xkZXItZW1wdHkIZG93bmxvYWQGdXBsb2FkA2dpdAVjdWJlcwhkYXRhYmFzZQVnYXVnZQdzaXRlbWFwDHNvcnQtbmFtZS11cA5zb3J0LW5hbWUtZG93bgltZWdhcGhvbmUDYnVnBXRhc2tzBmZpbHRlcgNvZmYEYm9vawVwYXN0ZQhzY2lzc29ycwVnbG9iZQVjbG91ZAVmbGFzaAhiYXJjaGFydAhkb3duLWRpcgZ1cC1kaXIIbGVmdC1kaXIJcmlnaHQtZGlyCWRvd24tb3BlbgpyaWdodC1vcGVuB3VwLW9wZW4JbGVmdC1vcGVuBnVwLWJpZwlyaWdodC1iaWcIbGVmdC1iaWcIZG93bi1iaWcPcmVzaXplLWZ1bGwtYWx0C3Jlc2l6ZS1mdWxsDHJlc2l6ZS1zbWFsbARtb3ZlEXJlc2l6ZS1ob3Jpem9udGFsD3Jlc2l6ZS12ZXJ0aWNhbAd6b29tLWluBWJsb2NrCHpvb20tb3V0CWxpZ2h0YnVsYgVjbG9jawl2b2x1bWUtdXALdm9sdW1lLWRvd24Kdm9sdW1lLW9mZgRtdXRlA21pYwdlbmR0aW1lCXN0YXJ0dGltZQ5jYWxlbmRhci1lbXB0eQhjYWxlbmRhcgZ3cmVuY2gHc2xpZGVycwhzZXJ2aWNlcwdzZXJ2aWNlBXBob25lCGZpbGUtcGRmCWZpbGUtd29yZApmaWxlLWV4Y2VsCGRvYy10ZXh0BXRyYXNoDWNvbW1lbnQtZW1wdHkHY29tbWVudARjaGF0CmNoYXQtZW1wdHkEYmVsbAhiZWxsLWFsdA1hdHRlbnRpb24tYWx0BXByaW50BGVkaXQHZm9yd2FyZAVyZXBseQlyZXBseS1hbGwDZXllA3RhZwR0YWdzDWxvY2stb3Blbi1hbHQJbG9jay1vcGVuBGxvY2sEaG9tZQRpbmZvBGhlbHAGc2VhcmNoCGZsYXBwaW5nBnJld2luZApjaGFydC1saW5lCGJlbGwtb2ZmDmJlbGwtb2ZmLWVtcHR5BHBsdWcHZXllLW9mZgpyZXNjaGVkdWxlAmN3BGhvc3QJdGh1bWJzLXVwC3RodW1icy1kb3duB3Nw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') format('truetype'); } /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ @@ -17,7 +17,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'ifont'; - src: url('../font/ifont.svg?75097146#ifont') format('svg'); + src: url('../font/ifont.svg?12843713#ifont') format('svg'); } } */ @@ -168,3 +168,8 @@ .icon-chart-bar:before { content: '\e871'; } /* '' */ .icon-beaker:before { content: '\e872'; } /* '' */ .icon-magic:before { content: '\e873'; } /* '' */ +.icon-spin6:before { content: '\e874'; } /* '' */ +.icon-down-small:before { content: '\e875'; } /* '' */ +.icon-left-small:before { content: '\e876'; } /* '' */ +.icon-right-small:before { content: '\e877'; } /* '' */ +.icon-up-small:before { content: '\e878'; } /* '' */ \ No newline at end of file diff --git a/application/fonts/fontello-ifont/css/ifont-ie7-codes.css b/application/fonts/fontello-ifont/css/ifont-ie7-codes.css index 130f0e414..4f30af4af 100644 --- a/application/fonts/fontello-ifont/css/ifont-ie7-codes.css +++ b/application/fonts/fontello-ifont/css/ifont-ie7-codes.css @@ -114,4 +114,9 @@ .icon-chart-area { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-beaker { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-magic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file +.icon-magic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-spin6 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-down-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-left-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-right-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-up-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/application/fonts/fontello-ifont/css/ifont-ie7.css b/application/fonts/fontello-ifont/css/ifont-ie7.css index 4e6ee83dd..c0047b50a 100644 --- a/application/fonts/fontello-ifont/css/ifont-ie7.css +++ b/application/fonts/fontello-ifont/css/ifont-ie7.css @@ -125,4 +125,9 @@ .icon-chart-area { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-beaker { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-magic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file +.icon-magic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-spin6 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-down-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-left-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-right-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-up-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/application/fonts/fontello-ifont/css/ifont.css b/application/fonts/fontello-ifont/css/ifont.css index 546dffff1..971bf44b3 100644 --- a/application/fonts/fontello-ifont/css/ifont.css +++ b/application/fonts/fontello-ifont/css/ifont.css @@ -1,10 +1,10 @@ @font-face { font-family: 'ifont'; - src: url('../font/ifont.eot?81587324'); - src: url('../font/ifont.eot?81587324#iefix') format('embedded-opentype'), - url('../font/ifont.woff?81587324') format('woff'), - url('../font/ifont.ttf?81587324') format('truetype'), - url('../font/ifont.svg?81587324#ifont') format('svg'); + src: url('../font/ifont.eot?54745533'); + src: url('../font/ifont.eot?54745533#iefix') format('embedded-opentype'), + url('../font/ifont.woff?54745533') format('woff'), + url('../font/ifont.ttf?54745533') format('truetype'), + url('../font/ifont.svg?54745533#ifont') format('svg'); font-weight: normal; font-style: normal; } @@ -14,7 +14,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'ifont'; - src: url('../font/ifont.svg?81587324#ifont') format('svg'); + src: url('../font/ifont.svg?54745533#ifont') format('svg'); } } */ @@ -35,7 +35,7 @@ /* For safety - reset parent styles, that can break glyph codes*/ font-variant: normal; text-transform: none; - + /* fix buttons height, for twitter bootstrap */ line-height: 1em; @@ -46,6 +46,10 @@ /* you can be more comfortable with increased icons size */ /* font-size: 120%; */ + /* Font smoothing. That was taken from TWBS */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* Uncomment for 3D effect */ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } @@ -165,4 +169,9 @@ .icon-chart-area:before { content: '\e870'; } /* '' */ .icon-chart-bar:before { content: '\e871'; } /* '' */ .icon-beaker:before { content: '\e872'; } /* '' */ -.icon-magic:before { content: '\e873'; } /* '' */ \ No newline at end of file +.icon-magic:before { content: '\e873'; } /* '' */ +.icon-spin6:before { content: '\e874'; } /* '' */ +.icon-down-small:before { content: '\e875'; } /* '' */ +.icon-left-small:before { content: '\e876'; } /* '' */ +.icon-right-small:before { content: '\e877'; } /* '' */ +.icon-up-small:before { content: '\e878'; } /* '' */ \ No newline at end of file diff --git a/application/fonts/fontello-ifont/demo.html b/application/fonts/fontello-ifont/demo.html index c9a06c6dc..5cfd8dbf8 100644 --- a/application/fonts/fontello-ifont/demo.html +++ b/application/fonts/fontello-ifont/demo.html @@ -227,8 +227,54 @@ body { .i-code { display: none; } - - +@font-face { + font-family: 'ifont'; + src: url('./font/ifont.eot?11424534'); + src: url('./font/ifont.eot?11424534#iefix') format('embedded-opentype'), + url('./font/ifont.woff?11424534') format('woff'), + url('./font/ifont.ttf?11424534') format('truetype'), + url('./font/ifont.svg?11424534#ifont') format('svg'); + font-weight: normal; + font-style: normal; + } + + + .demo-icon + { + font-family: "ifont"; + font-style: normal; + font-weight: normal; + speak: none; + + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + + /* You can be more comfortable with increased icons size */ + /* font-size: 120%; */ + + /* Font smoothing. That was taken from TWBS */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ + } + -img('img/logo_icinga_big_dark.png', array('align' => 'right', 'width' => '150')) ?> +img('img/logo_icinga_big_dark.png', null, array('align' => 'right', 'width' => '75')) ?>
-
- translate('Logging out...'); ?> +
+ translate( + 'If this message does not disappear, it might be necessary to quit the' + . ' current session manually by clearing the cache, or by closing the current' + . ' browser session.' ); ?>
- diff --git a/application/views/scripts/config/authentication/create.phtml b/application/views/scripts/config/authentication/create.phtml deleted file mode 100644 index 427758292..000000000 --- a/application/views/scripts/config/authentication/create.phtml +++ /dev/null @@ -1,10 +0,0 @@ -
- tabs->showOnlyCloseButton() ?> -
-

translate('Create New Authentication Backend'); ?>

-

- translate( - 'Create a new backend for authenticating your users. This backend will be added at the end of your authentication order.' - ); ?> -

- \ No newline at end of file diff --git a/application/views/scripts/config/authentication/remove.phtml b/application/views/scripts/config/authentication/remove.phtml deleted file mode 100644 index 424aff9a0..000000000 --- a/application/views/scripts/config/authentication/remove.phtml +++ /dev/null @@ -1,5 +0,0 @@ -
- tabs->showOnlyCloseButton() ?> -
-

translate('Remove Backend'); ?>

- \ No newline at end of file diff --git a/application/views/scripts/config/authentication/reorder.phtml b/application/views/scripts/config/authentication/reorder.phtml deleted file mode 100644 index 688d99f88..000000000 --- a/application/views/scripts/config/authentication/reorder.phtml +++ /dev/null @@ -1,11 +0,0 @@ -
- -
- diff --git a/application/views/scripts/config/authentication/modify.phtml b/application/views/scripts/config/form.phtml similarity index 54% rename from application/views/scripts/config/authentication/modify.phtml rename to application/views/scripts/config/form.phtml index b01d7095a..a53f51721 100644 --- a/application/views/scripts/config/authentication/modify.phtml +++ b/application/views/scripts/config/form.phtml @@ -1,5 +1,6 @@
tabs->showOnlyCloseButton() ?>
-

translate('Edit Backend'); ?>

- \ No newline at end of file +
+ +
\ No newline at end of file diff --git a/application/views/scripts/config/general.phtml b/application/views/scripts/config/general.phtml new file mode 100644 index 000000000..e9aa039f9 --- /dev/null +++ b/application/views/scripts/config/general.phtml @@ -0,0 +1,9 @@ +
+ tabs->render($this); ?> +
+
+ messageBox)): ?> + messageBox->render() ?> + + form ?> +
diff --git a/application/views/scripts/config/index.phtml b/application/views/scripts/config/index.phtml deleted file mode 100644 index dab0acc4f..000000000 --- a/application/views/scripts/config/index.phtml +++ /dev/null @@ -1,11 +0,0 @@ -
-tabs->render($this); ?> -
- -
-messageBox)): ?> - messageBox->render() ?> - - -form ?> -
diff --git a/application/views/scripts/config/logging.phtml b/application/views/scripts/config/logging.phtml deleted file mode 100644 index da6d886cc..000000000 --- a/application/views/scripts/config/logging.phtml +++ /dev/null @@ -1,35 +0,0 @@ -
-tabs->render($this); ?> -
- -
-form->getErrorMessages(); ?> - -messageBox)): ?> - messageBox->render() ?> - - -successMessage): ?> -
- - escape($this->successMessage); ?> -
- - - -
-

Errors occured when trying to save the project.

-

- The following errors occured when trying to save the configuration: -

-
    - -
  • escape($error) ?>
  • - -
-
- - -form ?> -
- diff --git a/application/views/scripts/config/module.phtml b/application/views/scripts/config/module.phtml index b7f6bf346..1d03f5d37 100644 --- a/application/views/scripts/config/module.phtml +++ b/application/views/scripts/config/module.phtml @@ -1,70 +1,77 @@
-tabs ?> -

escape($module->getTitle()) ?>

+ tabs ?>
- -translate('There is no such module installed.') ?> - -getDependencies(); -$restrictions = $module->getProvidedRestrictions(); -$permissions = $module->getProvidedPermissions(); -$state = $moduleData->enabled ? ($moduleData->loaded ? 'enabled' : 'failed') : 'disabled' - -?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
escape('Name') ?>escape($module->getName()) ?>
translate('State') ?> - qlink('disable', 'config/moduledisable', array( - 'name' => $module->getName() - )) ?> - - - qlink('enable', 'config/moduleenable', array( - 'name' => $module->getName() - )) ?> - -
escape('Version') ?>escape($module->getVersion()) ?>
escape('Description') ?>escape($module->getDescription())) ?>
escape('Dependencies') ?>translate('This module has no dependencies'); - -else: foreach ($dependencies as $name => $versionString): ?> -escape($name) ?>: escape($versionString) ?>
-
escape('Permissions') ?> -escape($permission->name) ?>: escape($permission->description) ?>
-
escape('Restrictions') ?> -escape($restriction->name) ?>: escape($restriction->description) ?>
-
- + + translate('There is no such module installed.') ?> + + getDependencies(); + $restrictions = $module->getProvidedRestrictions(); + $permissions = $module->getProvidedPermissions(); + $state = $moduleData->enabled ? ($moduleData->loaded ? 'enabled' : 'failed') : 'disabled' + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
escape($this->translate('Name')) ?>escape($module->getName()) ?>
translate('State') ?> + qlink( + $this->translate('disable'), + 'config/moduledisable', + array('name' => $module->getName()), + array('title' => sprintf($this->translate('Disable the %s module'), $module->getName())) + ); ?> + + + qlink( + $this->translate('enable'), + 'config/moduleenable', + array('name' => $module->getName()), + array('title' => sprintf($this->translate('Enable the %s module'), $module->getName())) + ); ?> + +
escape($this->translate('Version')) ?>escape($module->getVersion()) ?>
escape($this->translate('Description')) ?>escape($module->getDescription())) ?>
escape($this->translate('Dependencies')) ?> + translate('This module has no dependencies'); + else: foreach ($dependencies as $name => $versionString): ?> + escape($name) ?>: escape($versionString) ?>
+ +
escape($this->translate('Permissions')) ?> + + escape($permission->name) ?>: escape($permission->description) ?>
+ +
escape($this->translate('Restrictions')) ?> + + escape($restriction->name) ?>: escape($restriction->description) ?>
+ +
diff --git a/application/views/scripts/config/modules.phtml b/application/views/scripts/config/modules.phtml index 95c9fa731..9cf22c026 100644 --- a/application/views/scripts/config/modules.phtml +++ b/application/views/scripts/config/modules.phtml @@ -1,29 +1,35 @@ +compact): ?>
-tabs ?> -

translate('Installed Modules') ?>

-paginationControl($modules) ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
- +
- - - - - + + + - - - -
- enabled): ?> - icon('thumbs-up', $this->translate('Module is enabled')) ?> - - icon('thumbs-down', $this->translate('Module is disabled')) ?> - - +
+ enabled && $module->loaded) { + echo $this->icon('thumbs-up', sprintf($this->translate('Module %s is enabled'), $module->name)); + } elseif (! $module->enabled) { + echo $this->icon('thumbs-down', sprintf($this->translate('Module %s is disabled'), $module->name)); + } else { // ! $module->loaded + echo $this->icon('thumbs-down', sprintf($this->translate('Module %s has failed to load'), $module->name)); + } + + echo $this->qlink( + $module->name, 'config/module/', - array('name' => $module->name) - ) ?>">escape($module->name); ?> (enabled ? ($module->loaded ? 'enabled' : 'failed') : 'disabled' - ?>) -
+ array('name' => $module->name), + array('title' => sprintf($this->translate('Show the overview of the %s module'), $module->name)) + ); ?> + + + + +
diff --git a/application/views/scripts/config/resource.phtml b/application/views/scripts/config/resource.phtml index c5d4ca0fc..3e34c308d 100644 --- a/application/views/scripts/config/resource.phtml +++ b/application/views/scripts/config/resource.phtml @@ -2,31 +2,43 @@
-

- - icon('plus'); ?> translate('Create A New Resource'); ?> - -

- + + icon('plus'); ?> translate('Create A New Resource'); ?> + +
-resources as $name): ?> + resources as $name): ?> - - +
translate('Resource'); ?> translate('Remove'); ?>
- - icon('edit'); ?> escape($name); ?> - + qlink( + $name, + 'config/editresource', + array('resource' => $name), + array( + 'icon' => 'edit', + 'title' => sprintf($this->translate('Edit resource %s'), $name) + ) + ); ?> - - icon('cancel'); ?> - + +
+ qlink( + '', + 'config/removeresource', + array('resource' => $name), + array( + 'icon' => 'trash', + 'title' => sprintf($this->translate('Remove resource %s'), $name) + ) + ); ?> +
diff --git a/application/views/scripts/config/resource/create.phtml b/application/views/scripts/config/resource/create.phtml index 0750a5aa9..a53f51721 100644 --- a/application/views/scripts/config/resource/create.phtml +++ b/application/views/scripts/config/resource/create.phtml @@ -1,6 +1,6 @@
tabs->showOnlyCloseButton() ?>
-

translate('Create A New Resource'); ?>

-

translate('Resources are entities that provide data to Icinga Web 2.'); ?>

- \ No newline at end of file +
+ +
\ No newline at end of file diff --git a/application/views/scripts/config/resource/modify.phtml b/application/views/scripts/config/resource/modify.phtml index d355aeb4c..a53f51721 100644 --- a/application/views/scripts/config/resource/modify.phtml +++ b/application/views/scripts/config/resource/modify.phtml @@ -1,5 +1,6 @@
tabs->showOnlyCloseButton() ?>
-

translate('Edit Existing Resource'); ?>

- \ No newline at end of file +
+ +
\ No newline at end of file diff --git a/application/views/scripts/config/resource/remove.phtml b/application/views/scripts/config/resource/remove.phtml index 14829ab40..a53f51721 100644 --- a/application/views/scripts/config/resource/remove.phtml +++ b/application/views/scripts/config/resource/remove.phtml @@ -1,5 +1,6 @@
tabs->showOnlyCloseButton() ?>
-

translate('Remove Existing Resource'); ?>

- \ No newline at end of file +
+ +
\ No newline at end of file diff --git a/application/views/scripts/config/userbackend/reorder.phtml b/application/views/scripts/config/userbackend/reorder.phtml new file mode 100644 index 000000000..08b5f19be --- /dev/null +++ b/application/views/scripts/config/userbackend/reorder.phtml @@ -0,0 +1,11 @@ +
+ +
+ diff --git a/application/views/scripts/dashboard/error.phtml b/application/views/scripts/dashboard/error.phtml index e5a0f3939..56e32faf4 100644 --- a/application/views/scripts/dashboard/error.phtml +++ b/application/views/scripts/dashboard/error.phtml @@ -1,13 +1,13 @@
-

+

translate('Could not persist dashboard'); ?>

- - config->getFilename(); ?>;. + translate('Please copy the following dashboard snippet to '); ?> + config->getConfigFile(); ?>;.
- + translate('Make sure that the webserver can write to this file.'); ?>

-
config->render(); ?>
+
config; ?>

-

+

translate('Error details'); ?>

error->getMessage(); ?>

-
+ \ No newline at end of file diff --git a/application/views/scripts/dashboard/index.phtml b/application/views/scripts/dashboard/index.phtml index 7c3724dc6..1d561146f 100644 --- a/application/views/scripts/dashboard/index.phtml +++ b/application/views/scripts/dashboard/index.phtml @@ -1,5 +1,7 @@
+compact): ?> tabs ?> +
dashboard): ?>
@@ -9,10 +11,16 @@

escape($this->translate('Welcome to Icinga Web!')) ?>

- hasPermission('config/modules')) { + echo $this->escape($this->translate( + 'Currently there is no dashlet available. Please contact the administrator.' + )); + } else { + printf( $this->escape($this->translate('Currently there is no dashlet available. This might change once you enabled some of the available %s.')), $this->qlink($this->translate('modules'), 'config/modules') - ) ?> + ); + } ?>

- \ No newline at end of file + diff --git a/application/views/scripts/dashboard/new-dashlet.phtml b/application/views/scripts/dashboard/new-dashlet.phtml index 98b055414..b265a253a 100644 --- a/application/views/scripts/dashboard/new-dashlet.phtml +++ b/application/views/scripts/dashboard/new-dashlet.phtml @@ -2,6 +2,5 @@ tabs ?>
-

form; ?>
\ No newline at end of file diff --git a/application/views/scripts/dashboard/remove-dashlet.phtml b/application/views/scripts/dashboard/remove-dashlet.phtml index 4c842cf29..b265a253a 100644 --- a/application/views/scripts/dashboard/remove-dashlet.phtml +++ b/application/views/scripts/dashboard/remove-dashlet.phtml @@ -1,14 +1,6 @@
tabs ?>
-
-

- -

- translate('Please confirm the removal'); ?>: - pane; ?>/dashlet; ?> -

- form; ?>
\ No newline at end of file diff --git a/application/views/scripts/dashboard/remove-pane.phtml b/application/views/scripts/dashboard/remove-pane.phtml index 45455d37d..b265a253a 100644 --- a/application/views/scripts/dashboard/remove-pane.phtml +++ b/application/views/scripts/dashboard/remove-pane.phtml @@ -1,14 +1,6 @@
tabs ?>
-
-

- -

- translate('Please confirm the removal of'); ?>: - pane; ?> -

- form; ?>
\ No newline at end of file diff --git a/application/views/scripts/dashboard/settings.phtml b/application/views/scripts/dashboard/settings.phtml index 19a846ace..3376da835 100644 --- a/application/views/scripts/dashboard/settings.phtml +++ b/application/views/scripts/dashboard/settings.phtml @@ -1,5 +1,3 @@ -
tabs ?>
@@ -25,9 +23,15 @@ getName(); ?> - - icon('cancel'); ?> - + qlink( + '', + 'dashboard/remove-pane', + array('pane' => $pane->getName()), + array( + 'icon' => 'trash', + 'title' => sprintf($this->translate('Remove pane %s'), $pane->getName()) + ) + ); ?> getDashlets(); ?> @@ -42,17 +46,31 @@ getDisabled() === true) continue; ?> - - getTitle(); ?> - + qlink( + $dashlet->getTitle(), + 'dashboard/update-dashlet', + array('pane' => $pane->getName(), 'dashlet' => $dashlet->getTitle()), + array('title' => sprintf($this->translate('Edit dashlet %s'), $dashlet->getTitle())) + ); ?> - getUrl(); ?> + qlink( + $dashlet->getUrl()->getRelativeUrl(), + $dashlet->getUrl()->getRelativeUrl(), + null, + array('title' => sprintf($this->translate('Show dashlet %s'), $dashlet->getTitle())) + ); ?> - - icon('cancel'); ?> - + qlink( + '', + 'dashboard/remove-dashlet', + array('pane' => $pane->getName(), 'dashlet' => $dashlet->getTitle()), + array( + 'icon' => 'trash', + 'title' => sprintf($this->translate('Remove dashlet %s from pane %s'), $dashlet->getTitle(), $pane->getName()) + ) + ); ?> @@ -60,4 +78,4 @@ - \ No newline at end of file + diff --git a/application/views/scripts/dashboard/update-dashlet.phtml b/application/views/scripts/dashboard/update-dashlet.phtml index e83c8b6c3..b265a253a 100644 --- a/application/views/scripts/dashboard/update-dashlet.phtml +++ b/application/views/scripts/dashboard/update-dashlet.phtml @@ -1,8 +1,6 @@
tabs ?>
-
-

form; ?>
\ No newline at end of file diff --git a/application/views/scripts/error/error.phtml b/application/views/scripts/error/error.phtml index 2a900651b..5b3480922 100644 --- a/application/views/scripts/error/error.phtml +++ b/application/views/scripts/error/error.phtml @@ -1,12 +1,8 @@ -title): ?>
-

escape($title) ?>

+tabs->showOnlyCloseButton() ?>
-
-message): ?>

escape($message)) ?>

-
escape($stackTrace) ?>
diff --git a/application/views/scripts/form/reorder-authbackend.phtml b/application/views/scripts/form/reorder-authbackend.phtml index 90319e0ad..3ced989e2 100644 --- a/application/views/scripts/form/reorder-authbackend.phtml +++ b/application/views/scripts/form/reorder-authbackend.phtml @@ -1,5 +1,5 @@ -
- + +
@@ -10,24 +10,55 @@ - diff --git a/application/views/scripts/group/form.phtml b/application/views/scripts/group/form.phtml new file mode 100644 index 000000000..cbf06590d --- /dev/null +++ b/application/views/scripts/group/form.phtml @@ -0,0 +1,6 @@ +
+ showOnlyCloseButton(); ?> +
+
+ +
\ No newline at end of file diff --git a/application/views/scripts/group/list.phtml b/application/views/scripts/group/list.phtml new file mode 100644 index 000000000..8c8a8a2a0 --- /dev/null +++ b/application/views/scripts/group/list.phtml @@ -0,0 +1,82 @@ +compact): ?> +
+ tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> +
+ backendSelection; ?> + filterEditor; ?> +
+
+ +
+translate('No backend found which is able to list groups') . '
'; + return; +} else { + $extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible; + $reducible = $this->hasPermission('config/authentication/groups/remove') && $backend instanceof Reducible; +} + +$firstRow = true; +foreach ($groups as $group): ?> + + +
Backend translate('Remove'); ?>
- - icon('edit'); ?> escape($backendNames[$i]); ?> - + qlink( + $backendNames[$i], + 'config/edituserbackend', + array('backend' => $backendNames[$i]), + array( + 'icon' => 'edit', + 'class' => 'rowaction', + 'title' => sprintf($this->translate('rEdit user backend %s'), $backendNames[$i]) + ) + ); ?> - - icon('cancel', $this->translate('Remove')); ?> - + qlink( + '', + 'config/removeuserbackend', + array('backend' => $backendNames[$i]), + array( + 'icon' => 'trash', + 'title' => sprintf($this->translate('Remove user backend %s'), $backendNames[$i]) + ) + ); ?> + 0): ?> - -
+ + + + + + + + + + + + + + + + + +hasResult()): ?> + +
translate('Group'); ?>translate('Remove'); ?>
qlink($group->group_name, 'group/show', array( + 'backend' => $backend->getName(), + 'group' => $group->group_name + ), array( + 'title' => sprintf($this->translate('Show detailed information for group %s'), $group->group_name) + )); ?> + qlink( + null, + 'group/remove', + array( + 'backend' => $backend->getName(), + 'group' => $group->group_name + ), + array( + 'title' => sprintf($this->translate('Remove group %s'), $group->group_name), + 'icon' => 'trash' + ) + ); ?> +
+ +

translate('No groups found matching the filter'); ?>

+ + +qlink($this->translate('Add a new group'), 'group/add', array('backend' => $backend->getName()), array( + 'icon' => 'plus', + 'data-base-target' => '_next', + 'class' => 'group-add' +)); ?> + +
\ No newline at end of file diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml new file mode 100644 index 000000000..725f22a27 --- /dev/null +++ b/application/views/scripts/group/show.phtml @@ -0,0 +1,94 @@ +hasPermission('config/authentication/groups/add') && $backend instanceof Extensible; + +$editLink = null; +if ($this->hasPermission('config/authentication/groups/edit') && $backend instanceof Updatable) { + $editLink = $this->qlink( + null, + 'group/edit', + array( + 'backend' => $backend->getName(), + 'group' => $group->group_name + ), + array( + 'title' => sprintf($this->translate('Edit group %s'), $group->group_name), + 'class' => 'group-edit', + 'icon' => 'edit' + ) + ); +} + +?> +
+ compact): ?> + + +

escape($group->group_name); ?>

+ + + + + + + + + +
translate('Created at'); ?>created_at === null ? '-' : $this->formatDateTime($group->created_at); ?>
translate('Last modified'); ?>last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?>
+

translate('Members'); ?>

+compact): ?> + sortBox; ?> + + limiter; ?> + paginator; ?> +compact): ?> + filterEditor; ?> + +
+
+ + + + + + + + + + + + + + + + + + + + + +hasResult()): ?> + +
translate('Username'); ?>translate('Remove'); ?>
escape($member->user_name); ?> + getElement('user_name')->setValue($member->user_name); echo $removeForm; ?> +
+ +

translate('No group member found matching the filter'); ?>

+ + + qlink($this->translate('Add a new member'), 'group/addmember', array( + 'backend' => $backend->getName(), + 'group' => $group->group_name + ), array( + 'icon' => 'plus', + 'data-base-target' => '_next', + 'class' => 'member-add' + )); ?> + +
\ No newline at end of file diff --git a/application/views/scripts/joystickPagination.phtml b/application/views/scripts/joystickPagination.phtml index 27cafa309..9549fc2b0 100644 --- a/application/views/scripts/joystickPagination.phtml +++ b/application/views/scripts/joystickPagination.phtml @@ -6,7 +6,7 @@ if ($xAxisPaginator->count() <= 1 && $yAxisPaginator->count() <= 1) { return; // Display this pagination only if there are multiple pages } -$fromTo = t('%s: %d to %d of %d'); +$showText = $this->translate('%s: Show %s %u to %u out of %u', 'pagination.joystick'); $xAxisPages = $xAxisPaginator->getPages('all'); $yAxisPages = $yAxisPaginator->getPages('all'); @@ -28,15 +28,25 @@ $nextXAxisPage = $currentXAxisPage < $totalXAxisPages ? $currentXAxisPage + 1 :   - icon('up-open'); ?> + qlink( + '', + Url::fromRequest(), + array( + 'page' => $currentXAxisPage . ',' . $prevYAxisPage + ), + array( + 'icon' => 'up-open', + 'data-base-target' => '_self', + 'title' => sprintf( + $showText, + $this->translate('Y-Axis', 'pagination.joystick'), + $this->translate('hosts', 'pagination.joystick'), + ($prevYAxisPage - 1) * $yAxisPages->itemCountPerPage + 1, + $prevYAxisPage * $yAxisPages->itemCountPerPage, + $yAxisPages->totalItemCount + ) + ) + ); ?> icon('up-open'); ?> @@ -46,15 +56,25 @@ $nextXAxisPage = $currentXAxisPage < $totalXAxisPages ? $currentXAxisPage + 1 : - icon('left-open'); ?> + qlink( + '', + Url::fromRequest(), + array( + 'page' => $prevXAxisPage . ',' . $currentYAxisPage + ), + array( + 'icon' => 'left-open', + 'data-base-target' => '_self', + 'title' => sprintf( + $showText, + $this->translate('X-Axis', 'pagination.joystick'), + $this->translate('services', 'pagination.joystick'), + ($prevXAxisPage - 1) * $xAxisPages->itemCountPerPage + 1, + $prevXAxisPage * $xAxisPages->itemCountPerPage, + $xAxisPages->totalItemCount + ) + ) + ); ?> icon('left-open'); ?> @@ -62,15 +82,26 @@ $nextXAxisPage = $currentXAxisPage < $totalXAxisPages ? $currentXAxisPage + 1 :   - icon('right-open'); ?> + qlink( + '', + Url::fromRequest(), + array( + 'page' => $nextXAxisPage . ',' . $currentYAxisPage + ), + array( + 'icon' => 'right-open', + 'data-base-target' => '_self', + 'title' => sprintf( + $showText, + $this->translate('X-Axis', 'pagination.joystick'), + $this->translate('services', 'pagination.joystick'), + $currentXAxisPage * $xAxisPages->itemCountPerPage + 1, + $nextXAxisPage === $xAxisPages->last ? $xAxisPages->totalItemCount : $nextXAxisPage * $xAxisPages->itemCountPerPage, + $xAxisPages->totalItemCount + ) + ), + false + ); ?> icon('right-open'); ?> @@ -80,15 +111,25 @@ $nextXAxisPage = $currentXAxisPage < $totalXAxisPages ? $currentXAxisPage + 1 :   - icon('down-open'); ?> + qlink( + '', + Url::fromRequest(), + array( + 'page' => $currentXAxisPage . ',' . $nextYAxisPage + ), + array( + 'icon' => 'down-open', + 'data-base-target' => '_self', + 'title' => sprintf( + $showText, + $this->translate('Y-Axis', 'pagination.joystick'), + $this->translate('hosts', 'pagination.joystick'), + $currentYAxisPage * $yAxisPages->itemCountPerPage + 1, + $nextYAxisPage === $yAxisPages->last ? $yAxisPages->totalItemCount : $nextYAxisPage * $yAxisPages->itemCountPerPage, + $yAxisPages->totalItemCount + ) + ) + ); ?> icon('down-open'); ?> diff --git a/application/views/scripts/layout/menu.phtml b/application/views/scripts/layout/menu.phtml index 83196d362..ee2ab0081 100644 --- a/application/views/scripts/layout/menu.phtml +++ b/application/views/scripts/layout/menu.phtml @@ -1,13 +1,19 @@ -getPane('search')->hasDashlets()): ?> +use Icinga\Web\Widget\SearchDashboard; + +$searchDashboard = new SearchDashboard(); +$searchDashboard->setUser($this->Auth()->getUser()); + +if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?> - + diff --git a/application/views/scripts/list/applicationlog.phtml b/application/views/scripts/list/applicationlog.phtml index 583fde516..00102e855 100644 --- a/application/views/scripts/list/applicationlog.phtml +++ b/application/views/scripts/list/applicationlog.phtml @@ -1,9 +1,12 @@ +compact): ?>
- tabs->render($this) ?> -
- logData ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
- +
logData !== null): ?> @@ -16,7 +19,7 @@ escape($value->loglevel) ?> diff --git a/application/views/scripts/mixedPagination.phtml b/application/views/scripts/mixedPagination.phtml index bac2b202b..160657b12 100644 --- a/application/views/scripts/mixedPagination.phtml +++ b/application/views/scripts/mixedPagination.phtml @@ -9,11 +9,11 @@ use Icinga\Web\Url; if ($this->pageCount <= 1) return; -?>

-
- escape($value->message) ?> + escape($value->message), false) ?>
+ + + +translate('Permissions') ?> +translate('Restrictions') ?> + + + + + + $role): /** @var object $role */ ?> + + +escape($role->permissions, 0, 50) ?> + +without(...) or $role->shift(...) would be nice! +// $restrictions = clone $role; +// unset($restrictions['users']); +// unset($restrictions['groups']); +// unset($restrictions['permissions']); +// ?> + + + + $restriction): ?> + +escape($restrictionName) ?> +escape($restriction) ?> + + + + + + + + + + + + +
translate('Name') ?>translate('Users') ?>translate('Groups') ?>
+ qlink( + $name, + 'role/edit', + array('role' => $name), + array('title' => sprintf($this->translate('Edit role %s'), $name)) + ); ?> + escape($role->users) ?>escape($role->groups) ?> + qlink( + '', + 'role/remove', + array('role' => $name), + array( + 'icon' => 'trash', + 'title' => sprintf($this->translate('Remove role %s'), $name) + ) + ); ?> +
+ + + translate('Create a New Role') ?> + +
+ diff --git a/application/views/scripts/roles/index.phtml b/application/views/scripts/roles/index.phtml deleted file mode 100644 index 0f40f8279..000000000 --- a/application/views/scripts/roles/index.phtml +++ /dev/null @@ -1,68 +0,0 @@ -
- -

translate('Roles') ?>

-
-
-
- isEmpty()): ?> - translate('No roles found.') ?> - - - - - - - - - - - - - $role): /** @var object $role */ ?> - - - - - - - - - - -
translate('Name') ?>translate('Permissions') ?>translate('Restrictions') ?>translate('Users') ?>translate('Groups') ?>
- escape($name) ?> - - escape($role->permissions, 0, 50) ?> - without(...) or $role->shift(...) would be nice! - $restrictions = $role; - unset($restrictions['users']); - unset($restrictions['groups']); - unset($restrictions['permissions']); - ?> - - - - $restriction): ?> - - - - - - -
escape($restrictionName) ?>escape($restriction) ?>
- -
escape($role->users) ?>escape($role->groups) ?> - - icon('cancel') ?> - -
- - - translate('New Role') ?> - -
-
diff --git a/application/views/scripts/roles/new.phtml b/application/views/scripts/roles/new.phtml deleted file mode 100644 index d7b277008..000000000 --- a/application/views/scripts/roles/new.phtml +++ /dev/null @@ -1,6 +0,0 @@ -
-

translate('New Role') ?>

-
-
- -
diff --git a/application/views/scripts/roles/remove.phtml b/application/views/scripts/roles/remove.phtml deleted file mode 100644 index e2d01ed62..000000000 --- a/application/views/scripts/roles/remove.phtml +++ /dev/null @@ -1,6 +0,0 @@ -
-

translate('Remove Role %s'), $name) ?>

-
-
- -
diff --git a/application/views/scripts/roles/update.phtml b/application/views/scripts/roles/update.phtml deleted file mode 100644 index 44f756b06..000000000 --- a/application/views/scripts/roles/update.phtml +++ /dev/null @@ -1,6 +0,0 @@ -
-

translate('Update Role %s'), $name) ?>

-
-
- -
diff --git a/application/views/scripts/user/form.phtml b/application/views/scripts/user/form.phtml new file mode 100644 index 000000000..cbf06590d --- /dev/null +++ b/application/views/scripts/user/form.phtml @@ -0,0 +1,6 @@ +
+ showOnlyCloseButton(); ?> +
+
+ +
\ No newline at end of file diff --git a/application/views/scripts/user/list.phtml b/application/views/scripts/user/list.phtml new file mode 100644 index 000000000..5d8d7b017 --- /dev/null +++ b/application/views/scripts/user/list.phtml @@ -0,0 +1,82 @@ +compact): ?> +
+ tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> +
+ backendSelection; ?> + filterEditor; ?> +
+
+ +
+translate('No backend found which is able to list users') . '
'; + return; +} else { + $extensible = $this->hasPermission('config/authentication/users/add') && $backend instanceof Extensible; + $reducible = $this->hasPermission('config/authentication/users/remove') && $backend instanceof Reducible; +} + +$firstRow = true; +foreach ($users as $user): ?> + + + + + + + + + + + + + + + + + + + + +hasResult()): ?> + +
translate('Username'); ?>translate('Remove'); ?>
qlink($user->user_name, 'user/show', array( + 'backend' => $backend->getName(), + 'user' => $user->user_name + ), array( + 'title' => sprintf($this->translate('Show detailed information about %s'), $user->user_name) + )); ?> + qlink( + null, + 'user/remove', + array( + 'backend' => $backend->getName(), + 'user' => $user->user_name + ), + array( + 'title' => sprintf($this->translate('Remove user %s'), $user->user_name), + 'icon' => 'trash' + ) + ); ?> +
+ +

translate('No users found matching the filter'); ?>

+ + +qlink($this->translate('Add a new user'), 'user/add', array('backend' => $backend->getName()), array( + 'icon' => 'plus', + 'data-base-target' => '_next', + 'class' => 'user-add' +)); ?> + + \ No newline at end of file diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml new file mode 100644 index 000000000..a5b9850e1 --- /dev/null +++ b/application/views/scripts/user/show.phtml @@ -0,0 +1,116 @@ +hasPermission('config/authentication/users/edit') && $backend instanceof Updatable) { + $editLink = $this->qlink( + null, + 'user/edit', + array( + 'backend' => $backend->getName(), + 'user' => $user->user_name + ), + array( + 'title' => sprintf($this->translate('Edit user %s'), $user->user_name), + 'class' => 'user-edit', + 'icon' => 'edit' + ) + ); +} + +?> +
+compact): ?> + + +

escape($user->user_name); ?>

+ + + + + + + + + + + + + +
translate('State'); ?>is_active === null ? '-' : ($user->is_active ? $this->translate('Active') : $this->translate('Inactive')); ?>
translate('Created at'); ?>created_at === null ? '-' : $this->formatDateTime($user->created_at); ?>
translate('Last modified'); ?>last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?>
+

translate('Group Memberships'); ?>

+compact): ?> + sortBox; ?> + + limiter; ?> + paginator; ?> +compact): ?> + filterEditor; ?> + +
+
+ + + + + + + + + + + + + + + + + +hasResult()): ?> + +
translate('Group'); ?>translate('Cancel', 'group.membership'); ?>
+ hasPermission('config/authentication/groups/show') && $membership->backend instanceof Selectable): ?> + qlink($membership->group_name, 'group/show', array( + 'backend' => $membership->backend->getName(), + 'group' => $membership->group_name + ), array( + 'title' => sprintf($this->translate('Show detailed information for group %s'), $membership->group_name) + )); ?> + + escape($membership->group_name); ?> + + + backend instanceof Reducible): ?> + setAction($this->url('group/removemember', array( + 'backend' => $membership->backend->getName(), + 'group' => $membership->group_name + ))); ?> + + - + +
+ +

translate('No memberships found matching the filter'); ?>

+ + +qlink( + $this->translate('Create New Membership'), + 'user/createmembership', + array( + 'backend' => $backend->getName(), + 'user' => $user->user_name + ), + array( + 'icon' => 'plus', + 'data-base-target' => '_next', + 'class' => 'membership-create' + ) +); ?> + +
\ No newline at end of file diff --git a/application/views/scripts/usergroupbackend/form.phtml b/application/views/scripts/usergroupbackend/form.phtml new file mode 100644 index 000000000..cbf06590d --- /dev/null +++ b/application/views/scripts/usergroupbackend/form.phtml @@ -0,0 +1,6 @@ +
+ showOnlyCloseButton(); ?> +
+
+ +
\ No newline at end of file diff --git a/application/views/scripts/usergroupbackend/list.phtml b/application/views/scripts/usergroupbackend/list.phtml new file mode 100644 index 000000000..49b6d5372 --- /dev/null +++ b/application/views/scripts/usergroupbackend/list.phtml @@ -0,0 +1,46 @@ +
+ +
+
+qlink( + $this->translate('Create A New User Group Backend'), + 'usergroupbackend/create', + null, + array( + 'icon' => 'plus' + ) +); ?> + 0): ?> + + + + + + + + + + + + + + + +
translate('Backend'); ?>translate('Remove'); ?>
+ qlink( + $backendName, + 'usergroupbackend/edit', + array('backend' => $backendName), + array('title' => sprintf($this->translate('Edit user group backend %s'), $backendName)) + ); ?> + qlink( + null, + 'usergroupbackend/remove', + array('backend' => $backendName), + array( + 'title' => sprintf($this->translate('Remove user group backend %s'), $backendName), + 'icon' => 'trash' + ) + ); ?>
+ +
\ No newline at end of file diff --git a/bin/icingacli b/bin/icingacli index c85777c9c..1922c9706 100755 --- a/bin/icingacli +++ b/bin/icingacli @@ -1,7 +1,6 @@ -#!/usr/bin/php +#!/usr/bin/env php %s" % (lvl, text, lvl) + if ftype == "md": + return "#" * lvl + " " + text + +def format_logentry(log_entry, args = args, issue_url = ISSUE_URL): + if args.links: + if args.html: + return "
  • {0} {1}: {2}
  • ".format(log_entry[0], log_entry[1], log_entry[2], issue_url) + else: + return "* {0} [{1}]({3}{1} \"{0} {1}\"): {2}".format(log_entry[0], log_entry[1], log_entry[2], issue_url) + else: + if args.html: + return "
  • %s %d: %s
  • " % log_entry + else: + return "* %s %d: %s" % log_entry + + +version_name = args.version + +if args.project: + ISSUE_PROJECT=args.project + +rsp = urllib2.urlopen("https://dev.icinga.org/projects/%s/versions.json" % (ISSUE_PROJECT)) +versions_data = json.loads(rsp.read()) + +version_id = None + +for version in versions_data["versions"]: + if version["name"] == version_name: + version_id = version["id"] + break + +if version_id == None: + print "Version '%s' not found." % (version_name) + sys.exit(1) + +changes = "" + +if "custom_fields" in version: + for field in version["custom_fields"]: + if field["id"] == 14: + changes = field["value"] + break + + changes = string.join(string.split(changes, "\r\n"), "\n") + +print format_header("What's New in Version %s" % (version_name), 2) +print "" + +if changes: + print format_header("Changes", 3) + print "" + print changes + print "" + +offset = 0 + +log_entries = [] + +while True: + # We could filter using &cf_13=1, however this doesn't currently work because the custom field isn't set + # for some of the older tickets: + rsp = urllib2.urlopen("https://dev.icinga.org/projects/%s/issues.json?offset=%d&status_id=closed&fixed_version_id=%d" % (ISSUE_PROJECT, offset, version_id)) + issues_data = json.loads(rsp.read()) + issues_count = len(issues_data["issues"]) + offset = offset + issues_count + + if issues_count == 0: + break + + for issue in issues_data["issues"]: + ignore_issue = False + + if "custom_fields" in issue: + for field in issue["custom_fields"]: + if field["id"] == 13 and "value" in field and field["value"] == "0": + ignore_issue = True + break + + if ignore_issue: + continue + + log_entries.append((issue["tracker"]["name"], issue["id"], issue["subject"].strip())) + +for p in range(2): + not_empty = False + + for log_entry in log_entries: + if (p == 0 and log_entry[0] == "Feature") or (p == 1 and log_entry[0] != "Feature"): + not_empty = True + + if not_empty: + print format_header("Features", 4) if p == 0 else format_header("Bugfixes", 4) + print "" + if args.html: + print "
      " + + for log_entry in sorted(log_entries): + if (p == 0 and log_entry[0] == "Feature") or (p == 1 and log_entry[0] != "Feature"): + print format_logentry(log_entry) + + if not_empty: + if args.html: + print "
    " + + print "" + +sys.exit(0) diff --git a/doc/LICENSEHEAD b/doc/LICENSEHEAD deleted file mode 100644 index 6f621ae36..000000000 --- a/doc/LICENSEHEAD +++ /dev/null @@ -1,22 +0,0 @@ -This file is part of Icinga Web 2. - -Icinga Web 2 - Head for multiple monitoring backends. -Copyright (C) %(YEAR)s Icinga Development Team - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -@copyright %(YEAR)s Icinga Development Team -@license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 -@author Icinga Development Team diff --git a/doc/LIVESTATUS_COLUMNS b/doc/LIVESTATUS_COLUMNS deleted file mode 100644 index ada6cf100..000000000 --- a/doc/LIVESTATUS_COLUMNS +++ /dev/null @@ -1,293 +0,0 @@ -# TODO: Remove from release, used while developing - -hosts -===== -accept_passive_checks -acknowledged -acknowledgement_type -action_url -action_url_expanded -active_checks_enabled -address -alias -check_command -check_flapping_recovery_notification -check_freshness -check_interval -check_options -check_period -check_type -checks_enabled -childs -comments -comments_with_info -contact_groups -contacts -current_attempt -current_notification_number -custom_variable_names -custom_variable_values -custom_variables -display_name -downtimes -downtimes_with_info -event_handler_enabled -execution_time -filename -first_notification_delay -flap_detection_enabled -groups -hard_state -has_been_checked -high_flap_threshold -icon_image -icon_image_alt -icon_image_expanded -in_check_period -in_notification_period -initial_state -is_executing -is_flapping -last_check -last_hard_state -last_hard_state_change -last_notification -last_state -last_state_change -last_time_down -last_time_unreachable -last_time_up -latency -long_plugin_output -low_flap_threshold -max_check_attempts -modified_attributes -modified_attributes_list -name -next_check -next_notification -no_more_notifications -notes -notes_expanded -notes_url -notes_url_expanded -notification_interval -notification_period -notifications_enabled -num_services -num_services_crit -num_services_hard_crit -num_services_hard_ok -num_services_hard_unknown -num_services_hard_warn -num_services_ok -num_services_pending -num_services_unknown -num_services_warn -obsess_over_host -parents -pending_flex_downtime -percent_state_change -perf_data -plugin_output -pnpgraph_present -process_performance_data -retry_interval -scheduled_downtime_depth -services -services_with_info -services_with_state -state -state_type -statusmap_image -total_services -worst_service_hard_state -worst_service_state -x_3d -y_3d -z_3d - -services -======== -accept_passive_checks -acknowledged -acknowledgement_type -action_url -action_url_expanded -active_checks_enabled -check_command -check_interval -check_options -check_period -check_type -checks_enabled -comments -comments_with_info -contact_groups -contacts -current_attempt -current_notification_number -custom_variable_names -custom_variable_values -custom_variables -description -display_name -downtimes -downtimes_with_info -event_handler -event_handler_enabled -execution_time -first_notification_delay -flap_detection_enabled -groups -has_been_checked -high_flap_threshold -host_accept_passive_checks -host_acknowledged -host_acknowledgement_type -host_action_url -host_action_url_expanded -host_active_checks_enabled -host_address -host_alias -host_check_command -host_check_flapping_recovery_notification -host_check_freshness -host_check_interval -host_check_options -host_check_period -host_check_type -host_checks_enabled -host_childs -host_comments -host_comments_with_info -host_contact_groups -host_contacts -host_current_attempt -host_current_notification_number -host_custom_variable_names -host_custom_variable_values -host_custom_variables -host_display_name -host_downtimes -host_downtimes_with_info -host_event_handler_enabled -host_execution_time -host_filename -host_first_notification_delay -host_flap_detection_enabled -host_groups -host_hard_state -host_has_been_checked -host_high_flap_threshold -host_icon_image -host_icon_image_alt -host_icon_image_expanded -host_in_check_period -host_in_notification_period -host_initial_state -host_is_executing -host_is_flapping -host_last_check -host_last_hard_state -host_last_hard_state_change -host_last_notification -host_last_state -host_last_state_change -host_last_time_down -host_last_time_unreachable -host_last_time_up -host_latency -host_long_plugin_output -host_low_flap_threshold -host_max_check_attempts -host_modified_attributes -host_modified_attributes_list -host_name -host_next_check -host_next_notification -host_no_more_notifications -host_notes -host_notes_expanded -host_notes_url -host_notes_url_expanded -host_notification_interval -host_notification_period -host_notifications_enabled -host_num_services -host_num_services_crit -host_num_services_hard_crit -host_num_services_hard_ok -host_num_services_hard_unknown -host_num_services_hard_warn -host_num_services_ok -host_num_services_pending -host_num_services_unknown -host_num_services_warn -host_obsess_over_host -host_parents -host_pending_flex_downtime -host_percent_state_change -host_perf_data -host_plugin_output -host_pnpgraph_present -host_process_performance_data -host_retry_interval -host_scheduled_downtime_depth -host_services -host_services_with_info -host_services_with_state -host_state -host_state_type -host_statusmap_image -host_total_services -host_worst_service_hard_state -host_worst_service_state -host_x_3d -host_y_3d -host_z_3d -icon_image -icon_image_alt -icon_image_expanded -in_check_period -in_notification_period -initial_state -is_executing -is_flapping -last_check -last_hard_state -last_hard_state_change -last_notification -last_state -last_state_change -last_time_critical -last_time_ok -last_time_unknown -last_time_warning -latency -long_plugin_output -low_flap_threshold -max_check_attempts -modified_attributes -modified_attributes_list -next_check -next_notification -no_more_notifications -notes -notes_expanded -notes_url -notes_url_expanded -notification_interval -notification_period -notifications_enabled -obsess_over_service -percent_state_change -perf_data -plugin_output -pnpgraph_present -process_performance_data -retry_interval -scheduled_downtime_depth -state -state_type - diff --git a/doc/ORACLE b/doc/ORACLE deleted file mode 100644 index 8e982550a..000000000 --- a/doc/ORACLE +++ /dev/null @@ -1,3 +0,0 @@ -pdo_oci has a Bug that shall be fixed with PHP 5.4.5, it segfaults when -fetching more than a single LOB value. The patch works also for older -pdo_oci versions and can be found here: https://bugs.php.net/bug.php?id=57702 diff --git a/doc/about.md b/doc/about.md new file mode 100644 index 000000000..48b4041ac --- /dev/null +++ b/doc/about.md @@ -0,0 +1,70 @@ +# About Icinga Web 2 + +Icinga Web 2 is a powerful PHP framework for web applications that comes in a clean and reduced design. +It's fast, responsive, accessible and easily extensible with modules. + +## The monitoring module + +This is the core module for most Icinga Web 2 users. + +It provides an intuitive user interface for monitoring with Icinga (1 and 2). +Especially there are lots of list and detail views (e.g. for hosts and services) +you can sort and filter depending on what you want to see. + +You can also control the monitoring process itself by sending external commands to Icinga. +Most such actions (like rescheduling a check) can be done with just a single click. + +## Installation + +Icinga Web 2 can be installed easily from packages from the official package repositories. +Setting it up is also easy with the web based setup wizard. + +See [here](installation#installation) for more information about the installation. + +## Configuration + +Icinga Web 2 can be configured via the user interface and .ini files. + +See [here](configuration#configuration) for more information about the configuration. + +## Authentication + +With Icinga Web 2 you can authenticate against relational databases, LDAP and more. +These authentication methods can be easily configured (via the corresponding .ini file). + +See [here](authentication#authentication) for more information about +the different authentication methods available and how to configure them. + +## Authorization + +In Icinga Web 2 there are permissions and restrictions to allow and deny (respectively) +roles to view or to do certain things. +These roles can be assigned to users and groups. + +See [here](security#security) for more information about authorization +and how to configure roles. + +## User preferences + +Besides the global configuration each user has individual configuration options +like the interface's language or the current timezone. +They can be stored either in a database or in .ini files. + +See [here](preferences#preferences) for more information about a user's preferences +and how to configure their storage type. + +## Documentation + +With the documentation module you can read the documentation of the framework (and any module) directly in the user interface. + +The module can also export the documentation to PDF. + +## Translation + +With the translation module every piece of text in the user interface (of the framework itself and any module) can be translated to a language of your choice. + +Currently provided languages: + +* German +* Italian +* Portuguese diff --git a/doc/accessibility/ifont-mute.html b/doc/accessibility/ifont-mute.html new file mode 100644 index 000000000..f8252e36a --- /dev/null +++ b/doc/accessibility/ifont-mute.html @@ -0,0 +1,21 @@ + + + + + + Accessibility: Muted Icon Fonts + + + + + + + + Visit top rated article + + + diff --git a/doc/accessibility/ifont.html b/doc/accessibility/ifont.html new file mode 100644 index 000000000..32f122127 --- /dev/null +++ b/doc/accessibility/ifont.html @@ -0,0 +1,18 @@ + + + + + + Accessibility: Icon Fonts + + + + + + + + \ No newline at end of file diff --git a/doc/accessibility/link-labels.html b/doc/accessibility/link-labels.html new file mode 100644 index 000000000..439adb85e --- /dev/null +++ b/doc/accessibility/link-labels.html @@ -0,0 +1,15 @@ + + + + + + Accessibility: Link Labels + + + + + localhost + + \ No newline at end of file diff --git a/doc/accessibility/required-form-elements.html b/doc/accessibility/required-form-elements.html new file mode 100644 index 000000000..86fc9374e --- /dev/null +++ b/doc/accessibility/required-form-elements.html @@ -0,0 +1,19 @@ + + + + + + Accessibility: Required form elements + + + + +
    + + +
    + + \ No newline at end of file diff --git a/doc/accessibility/skip-content.html b/doc/accessibility/skip-content.html new file mode 100644 index 000000000..3c1b2b492 --- /dev/null +++ b/doc/accessibility/skip-content.html @@ -0,0 +1,179 @@ + + + + + + Accessibility: Skip Links + + + + + +
    + + +
    +
    + +
    +

    Content

    + +

    + Organised prehistoric cultures began developing on Bulgarian lands + during the Neolithic period. Its ancient history saw the presence + of the Thracians and later the Greeks and Romans. The emergence of + a unified Bulgarian state dates back to the establishment of the + First Bulgarian Empire + in 681 CE, which dominated most of the + Balkans and functioned as a cultural hub for Slavs during the + Middle Ages. +

    +

    + With the downfall of the Second Bulgarian Empire in 1396, its + territories came under Ottoman + rule for nearly five centuries. + The Russo-Turkish War (1877–78) led to the formation of the Third + Bulgarian State. The following years saw several conflicts with its + neighbours, which prompted Bulgaria to align with Germany in both + world wars. +

    +

    + In 1946 it became a single-party socialist state as part of the + Soviet-led Eastern Bloc. In December 1989 the ruling Communist + Party allowed multi-party elections, which subsequently led to + Bulgaria's transition into a democracy and a market-based economy. +

    + +

    Content2

    +

    + The development of Final Fantasy VIII began in 1997, during the + English localization process of Final Fantasy VII. It was produced + by Shinji Hashimoto, + and directed by Yoshinori Kitase. The music + was scored by regular series composer Nobuo Uematsu, and in a + series first a vocal piece was written as the game's theme, "Eyes + on Me", performed by Faye Wong. +

    +

    + The game was positively received by + critics, + who praised the originality + and scope of the game. It was + voted the 22nd-best game of all time in 2006 by readers of the + Japanese magazine Famitsu. The game was a commercial success; + thirteen weeks after its release, +

    +
    +
    + + \ No newline at end of file diff --git a/doc/accessibility/svg.html b/doc/accessibility/svg.html new file mode 100644 index 000000000..8ee548f38 --- /dev/null +++ b/doc/accessibility/svg.html @@ -0,0 +1,19 @@ + + + + + + Accessibility: SVGs + + + + + + A Circle + A red circle with a black border. + + + + + + diff --git a/doc/accessibility/text-cue-for-required-form-control-labels.html b/doc/accessibility/text-cue-for-required-form-control-labels.html new file mode 100644 index 000000000..1dd38eb8a --- /dev/null +++ b/doc/accessibility/text-cue-for-required-form-control-labels.html @@ -0,0 +1,36 @@ + + + + + + Accessibility: Text cue for required form control labels + + + + + +
    + + +
    + + \ No newline at end of file diff --git a/doc/authentication.md b/doc/authentication.md index 994d44e48..39e0caf3c 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -2,43 +2,45 @@ **Choosing the Authentication Method** -With Icinga Web 2 you can authenticate against Active Directory, LDAP, a MySQL or PostgreSQL database or delegate -authentication to the web server. Authentication methods can be chained to set up fallback authentication methods +With Icinga Web 2 you can authenticate against Active Directory, LDAP, a MySQL or a PostgreSQL database or delegate +authentication to the web server. + +Authentication methods can be chained to set up fallback authentication methods or if users are spread over multiple places. -## Configuration +## Configuration Authentication methods are configured in the INI file **config/authentication.ini**. Each section in the authentication configuration represents a single authentication method. The order of entries in the authentication configuration determines the order of the authentication methods. -If the current authentication method errors or the current authentication method does not know the account being +If the current authentication method errors or if the current authentication method does not know the account being authenticated, the next authentication method will be used. -## External Authentication +### External Authentication For delegating authentication to the web server simply add `autologin` to your authentication configuration: ```` [autologin] -backend = autologin +backend = external ```` If your web server is not configured for authentication though the `autologin` section has no effect. -## Active Directory or LDAP Authentication +### Active Directory or LDAP Authentication If you want to authenticate against Active Directory or LDAP, you have to define a -[LDAP resource](#resources-configuration-ldap) first which will be referenced as data source for the Active Directory +[LDAP resource](resources.md#resources-configuration-ldap) which will be referenced as data source for the Active Directory or LDAP configuration method. -### LDAP +#### LDAP Directive | Description ------------------------|------------ **backend** | `ldap` -**resource** | The name of the LDAP resource defined in [resources.ini](resources). +**resource** | The name of the LDAP resource defined in [resources.ini](resources.md#resources). **user_class** | LDAP user class. **user_name_attribute** | LDAP attribute which contains the username. @@ -52,12 +54,16 @@ user_class = inetOrgPerson user_name_attribute = uid ``` -### Active Directory +Note that in case the set *user_name_attribute* holds multiple values it is required that all of its +values are unique. Additionally, a user will be logged in using the exact user id used to authenticate +with Icinga Web 2 (e.g. an alias) no matter what the primary user id might actually be. + +#### Active Directory Directive | Description ------------------------|------------ **backend** | `ad` -**resource** | The name of the LDAP resource defined in [resources.ini](resources). +**resource** | The name of the LDAP resource defined in [resources.ini](resources.md#resources). **Example:** @@ -67,29 +73,47 @@ backend = ad resource = my_ad ``` -## Database Authentication +### Database Authentication -If you want to authenticate against a MySQL or PostgreSQL database, you have to define a -[database resource](#resources-configuration-database) first which will be referenced as data source for the database +If you want to authenticate against a MySQL or a PostgreSQL database, you have to define a +[database resource](resources.md#resources-configuration-database) which will be referenced as data source for the database authentication method. Directive | Description ------------------------|------------ **backend** | `db` -**resource** | The name of the database resource defined in [resources.ini](resources). +**resource** | The name of the database resource defined in [resources.ini](resources.md#resources). **Example:** ``` -[auth_ad] -backend = ad -resource = my_db +[auth_db] +backend = db +resource = icingaweb-mysql ``` +#### Database Setup + +For authenticating against a database, you have to import one of the following database schemas: + +* **etc/schema/preferences.mysql.sql** (for **MySQL** database) +* **etc/schema/preferences.pgsql.sql** (for **PostgreSQL** databases) + +After that you have to define the [database resource](resources.md#resources-configuration-database). + **Manually Creating Users** -```` -openssl passwd -1 "password" +Icinga Web 2 uses the MD5 based BSD password algorithm. For generating a password hash, please use the following +command: +```` +openssl passwd -1 password +```` + +> Note: The switch to `openssl passwd` is the **number one** (`-1`) for using the MD5 based BSD password algorithm. + +Insert the user into the database using the generated password hash: + +```` INSERT INTO icingaweb_user (name, active, password_hash) VALUES ('icingaadmin', 1, 'hash from openssl'); ```` diff --git a/doc/configuration.md b/doc/configuration.md new file mode 100644 index 000000000..90946be83 --- /dev/null +++ b/doc/configuration.md @@ -0,0 +1,15 @@ +# Configuration + +## Overview + +Apart from its web configuration capabilities, the local configuration is +stored in `/etc/icingaweb2` by default (depending on your config setup). + + Location | File | Description + ------------------------------|-----------------------|--------------------------- + . | config.ini | General configuration (logging, preferences) + . | resources.ini | Global resources (Icinga Web 2 database for preferences and authentication, icinga ido database) + . | roles.ini | User specific roles (e.g. `administrators`) and permissions + . | [authentication.ini](authentication.md) | Authentication backends (e.g. database) + enabledModules | Symlink | Contains symlinks to enabled modules from `/usr/share/icingaweb2/modules/*`. Defaults to [monitoring](modules/monitoring/doc/configuration.md) and `doc`. + modules | Directory | Module specific configuration diff --git a/doc/external_authentication.md b/doc/external_authentication.md index 240713038..05225fb82 100644 --- a/doc/external_authentication.md +++ b/doc/external_authentication.md @@ -1,90 +1,86 @@ -# Externel Authentication +# External Authentication -It is possible to use the authentication mechanism of the webserver, -instead of using the internal authentication-manager to -authenticate users. This might be useful if you only have very few users, and -user management over *.htaccess* is sufficient, or if you must use some other -authentication mechanism that is only available through your webserver. +It is possible to utilize the authentication mechanism of the webserver instead +of the internal authentication of Icinga Web 2 to authenticate users. This might +be useful if you only have very few users and user management over **.htaccess** +is not sufficient or if you are required to use some other authentication +mechanism that is only available by utilizing the webserver. -When external authentication is used, Icingaweb will entrust the -complete authentication process to the external authentication provider (the webserver): -The provider should take care of authenticating the user and declining -all requests with invalid or missing credentials. When the authentication -was succesful, it should provide the authenticated users name to its php-module -and Icingaweb will assume that the user is authorized to access the page. -Because of this it is very important that the webservers authentication is -configured correctly, as wrong configuration could lead to unauthorized -access to the site, or a broken login-process. +Icinga Web 2 will entrust the complete authentication process to the +authentication provider of the webserver, if external authentication is used. +So it is very important that the webserver's authentication is configured +correctly as wrong configuration might lead to unauthorized access or a +malfunction in the login-process. +## Using External Authentication -## Use External Authentication +External authentication in Icinga Web 2 requires the following preparations: -Using external authentication in Icingaweb requires two steps to work: +1. The external authentication must be set up properly to correctly + authenticate users +2. Icinga Web 2 must be configured to use external authentication -1. The external authentication must be set up correctly to always - authenticate the users. -2. Icingaweb must be configured to use the external authentication. +### Preparing the External Authentication Provider +This step depends heavily on the used webserver and authentication mechanism you +want to use. It is not possible to cover all possibillities and you should +probably read the documentation for your webserver to get detailed instructions +on how to set up authentication properly. -### Prepare the External Authentication Provider - -This step depends heavily on the used webserver and authentication -mechanism you want to use. It is not possible to cover all possibillities -and you should probably read the documentation for your webserver for -detailed instructions on how to set up authentication properly. - -In general, you need to make sure that: - - - All routes require authentication - - Only permitted users are allowed to authenticate +In general you need to make sure that: +- All routes require authentication +- Only permitted users are allowed to authenticate #### Example Configuration for Apache and HTTPDigestAuthentication -The following example will show how to enable external authentication in Apache using -*HTTP Digest Authentication*. +The following example will show how to enable external authentication in Apache +using *HTTP Digest Authentication*. -##### Create users +##### Creating users -To create users for a digest authentication we can use the tool *htdigest*. -We choose *.icingawebdigest* as a name for the created file, containing -the user credentials. +To create users for digest authentication you can use the tool *htdigest*. In +this example **.icingawebdigest** is the name of the file containing the user +credentials. -This command will create a new file with the user *jdoe*. *htdigest* -will prompt you for your password, after it has been executed. If you -want to add more users to the file you need to ommit the *-c* parameter -in all further commands to avoInid the file to be overwritten. +This command creates a new file with the user *jdoe*. *htdigest* will prompt +you for a password. If you want to add more users to the file you need to omit +the *-c* parameter in all following commands to not to overwrite the file. +```` +sudo htdigest -c /etc/icingaweb2/.icingawebdigest "Icinga Web 2" jdoe +```` - sudo htdigest -c /etc/httpd/conf.d/.icingawebdigest "Icingaweb 2" jdoe +##### Configuring the Webserver +The webserver should require authentication for all public Icinga Web 2 files. -##### Set up authentication +```` + + AuthType digest + AuthName "Icinga Web 2" + AuthDigestProvider file + AuthUserFile /etc/icingaweb2/.icingawebdigest + Require valid-user + +```` -The webserver should require authentication for all public icingaweb files. +To get these changes to work, make sure to enable the module for +HTTPDigestAuthentication and restart the webserver. +### Preparing Icinga Web 2 - - AuthType digest - AuthName "Icingaweb 2" - AuthDigestProvider file - AuthUserFile /etc/httpd/conf.d/.icingawebdigest - Require valid-user - +Once external authentication is set up correctly you need to configure Icinga +Web 2. In case you already completed the setup wizard it is likely that you are +now finished. - -### Prepare Icingaweb +To get Icinga Web 2 to use external authentication the file +**config/authentication.ini** is required. Just add the following section +called "autologin", or any name of your choice, and save your changes: -When the external authentication is set up correctly, we need -to configure IcingaWeb to use it as an authentication source. The -configuration key *authenticationMode* in the section *global* defines -if the authentication should be handled internally or externally. Since -we want to delegate the authentication to the Webserver we choose -"external" as the new value: +```` +[autologin] +backend = external +```` - - [global] - ; ... - authenticationMode = "external" - ; ... - +Congratulations! You are now logged in when visiting Icinga Web 2. \ No newline at end of file diff --git a/doc/installation.md b/doc/installation.md index 88c9c3650..83939b143 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -3,21 +3,147 @@ The preferred way of installing Icinga Web 2 is to use the official package repositories depending on which operating system and distribution you are running. But it is also possible to install Icinga Web 2 directly from source. -## Installing Requirements +## Installing Requirements * A web server, e.g. Apache or nginx -* PHP >= 5.3.0 -* MySQL or PostgreSQL PHP libraries when using a database for authentication or storing user preferences into a database +* PHP >= 5.3.0 w/ gettext, intl and OpenSSL support +* MySQL or PostgreSQL PHP libraries when using a database for authentication or for storing preferences into a database * LDAP PHP library when using Active Directory or LDAP for authentication -* Icinga 1.x w/ Livestatus or IDO, Icinga 2 w/ Livestatus or IDO feature enabled +* Icinga 1.x w/ Livestatus or IDO; Icinga 2.x w/ Livestatus or IDO feature enabled +* MySQL or PostgreSQL PHP libraries when using IDO -## Installing Icinga Web 2 from Package +## Installing Icinga Web 2 from Package -A guide on how to install Icinga Web 2 from package will follow shortly. +Below is a list of official package repositories for installing Icinga Web 2 for various operating systems. -## Installing Icinga Web 2 from Source +Distribution | Repository +------------------------|--------------------------- +Debian | [debmon](http://debmon.org/packages/debmon-wheezy/icingaweb2), [Icinga Repository](http://packages.icinga.org/debian/) +Ubuntu | [Icinga Repository](http://packages.icinga.org/ubuntu/) +RHEL/CentOS | [Icinga Repository](http://packages.icinga.org/epel/) +openSUSE | [Icinga Repository](http://packages.icinga.org/openSUSE/) +SLES | [Icinga Repository](http://packages.icinga.org/SUSE/) +Gentoo | - +FreeBSD | - +ArchLinux | [Upstream](https://aur.archlinux.org/packages/icingaweb2) -**Step 1: Getting the Source** +Packages for distributions other than the ones listed above may also be available. +Please contact your distribution packagers. + +### Setting up Package Repositories + +You need to add the Icinga repository to your package management configuration for installing Icinga Web 2. +Below is a list with examples for various distributions. + +**Debian (debmon)**: +```` +wget -O - http://debmon.org/debmon/repo.key 2>/dev/null | apt-key add - +echo 'deb http://debmon.org/debmon debmon-wheezy main' >/etc/apt/sources.list.d/debmon.list +apt-get update +```` + +**Ubuntu Trusty**: +```` +wget -O - http://packages.icinga.org/icinga.key | apt-key add - +add-apt-repository 'deb http://packages.icinga.org/ubuntu icinga-trusty main' +apt-get update +```` + +For other Ubuntu versions just replace trusty with your distribution's code name. + +**RHEL and CentOS**: +```` +rpm --import http://packages.icinga.org/icinga.key +curl -o /etc/yum.repos.d/ICINGA-release.repo http://packages.icinga.org/epel/ICINGA-release.repo +yum makecache +```` + +**Fedora**: +```` +rpm --import http://packages.icinga.org/icinga.key +curl -o /etc/yum.repos.d/ICINGA-release.repo http://packages.icinga.org/fedora/ICINGA-release.repo +yum makecache +```` + +**SLES 11**: +```` +zypper ar http://packages.icinga.org/SUSE/ICINGA-release-11.repo +zypper ref +```` + +**SLES 12**: +```` +zypper ar http://packages.icinga.org/SUSE/ICINGA-release.repo +zypper ref +```` + +**openSUSE**: +```` +zypper ar http://packages.icinga.org/openSUSE/ICINGA-release.repo +zypper ref +```` + +#### RHEL/CentOS Notes + +The packages for RHEL/CentOS depend on other packages which are distributed as part of the +[EPEL repository](http://fedoraproject.org/wiki/EPEL). Please make sure to enable this repository by following +[these instructions](http://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F). + +> Please note that installing Icinga Web 2 on **RHEL/CentOS 5** is not supported due to EOL versions of PHP and +> PostgreSQL. + +#### Debian wheezy Notes + +The packages for Debian wheezy depend on other packages which are distributed as part of the +[wheezy-packports](http://backports.debian.org/) repository. Please make sure to enable this repository by following +[these instructions](http://backports.debian.org/Instructions/). + +### Installing Icinga Web 2 + +You can install Icinga Web 2 by using your distribution's package manager to install the `icingaweb2` package. +Below is a list with examples for various distributions. The additional package `icingacli` is necessary +for being able to follow further steps in this guide. + +**Debian and Ubuntu**: +```` +apt-get install icingaweb2 icingacli +```` +For Debian wheezy please read the [package repositories notes](#package-repositories-wheezy-notes). + +**RHEL, CentOS and Fedora**: +```` +yum install icingaweb2 icingacli +```` +For RHEL/CentOS please read the [package repositories notes](#package-repositories-rhel-notes). + +**SLES and openSUSE**: +```` +zypper install icingaweb2 icingacli +```` + +### Preparing Web Setup + +You can set up Icinga Web 2 quickly and easily with the Icinga Web 2 setup wizard which is available the first time +you visit Icinga Web 2 in your browser. When using the web setup you are required to authenticate using a token. +In order to generate a token use the `icingacli`: +```` +icingacli setup token create +```` + +In case you do not remember the token you can show it using the `icingacli`: +```` +icingacli setup token show +```` + +Finally visit Icinga Web 2 in your browser to access the setup wizard and complete the installation: +`/icingaweb2/setup`. + +## Installing Icinga Web 2 from Source + +Although the preferred way of installing Icinga Web 2 is to use packages, it is also possible to install Icinga Web 2 +directly from source. + +### Getting the Source First of all, you need to download the sources. Icinga Web 2 is available through a Git repository. You can clone this repository either via git or http protocol using the following URLs: @@ -26,37 +152,147 @@ repository either via git or http protocol using the following URLs: * http://git.icinga.org/icingaweb2.git There is also a browsable version available at -[gi.icinga.org](https://git.icinga.org/?p=icingaweb2.git;a=summary "Icinga Web 2 Git Repository"). +[git.icinga.org](https://git.icinga.org/?p=icingaweb2.git;a=summary "Icinga Web 2 Git Repository"). This version also offers snapshots for easy download which you can use if you do not have git present on your system. ```` git clone git://git.icinga.org/icingaweb2.git ```` -**Step 2: Install the Source** +### Installing Icinga Web 2 Choose a target directory and move Icinga Web 2 there. ```` -mv icingaweb2 /usr/share/icingaweb +mv icingaweb2 /usr/share/icingaweb2 ```` -**Step 3: Configuring the Web Server** +### Configuring the Web Server Use `icingacli` to generate web server configuration for either Apache or nginx. -*Apache* - +**Apache**: ```` -./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb/public +./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public ```` -*nginx* - +**nginx**: ```` -./bin/icingacli setup config webserver nginx --document-root /usr/share/icingaweb/public +./bin/icingacli setup config webserver nginx --document-root /usr/share/icingaweb2/public ```` -**Step 4: Web Setup** +Save the output as new file in your webserver's configuration directory. -Visit Icinga Web 2 in your browser and complete installation using the web setup. +Example for Apache on RHEL or CentOS: +```` +./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public > /etc/httpd/conf.d/icingaweb2.conf +```` + +### Preparing Web Setup + +You can set up Icinga Web 2 quickly and easily with the Icinga Web 2 setup wizard which is available the first time +you visit Icinga Web 2 in your browser. Please follow the steps listed below for preparing the web setup. + +Because both web and CLI must have access to configuration and logs, permissions will be managed using a special +system group. The web server user and CLI user have to be added to this system group. + +Add the system group `icingaweb2` in the first place. + +**Fedora, RHEL, CentOS, SLES and OpenSUSE**: +```` +groupadd -r icingaweb2 +```` + +**Debian and Ubuntu**: +```` +addgroup --system icingaweb2 +```` + +Add your web server's user to the system group `icingaweb2`: + +**Fedora, RHEL and CentOS**: +```` +usermod -a -G icingaweb2 apache +```` + +**SLES and OpenSUSE**: +```` +usermod -A icingaweb2 wwwrun +```` + +**Debian and Ubuntu**: +```` +usermod -a -G icingaweb2 www-data +```` + +Use `icingacli` to create the configuration directory which defaults to **/etc/icingaweb2**: +```` +./bin/icingacli setup config directory +```` + + +When using the web setup you are required to authenticate using a token. In order to generate a token use the +`icingacli`: +```` +./bin/icingacli setup token create +```` + +In case you do not remember the token you can show it using the `icingacli`: +```` +./bin/icingacli setup token show +```` + +Finally visit Icinga Web 2 in your browser to access the setup wizard and complete the installation: +`/icingaweb2/setup`. + +## Upgrading to Icinga Web 2 Beta 2 + +Icinga Web 2 Beta 2 introduces access control based on roles for secured actions. If you've already set up Icinga Web 2, +you are required to create the file **roles.ini** beneath Icinga Web 2's configuration directory with the following +content: +```` +[administrators] +users = "your_user_name, another_user_name" +permissions = "*" +```` + +After please log out from Icinga Web 2 and log in again for having all permissions granted. + +If you delegated authentication to your web server using the `autologin` backend, you have to switch to the `external` +authentication backend to be able to log in again. The new name better reflects what’s going on. A similar change +affects environments that opted for not storing preferences, your new backend is `none`. + +## Upgrading to Icinga Web 2 Beta 3 + +Because Icinga Web 2 Beta 3 does not introduce any backward incompatible change you don't have to change your +configuration files after upgrading to Icinga Web 2 Beta 3. + +## Upgrading to Icinga Web 2 Release Candidate 1 + +The first release candidate of Icinga Web 2 introduces the following non-backward compatible changes: + +* The database schema has been adjusted and the tables `icingaweb_group` and + `icingaweb_group_membership` were altered to ensure referential integrity. + Please use the upgrade script located in **etc/schema/** to update your + database schema +* Users who are using PostgreSQL < v9.1 are required to upgrade their + environment to v9.1+ as this is the new minimum required version + for utilizing PostgreSQL as database backend +* The restrictions `monitoring/hosts/filter` and `monitoring/services/filter` + provided by the monitoring module were merged together. The new + restriction is called `monitoring/filter/objects` and supports only a + predefined subset of filter columns. Please see the module's security + related documentation for more details. + +## Upgrading to Icinga Web 2 2.0.0 + +* Icinga Web 2 installations from package on RHEL/CentOS 7 now depend on `php-ZendFramework` which is available through +the [EPEL repository](http://fedoraproject.org/wiki/EPEL). Before, Zend was installed as Icinga Web 2 vendor library +through the package `icingaweb2-vendor-zend`. After upgrading, please make sure to remove the package +`icingaweb2-vendor-zend`. + +* Icinga Web 2 version 2.0.0 requires permissions for accessing modules. Those permissions are automatically generated +for each installed module in the format `module/`. Administrators have to grant the module permissions to +users and/or user groups in the roles configuration for permitting access to specific modules. +In addition, restrictions provided by modules are now configurable for each installed module too. Before, +a module had to be enabled before having the possibility to configure restrictions. diff --git a/doc/preferences.md b/doc/preferences.md index 0ac15034e..ca26b66b0 100644 --- a/doc/preferences.md +++ b/doc/preferences.md @@ -1,101 +1,53 @@ -# Preferences +# Preferences -Preferences are user based configuration for Icinga Web 2. For example max page -items, languages or date time settings can controlled by users. +Preferences are settings a user can set for his account only, for example his language and time zone. -# Architecture +**Choosing Where to Store Preferences** -Preferences are initially loaded from a provider (ini files or database) and -stored into session at login time. After this step preferences are only -persisted to the configured backend, but never reloaded from them. +Preferences can be stored either in INI files or in a MySQL or in a PostgreSQL database. By default, Icinga Web 2 stores +preferences in INI files beneath Icinga Web 2's configuration directory. -# Configuration +## Configuration -Preferences can be configured in config.ini in **preferences** section, default -settings are this: +Where to store preferences is defined in the INI file **config/config.ini** in the *preferences* section. - [preferences] - type=ini +### Store Preferences in INI Files -The ini provider uses the directory **config/preferences** to create one ini -file per user and persists the data into a single file. If you want to drop your -preferences just drop the file from disk and you'll start with a new profile. +If preferences are stored in INI Files, Icinga Web 2 automatically creates one file per user using the username as +file name for storing preferences. A INI file is created once a user saves changed preferences the first time. +The files are located beneath the `preferences` directory beneath Icinga Web 2's configuration directory. -## Database Provider +For storing preferences in INI files you have to add the following section to the INI file **config/config.ini**: -To be more flexible in distributed setups you can store preferences in a -database (pgsql or mysql), a typical configuration looks like the following -example: +``` +[preferences] +type = ini +```` - [preferences] - type=db - resource=icingaweb-pgsql +### Store Preferences in a Database -## Null Provider +In order to be more flexible in distributed setups you can store preferences in a MySQL or in a PostgreSQL database. +For storing preferences in a database, you have to define a [database resource](resources.md#resources-configuration-database) +which will be referenced as resource for the preferences storage. -The Null Provider discards all preferences and is mainly used as a fallback when no provider could be -created (due to permission errors, database outtakes, etc.). +Directive | Description +------------------------|------------ +**type** | `db` +**resource** | The name of the database resource defined in [resources.ini](resources.md#resources). - [preferences] - type=null +**Example:** -If your preferences aren't stored it's best to take a look into the logfiles - errors during the preference setup -are displayed as warnings here. +``` +[preferences] +type = db +resource = icingaweb-mysql +``` -### Settings +#### Database Setup -* **resource**: A reference to a database declared in *resources.ini*. Please read the chapter about - resources for a detailed description about how to set up resources. +For storing preferences in a database, you have to import one of the following database schemas: -### Preparation +* **etc/schema/preferences.mysql.sql** (for **MySQL** database) +* **etc/schema/preferences.pgsql.sql** (for **PostgreSQL** databases) -To use this feature you need a running database environment. After creating a -database and a writable user you need to import the initial table file: - -* etc/schema/preferences.mysql.sql (for mysql database) -* etc/schema/preferemces.pgsql.sql (for postgres databases) - -#### Example for mysql - - # mysql -u root -p - mysql> create database icingaweb; - mysql> GRANT SELECT,INSERT,UPDATE,DELETE ON icingaweb.* TO \ - 'icingaweb'@'localhost' IDENTIFIED BY 'icingaweb'; - mysql> exit - # mysql -u root -p icingaweb < /path/to/icingaweb/etc/schema/preferences.mysql.sql - -After following these steps above you can configure your preferences provider. - -## Coding API - -You can set, update or remove preferences using the Preference data object -which is bound to the user. Here are some simple examples how to work with -that: - - $preferences = $user->getPreferences(); - // Get language with en_US as fallback - $preferences->get('app.language', 'en_US'); - $preferences->set('app.language', 'de_DE'); - $preferences->remove('app.language'); - - // Using transactional mode - $preferences->startTransaction(); - $preferences->set('test.pref1', 'pref1'); - $preferences->set('test.pref2', 'pref2'); - $preferences->remove('test.pref3'); - $preferemces->commit(); // Stores 3 changes in one operation - -More information can be found in the api docs. - -## Namespaces and behaviour - -If you are using this API please obey the following rules: - -* Use dotted notation for preferences -* Namespaces starting with one context identifier - * **app** as global identified (e.g. app.language) - * **mymodule** for your module - * **monitoring** for the monitoring module -* Use preferences wisely (set only when needed and write small settings) -* Use only simple data types, e.g. strings or numbers - * If you need complex types you have to do it your self (e.g. serialization) +After that you have to define the [database resource](resources.md#resources-configuration-database). diff --git a/doc/res/Dashboard.graphml b/doc/res/Dashboard.graphml deleted file mode 100644 index eed85e46d..000000000 --- a/doc/res/Dashboard.graphml +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - Dashboard - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Panes - - - - - - - - - - - - - - - - - - Dashboard - - - - - - - - - - - - - - - - - - Components - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/res/Dashboard.png b/doc/res/Dashboard.png deleted file mode 100644 index e78d45132..000000000 Binary files a/doc/res/Dashboard.png and /dev/null differ diff --git a/doc/res/Form.png b/doc/res/Form.png deleted file mode 100644 index 388f80acb..000000000 Binary files a/doc/res/Form.png and /dev/null differ diff --git a/doc/res/Form.violet b/doc/res/Form.violet deleted file mode 100644 index 20a083a3f..000000000 --- a/doc/res/Form.violet +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - init() -isValid() - - - - - - Zend_Form - - - - - - x - - - 50.0 - - - - y - - - 70.0 - - - - 50.0 - 70.0 - - - - - - - - create(): void -preValid(data: array): void -postValid(data: array, isValid: boolean): void -isValid(data: mixed): boolean -setRequest(request: Zend_Controller_Request_Abstract) -getRequest(): Zend_Controller_Request_Abstract -buildForm(): void -initCsrfToken(): void - - - - - Icinga\Web\Form - - - - - - x - - - 310.0 - - - - y - - - 30.0 - - - - 310.0 - 30.0 - - - - - - - - - - - - - - - - - diff --git a/doc/resources.md b/doc/resources.md index 29a35c30d..a2bfb66af 100644 --- a/doc/resources.md +++ b/doc/resources.md @@ -8,7 +8,7 @@ different files, when the information about a data source changes. Each section in **config/resources.ini** represents a data source with the section name being the identifier used to reference this specific data source. Depending on the data source type, the sections define different directives. -The available data source types are *db*, *ldap* and *livestatus* which will described in detail in the following +The available data source types are *db*, *ldap*, *ssh* and *livestatus* which will described in detail in the following paragraphs. ### Database @@ -64,6 +64,26 @@ bind_dn = "cn=admin,ou=people,dc=icinga,dc=org" bind_pw = admin` ```` +### SSH + +A SSH resource contains the information about the user and the private key location, which can be used for the key-based +ssh authentication. + +Directive | Description +--------------------|------------ +**type** | `ssh` +**user** | The username to use when connecting to the server. +**private_key** | The path to the private key of the user. + +**Example:** + +```` +[ssh] +type = "ssh" +user = "ssh-user" +private_key = "/etc/icingaweb2/ssh/ssh-user" +```` + ### Livestatus A Livestatus resource represents the location of a Livestatus socket which is used for fetching monitoring data. diff --git a/doc/security.md b/doc/security.md new file mode 100644 index 000000000..3b8731a7d --- /dev/null +++ b/doc/security.md @@ -0,0 +1,262 @@ +# Security + +Access control is a vital part of configuring Icinga Web 2 in a secure way. +It is important that not every user that has access to Icinga Web 2 is able +to do any action or to see any host and service. For example, it is useful to allow +only a small group of administrators to change the Icinga Web 2 configuration, +to prevent misconfiguration or security breaches. Another important use case is +creating groups of users which can only see the fraction of the monitoring +environment they are in charge of. + +This chapter will describe how to do the security configuration of Icinga Web 2 +and how to apply permissions and restrictions to users or groups of users. + +## Basics + +Icinga Web 2 access control is done by defining **roles** that associate permissions +and restrictions with **users** and **groups**. There are two general kinds of +things to which access can be managed: actions and objects. + + +### Actions + +Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration, +changing permissions or sending a command to the Icinga instance through the +Command Pipe +in the monitoring module. All actions must be be **allowed explicitly** using permissions. + +A permission is a simple list of identifiers of actions a user is +allowed to do. Permissions are described in greater detail in the +section [Permissions](#permissions). + +### Objects + +There are all kinds of different objects in Icinga Web 2: Hosts, Services, Notifications, Downtimes and Events. + +By default, a user can **see everything**, but it is possible to **explicitly restrict** what each user can see using restrictions. + +Restrictions are complex filter queries that describe what objects should be displayed to a user. Restrictions are described +in greater detail in the section [Restrictions](#restrictions). + +### Users + +Anyone who can **login** to Icinga Web 2 is considered a user and can be referenced to by the +**user name** used during login. +For example, there might be user called **jdoe** authenticated +using Active Directory, and a user **icingaadmin** that is authenticated using a MySQL-Database as backend. +In the configuration, both can be referenced to by using their user names **icingaadmin** or **jdoe**. + +Icinga Web 2 users and groups are not configured by a configuration file, but provided by +an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [Authentication](authentication.md#authentication). + + +
    + Since Icinga Web 2, users in the Icinga configuration and the web authentication are separated, to allow + use of external authentication providers. This means that users and groups defined in the Icinga configuration are not available to Icinga Web 2. Instead it uses its own authentication + backend to fetch users and groups from, which must be configured separately. +
    + +#### Managing Users + +When using a [Database +as authentication backend](authentication.md#authentication-configuration-db-authentication), it is possible to create, add and delete users directly in the frontend. This configuration +can be found at **Configuration > Authentication > Users **. + +### Groups + +If there is a big amount of users to manage, it would be tedious to specify each user +separately when regularly referring to the same group of users. Because of that, it is possible to group users. +A user can be member of multiple groups and will inherit all permissions and restrictions. + +Like users, groups are identified solely by their **name** that is provided by + a **group backend**. For extended information on setting up group backends, + please read the chapter [Authentication](authentication.md#authentication). + + +#### Managing Groups + +When using a [Database as an authentication backend](#authentication.md#authentication-configuration-db-authentication), +it is possible to manage groups and group memberships directly in the frontend. This configuration +can be found at **Configuration > Authentication > Groups **. + +## Roles + +A role defines a set of **permissions** and **restrictions** and assigns +those to **users** and **groups**. For example, a role **admins** could define that certain +users have access to all configuration options, or another role **support** +could define that a list of users or groups is restricted to see only hosts and services +that match a specific query. + +The actual permission of a certain user will be determined by merging the permissions +and restrictions of the user itself and all the groups the user is member of. Permissions can +be simply added up, while restrictions follow a slighty more complex pattern, that is described +in the section [Stacking Filters](#stacking-filters). + +### Configuration + +Roles can be changed either through the icingaweb2 interface, by navigation +to the page **Configuration > Authentication > Roles**, or through editing the +configuration file: + + + /etc/icingaweb2/roles.ini + + +#### Introducing Example + +To get you a quick start, here is an example of what a role definition could look like: + + + [winadmin] + users = "jdoe, janedoe" + groups = "admin" + permissions = "config/*, monitoring/commands/schedule-check" + monitoring/filter/objects = "host_name=*win*" + + +This example creates a role called **winadmin**, that grants all permissions in `config/*` and `monitoring/commands/schedule-check` and additionally only +allows the hosts and services that match the filter `host_name=*win*` to be displayed. The users +**jdoe** and **janedoe** and all members of the group **admin** will be affected +by this role. + + +#### Syntax + +Each role is defined as a section, with the name of the role as section name. The following +attributes can be defined for each role in a default Icinga Web 2 installation: + + + Directive | Description +---------------------------|----------------------------------------------------------------------------- + users | A comma-separated list of user **user names** that are affected by this role + groups | A comma-separated list of **group names** that are affected by this role + permissions | A comma-separated list of **permissions** granted by this role + monitoring/filter/objects | A **filter expression** that restricts the access to services and hosts + + + +## Permissions + +Permissions can be used to allow users or groups certain **actions**. By default, +all actions are **prohibited** and must be allowed explicitly by a role for any user. + +Each action in Icinga Web 2 is denoted by a **namespaced key**, which is used to order and +group those actions. All actions that affect the configuration of Icinga Web 2, are in a +namespace called **config**, while all configurations that affect modules +are in the namespace `config/modules` + +**Wildcards** can be used to grant permission for all actions in a certain namespace. +The permission `config/*` would grant permission to all configuration actions, +while just specifying a wildcard `*` would give permission for all actions. + +Access to modules is restricted to users who have the related module permission granted. Icinga Web 2 provides +a module permission in the format `module/` for each installed module. + +When multiple roles assign permissions to the same user (either directly or indirectly +through a group) all permissions are added together to get the users actual permission set. + +### Global Permissions + +Name | Permits +--------------- ----|-------------------------------------------------------- +* | Allow everything, including module-specific permissions +config/* | Allow all configuration actions +config/modules | Allow enabling or disabling modules +module/ | Allow access to module + + +### Monitoring Module Permissions + +The built-in monitoring module defines an additional set of permissions, that +is described in detail in the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security). + + +## Restrictions + +Restrictions can be used to define what a user or group can see by specifying +a filter expression that applies to a defined set of data. By default, when no +restrictions are defined, a user will be able to see every information that is available. + +A restrictions is always specified for a certain **filter directive**, that defines what +data the filter is applied to. The **filter directive** is a simple identifier, that was +defined in an Icinga Web 2 module. The only filter directive that is available +in a default installation, is the `monitoring/filter/objects` directive, defined by the monitoring module, +that can be used to apply filter to hosts and services. This directive was previously +mentioned in the section [Syntax](#syntax). + +### Filter Expressions + +Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in +the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security-restrictions). + +Any filter expression that is allowed in the filtered view, is also an allowed filter expression. +This means, that it is possible to define negations, wildcards, and even nested +filter expressions containing AND and OR-Clauses. + +The filter expression will be **implicitly** added as an **AND-Clause** to each query on +the filtered data. The following shows the filter expression `host_name=*win*` being applied on `monitoring/filter/objects`. + + +Regular filter query: + + AND-- service_problem = 1 + | + +--- service_handled = 0 + + +With our restriction applied, any user affected by this restrictions will see the +results of this query instead: + + + AND-- host_name = *win* + | + +--AND-- service_problem = 1 + | + +--- service_handled = 0 + + +#### Stacking Filters + +When multiple roles assign restrictions to the same user, either directly or indirectly +through a group, all filters will be combined using an **OR-Clause**, resulting in the final +expression: + + + AND-- OR-- $FILTER1 + | | + | +-- $FILTER2 + | | + | +-- $FILTER3 + | + +--AND-- service_problem = 1 + | + +--- service_handled = 0 + + +As a result, a user is be able to see hosts that are matched by **ANY** of +the filter expressions. The following examples will show the usefulness of this behavior: + +#### Example 1: Negation + + [winadmin] + groups = "windows-admins" + monitoring/filter/objects = "host_name=*win*" + +Will display only hosts and services whose host name contains **win**. + + [webadmin] + groups = "web-admins" + monitoring/filter/objects = "host_name!=*win*" + +Will only match hosts and services whose host name does **not** contain **win** + +Notice that because of the behavior of two stacking filters, a user that is member of **windows-admins** and **web-admins**, will now be able to see both, Windows and non-Windows hosts and services. + +#### Example 2: Hostgroups + + [unix-server] + groups = "unix-admins" + monitoring/filter/objects = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)" + +This role allows all members of the group unix-admins to see hosts and services +that are part of the host-group linux-servers or the host-group bsd-servers. diff --git a/doc/vagrant.md b/doc/vagrant.md new file mode 100644 index 000000000..b1b98b436 --- /dev/null +++ b/doc/vagrant.md @@ -0,0 +1,52 @@ +# Vagrant + +## Requirements + +* Vagrant >= version 1.5 +* VirtualBox or Parallels + +> **Note:** The deployment of the virtual machine is tested against Vagrant starting with version 1.5. +> Unfortunately older versions will not work. + +## General + +The Icinga Web 2 project ships with a Vagrant virtual machine that integrates +the source code with various services and example data in a controlled +environment. This enables developers and users to test Livestatus, +MySQL and PostgreSQL backends as well as the LDAP authentication. All you +have to do is install Vagrant and run: + +```` +vagrant up +```` + +> **Note:** The first boot of the vm takes a fairly long time because +> you'll download a plain CentOS base box and Vagrant will automatically +> provision the environment on the first go. + +After you should be able to browse [localhost:8080/icingaweb2](http://localhost:8080/icingaweb2). + +## Log into Icinga Web 2 + +Both LDAP and a MySQL are configured as authentication backend. Please use one of the following login credentials: + +> LDAP: +>> **Username**: `jdoe` + +>> **Password**: `password` + +>MySQL: +>> **Username**: `icingaadmin` + +>> **Password**: `icinga` + + + +## Testing the Source Code + +All software required to run tests is installed in the virtual machine. +In order to run all tests you have to execute the following command: + +```` +vagrant ssh -c "icingacli test php unit" +```` diff --git a/etc/bash_completion.d/icingacli b/etc/bash_completion.d/icingacli index 74e779218..f9be7bcf9 100644 --- a/etc/bash_completion.d/icingacli +++ b/etc/bash_completion.d/icingacli @@ -1,4 +1,3 @@ - _icingacli_completion() { local cur opts @@ -9,5 +8,3 @@ _icingacli_completion() } complete -F _icingacli_completion icingacli - - diff --git a/etc/license_header.txt b/etc/license_header.txt deleted file mode 100644 index db19a8436..000000000 --- a/etc/license_header.txt +++ /dev/null @@ -1,5 +0,0 @@ -Icinga Web 2 - -@link https://www.icinga.org/icingaweb2/ -@copyright Copyright (c) 2013-%(YEAR)s Icinga Development Team (https://www.icinga.org) -@license http://www.gnu.org/licenses/gpl-2.0.txt, or any later version \ No newline at end of file diff --git a/etc/schema/mysql-upgrades/2.0.0beta3-2.0.0rc1.sql b/etc/schema/mysql-upgrades/2.0.0beta3-2.0.0rc1.sql new file mode 100644 index 000000000..630dc6d57 --- /dev/null +++ b/etc/schema/mysql-upgrades/2.0.0beta3-2.0.0rc1.sql @@ -0,0 +1,26 @@ +# Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ + +DROP TABLE `icingaweb_group_membership`; +DROP TABLE `icingaweb_group`; + +CREATE TABLE `icingaweb_group`( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `parent` int(10) unsigned NULL DEFAULT NULL, + `ctime` timestamp NULL DEFAULT NULL, + `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_name` (`name`), + CONSTRAINT `fk_icingaweb_group_parent_id` FOREIGN KEY (`parent`) + REFERENCES `icingaweb_group` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `icingaweb_group_membership`( + `group_id` int(10) unsigned NOT NULL, + `username` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `ctime` timestamp NULL DEFAULT NULL, + `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`group_id`,`username`), + CONSTRAINT `fk_icingaweb_group_membership_icingaweb_group` FOREIGN KEY (`group_id`) + REFERENCES `icingaweb_group` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/etc/schema/mysql.schema.sql b/etc/schema/mysql.schema.sql index 74d2665d4..5f22aead3 100644 --- a/etc/schema/mysql.schema.sql +++ b/etc/schema/mysql.schema.sql @@ -1,19 +1,25 @@ +# Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ + CREATE TABLE `icingaweb_group`( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL, - `parent` varchar(64) COLLATE utf8_unicode_ci NULL DEFAULT NULL, + `parent` int(10) unsigned NULL DEFAULT NULL, `ctime` timestamp NULL DEFAULT NULL, `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`name`) + PRIMARY KEY (`id`), + UNIQUE KEY `idx_name` (`name`), + CONSTRAINT `fk_icingaweb_group_parent_id` FOREIGN KEY (`parent`) + REFERENCES `icingaweb_group` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `icingaweb_group_membership`( - `group_name` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `group_id` int(10) unsigned NOT NULL, `username` varchar(64) COLLATE utf8_unicode_ci NOT NULL, `ctime` timestamp NULL DEFAULT NULL, `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`group_name`,`username`), - CONSTRAINT `fk_icingaweb_group_membership_icingaweb_group` FOREIGN KEY (`group_name`) - REFERENCES `icingaweb_group` (`name`) + PRIMARY KEY (`group_id`,`username`), + CONSTRAINT `fk_icingaweb_group_membership_icingaweb_group` FOREIGN KEY (`group_id`) + REFERENCES `icingaweb_group` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `icingaweb_user`( diff --git a/etc/schema/pgsql-upgrades/2.0.0beta3-2.0.0rc1.sql b/etc/schema/pgsql-upgrades/2.0.0beta3-2.0.0rc1.sql new file mode 100644 index 000000000..ba6d3b149 --- /dev/null +++ b/etc/schema/pgsql-upgrades/2.0.0beta3-2.0.0rc1.sql @@ -0,0 +1,60 @@ +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +DROP TABLE "icingaweb_group_membership"; +DROP TABLE "icingaweb_group"; + +CREATE OR REPLACE FUNCTION unix_timestamp(timestamp with time zone) RETURNS bigint AS ' + SELECT EXTRACT(EPOCH FROM $1)::bigint AS result +' LANGUAGE sql; + +CREATE TABLE "icingaweb_group" ( + "id" serial, + "name" character varying(64) NOT NULL, + "parent" int NULL DEFAULT NULL, + "ctime" timestamp NULL DEFAULT NULL, + "mtime" timestamp NULL DEFAULT NULL +); + +ALTER TABLE ONLY "icingaweb_group" + ADD CONSTRAINT pk_icingaweb_group + PRIMARY KEY ( + "id" +); + +CREATE UNIQUE INDEX idx_icingaweb_group + ON "icingaweb_group" + USING btree ( + lower((name)::text) +); + +ALTER TABLE ONLY "icingaweb_group" + ADD CONSTRAINT fk_icingaweb_group_parent_id + FOREIGN KEY ( + "parent" + ) + REFERENCES "icingaweb_group" ( + "id" +); + +CREATE TABLE "icingaweb_group_membership" ( + "group_id" int NOT NULL, + "username" character varying(64) NOT NULL, + "ctime" timestamp NULL DEFAULT NULL, + "mtime" timestamp NULL DEFAULT NULL +); + +ALTER TABLE ONLY "icingaweb_group_membership" + ADD CONSTRAINT pk_icingaweb_group_membership + FOREIGN KEY ( + "group_id" + ) + REFERENCES "icingaweb_group" ( + "id" +); + +CREATE UNIQUE INDEX idx_icingaweb_group_membership + ON "icingaweb_group_membership" + USING btree ( + group_id, + lower((username)::text) +); diff --git a/etc/schema/pgsql.schema.sql b/etc/schema/pgsql.schema.sql index 77971222b..56117d4f8 100644 --- a/etc/schema/pgsql.schema.sql +++ b/etc/schema/pgsql.schema.sql @@ -1,9 +1,13 @@ -/** - * Table "icingaweb_group" - */ +/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +CREATE OR REPLACE FUNCTION unix_timestamp(timestamp with time zone) RETURNS bigint AS ' + SELECT EXTRACT(EPOCH FROM $1)::bigint AS result +' LANGUAGE sql; + CREATE TABLE "icingaweb_group" ( + "id" serial, "name" character varying(64) NOT NULL, - "parent" character varying(64) NULL DEFAULT NULL, + "parent" int NULL DEFAULT NULL, "ctime" timestamp NULL DEFAULT NULL, "mtime" timestamp NULL DEFAULT NULL ); @@ -11,7 +15,7 @@ CREATE TABLE "icingaweb_group" ( ALTER TABLE ONLY "icingaweb_group" ADD CONSTRAINT pk_icingaweb_group PRIMARY KEY ( - "name" + "id" ); CREATE UNIQUE INDEX idx_icingaweb_group @@ -20,12 +24,17 @@ CREATE UNIQUE INDEX idx_icingaweb_group lower((name)::text) ); +ALTER TABLE ONLY "icingaweb_group" + ADD CONSTRAINT fk_icingaweb_group_parent_id + FOREIGN KEY ( + "parent" + ) + REFERENCES "icingaweb_group" ( + "id" +); -/** - * Table "icingaweb_group_membership" - */ CREATE TABLE "icingaweb_group_membership" ( - "group_name" character varying(64) NOT NULL, + "group_id" int NOT NULL, "username" character varying(64) NOT NULL, "ctime" timestamp NULL DEFAULT NULL, "mtime" timestamp NULL DEFAULT NULL @@ -33,22 +42,20 @@ CREATE TABLE "icingaweb_group_membership" ( ALTER TABLE ONLY "icingaweb_group_membership" ADD CONSTRAINT pk_icingaweb_group_membership - PRIMARY KEY ( - "group_name", - "username" + FOREIGN KEY ( + "group_id" + ) + REFERENCES "icingaweb_group" ( + "id" ); CREATE UNIQUE INDEX idx_icingaweb_group_membership ON "icingaweb_group_membership" USING btree ( - lower((group_name)::text), + group_id, lower((username)::text) ); - -/** - * Table "icingaweb_user" - */ CREATE TABLE "icingaweb_user" ( "name" character varying(64) NOT NULL, "active" smallint NOT NULL, @@ -69,10 +76,6 @@ CREATE UNIQUE INDEX idx_icingaweb_user lower((name)::text) ); - -/** - * Table "icingaweb_user_preference" - */ CREATE TABLE "icingaweb_user_preference" ( "username" character varying(64) NOT NULL, "name" character varying(64) NOT NULL, @@ -96,4 +99,4 @@ CREATE UNIQUE INDEX idx_icingaweb_user_preference lower((username)::text), lower((section)::text), lower((name)::text) -); \ No newline at end of file +); diff --git a/icingaweb2.spec b/icingaweb2.spec index fb628f995..3ea135589 100644 --- a/icingaweb2.spec +++ b/icingaweb2.spec @@ -1,237 +1,267 @@ -#/** -# * This file is part of Icinga Web 2. -# * -# * Icinga Web 2 - Head for multiple monitoring backends. -# * Copyright (C) 2014 Icinga Development Team -# * -# * This program is free software; you can redistribute it and/or -# * modify it under the terms of the GNU General Public License -# * as published by the Free Software Foundation; either version 2 -# * of the License, or (at your option) any later version. -# * -# * This program is distributed in the hope that it will be useful, -# * but WITHOUT ANY WARRANTY; without even the implied warranty of -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# * GNU General Public License for more details. -# * -# * You should have received a copy of the GNU General Public License -# * along with this program; if not, write to the Free Software -# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# * -# * @copyright 2014 Icinga Development Team -# * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 -# * @author Icinga Development Team -# * -# */ +# Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ -%define revision 1 +%define revision 4.rc1 -%define configdir %{_sysconfdir}/%{name} -%define sharedir %{_datadir}/%{name} -%define prefixdir %{_datadir}/%{name} -%define usermodparam -a -G -%define logdir %{_localstatedir}/log/%{name} -%define docdir %{sharedir}/doc - -%if "%{_vendor}" == "suse" -%define phpname php5 -%define phpzendname php5-ZendFramework -%define apache2modphpname apache2-mod_php5 -%endif -# SLE 11 = 1110 -%if 0%{?suse_version} == 1110 -%define phpname php53 -%define apache2modphpname apache2-mod_php53 -%define usermodparam -A -%endif - -%if "%{_vendor}" == "redhat" -%define phpname php -%define phpzendname php-ZendFramework -%endif - -# el5 requires newer php53 rather than php (5.1) -%if 0%{?el5} || 0%{?rhel} == 5 || "%{?dist}" == ".el5" -%define phpname php53 -%endif - -%if "%{_vendor}" == "suse" -%define apacheconfdir %{_sysconfdir}/apache2/conf.d -%define apacheuser wwwrun -%define apachegroup www -%define extcmdfile %{_localstatedir}/run/icinga2/cmd/icinga.cmd -%define livestatussocket %{_localstatedir}/run/icinga2/cmd/livestatus -%endif -%if "%{_vendor}" == "redhat" -%define apacheconfdir %{_sysconfdir}/httpd/conf.d -%define apacheuser apache -%define apachegroup apache -%define extcmdfile %{_localstatedir}/run/icinga2/cmd/icinga.cmd -%define livestatussocket %{_localstatedir}/run/icinga2/cmd/livestatus -%endif - -Summary: Open Source host, service and network monitoring Web UI Name: icingaweb2 -Version: 0.0.1 +Version: 2.0.0 Release: %{revision}%{?dist} -License: GPLv2 +Summary: Icinga Web 2 Group: Applications/System -URL: http://www.icinga.org +License: GPLv2+ and MIT and BSD +URL: https://icinga.org +Source0: https://github.com/Icinga/%{name}/archive/v%{version}.tar.gz BuildArch: noarch +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release} +Packager: Icinga Team -%if "%{_vendor}" == "suse" -AutoReqProv: Off +%if 0%{?fedora} || 0%{?rhel} || 0%{?amzn} +%define php php +%define php_cli php-cli +%define wwwconfigdir %{_sysconfdir}/httpd/conf.d +%define wwwuser apache +%define zend php-ZendFramework %endif -Source: icingaweb2-%{version}.tar.gz - -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root - -BuildRequires: %{phpname} >= 5.3.0 -BuildRequires: %{phpname}-devel >= 5.3.0 -BuildRequires: %{phpname}-ldap -BuildRequires: %{phpname}-pdo -BuildRequires: %{phpzendname} -%if "%{_vendor}" != "suse" -BuildRequires: %{phpzendname}-Db-Adapter-Pdo -BuildRequires: %{phpzendname}-Db-Adapter-Pdo-Mysql -BuildRequires: %{phpzendname}-Db-Adapter-Pdo-Pgsql +%if 0%{?suse_version} +%define wwwconfigdir %{_sysconfdir}/apache2/conf.d +%define wwwuser wwwrun +%define zend php5-ZendFramework +%if 0%{?suse_version} == 1110 +%define php php53 +Requires: apache2-mod_php53 +%else +%define php php5 +Requires: apache2-mod_php5 +%endif %endif -%if "%{_vendor}" == "redhat" -%endif -%if "%{_vendor}" == "suse" -Requires: %{phpname}-devel >= 5.3.0 -BuildRequires: %{phpname}-json -BuildRequires: %{phpname}-sockets -BuildRequires: %{phpname}-dom -%endif - -Requires: %{phpname} >= 5.3.0 -Requires: %{phpzendname} -Requires: %{phpname}-ldap -Requires: %{phpname}-pdo -%if "%{_vendor}" == "redhat" -Requires: %{phpname}-common -Requires: %{phpzendname}-Db-Adapter-Pdo -Requires: %{phpzendname}-Db-Adapter-Pdo-Mysql -Requires: php-pear -%endif -%if "%{_vendor}" == "suse" -Requires: %{phpname}-pear -Requires: %{phpname}-dom -Requires: %{phpname}-tokenizer -Requires: %{phpname}-gettext -Requires: %{phpname}-ctype -Requires: %{phpname}-json -Requires: %{apache2modphpname} -%endif - -Requires: php-Icinga +%{?amzn:Requires(pre): shadow-utils} +%{?fedora:Requires(pre): shadow-utils} +%{?rhel:Requires(pre): shadow-utils} +%{?suse_version:Requires(pre): pwdutils} +Requires: %{name}-common = %{version}-%{release} +Requires: php-Icinga = %{version}-%{release} +Requires: %{name}-vendor-dompdf +Requires: %{name}-vendor-HTMLPurifier +Requires: %{name}-vendor-JShrink +Requires: %{name}-vendor-lessphp +Requires: %{name}-vendor-Parsedown %description -Icinga Web 2 for Icinga 2 or Icinga 1.x using multiple backends -for example DB IDO. +Icinga Web 2 -%package -n icingacli -Summary: Icinga CLI -Group: Applications/System -Requires: %{name} = %{version}-%{release} -Requires: php-Icinga -%description -n icingacli -Icinga CLI using php-Icinga Icinga Web 2 backend. +%define basedir %{_datadir}/%{name} +%define bindir %{_bindir} +%define configdir %{_sysconfdir}/%{name} +%define logdir %{_localstatedir}/log/%{name} +%define phpdir %{_datadir}/php +%define icingawebgroup icingaweb2 +%define docsdir %{_datadir}/doc/%{name} + + +%package common +Summary: Common files for Icinga Web 2 and the Icinga CLI +Group: Applications/System +%{?amzn:Requires(pre): shadow-utils} +%{?fedora:Requires(pre): shadow-utils} +%{?rhel:Requires(pre): shadow-utils} +%{?suse_version:Requires(pre): pwdutils} + +%description common +Common files for Icinga Web 2 and the Icinga CLI + %package -n php-Icinga -Summary: Icinga Web 2 PHP Libraries -Group: Applications/System -Requires: %{name} = %{version}-%{release} -Requires: %{phpname} >= 5.3.0 -Requires: %{phpzendname} - +Summary: Icinga Web 2 PHP library +Group: Development/Libraries +Requires: %{php} >= 5.3.0 +Requires: %{php}-gd %{php}-intl +%{?amzn:Requires: %{php}-pecl-imagick} +%{?fedora:Requires: php-pecl-imagick} +%{?rhel:Requires: php-pecl-imagick} +%{?suse_version:Requires: %{php}-gettext %{php}-json %{php}-openssl %{php}-posix} +Requires: %{zend} +Obsoletes: %{name}-vendor-zend +Requires: %{zend}-Db-Adapter-Pdo-Mysql +Requires: %{zend}-Db-Adapter-Pdo-Pgsql %description -n php-Icinga -Icinga Web 2 PHP Libraries required by the web frontend and cli tool. +Icinga Web 2 PHP library + + +%package -n icingacli +Summary: Icinga CLI +Group: Applications/System +Requires: %{name}-common = %{version}-%{release} +Requires: php-Icinga = %{version}-%{release} +%{?amzn:Requires: %{php_cli} >= 5.3.0 bash-completion} +%{?fedora:Requires: %{php_cli} >= 5.3.0 bash-completion} +%{?rhel:Requires: %{php_cli} >= 5.3.0 bash-completion} +%{?suse_version:Requires: %{php} >= 5.3.0} + +%description -n icingacli +Icinga CLI + + +%package vendor-dompdf +Version: 0.6.1 +Release: 1%{?dist} +Summary: Icinga Web 2 vendor library dompdf +Group: Development/Libraries +License: LGPLv2.1 +Requires: %{php} >= 5.3.0 + +%description vendor-dompdf +Icinga Web 2 vendor library dompdf + + +%package vendor-HTMLPurifier +Version: 4.6.0 +Release: 1%{?dist} +Summary: Icinga Web 2 vendor library HTMLPurifier +Group: Development/Libraries +License: LGPLv2.1 +Requires: %{php} >= 5.3.0 + +%description vendor-HTMLPurifier +Icinga Web 2 vendor library HTMLPurifier + + +%package vendor-JShrink +Version: 1.0.1 +Release: 1%{?dist} +Summary: Icinga Web 2 vendor library JShrink +Group: Development/Libraries +License: BSD +Requires: %{php} >= 5.3.0 + +%description vendor-JShrink +Icinga Web 2 vendor library JShrink + + +%package vendor-lessphp +Version: 0.4.0 +Release: 1%{?dist} +Summary: Icinga Web 2 vendor library lessphp +Group: Development/Libraries +License: MIT +Requires: %{php} >= 5.3.0 + +%description vendor-lessphp +Icinga Web 2 vendor library lessphp + + +%package vendor-Parsedown +Version: 1.0.0 +Release: 1%{?dist} +Summary: Icinga Web 2 vendor library Parsedown +Group: Development/Libraries +License: MIT +Requires: %{php} >= 5.3.0 + +%description vendor-Parsedown +Icinga Web 2 vendor library Parsedown %prep -#VERSION=0.0.1; git archive --format=tar --prefix=icingaweb2-$VERSION/ HEAD | gzip >icingaweb2-$VERSION.tar.gz -%setup -q -n %{name}-%{version} +%setup -q %build %install -[ "%{buildroot}" != "/" ] && [ -d "%{buildroot}" ] && rm -rf %{buildroot} - -# prepare configuration for sub packages - -# install rhel apache config -install -D -m0644 packages/files/apache/icingaweb.conf %{buildroot}/%{apacheconfdir}/icingaweb.conf - -# install public, library, modules -%{__mkdir} -p %{buildroot}/%{sharedir} -%{__mkdir} -p %{buildroot}/%{logdir} -%{__mkdir} -p %{buildroot}/%{docdir} -%{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name} -%{__mkdir} -p %{buildroot}/%{_sysconfdir}/dashboard -%{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/modules -%{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/modules/monitoring -%{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/enabledModules - -# make sure to install local icingacli for setup wizard token generation & webserver config -%{__cp} -r application doc library modules public bin %{buildroot}/%{sharedir}/ - -# enable the monitoring module by default -ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/%{name}/enabledModules/monitoring -## config - -# symlink icingacli -mkdir -p %{buildroot}/usr/bin -ln -sf %{sharedir}/bin/icingacli %{buildroot}/usr/bin/icingacli +rm -rf %{buildroot} +mkdir -p %{buildroot}/{%{basedir}/{modules,library/vendor,public},%{bindir},%{configdir}/modules,%{logdir},%{phpdir},%{wwwconfigdir},%{_sysconfdir}/bash_completion.d,%{docsdir}} +cp -prv application doc %{buildroot}/%{basedir} +cp -pv etc/bash_completion.d/icingacli %{buildroot}/%{_sysconfdir}/bash_completion.d/icingacli +cp -prv modules/{monitoring,setup,doc,translation} %{buildroot}/%{basedir}/modules +cp -prv library/Icinga %{buildroot}/%{phpdir} +cp -prv library/vendor/{dompdf,HTMLPurifier,JShrink,lessphp,Parsedown} %{buildroot}/%{basedir}/library/vendor +cp -prv public/{css,img,js,error_norewrite.html} %{buildroot}/%{basedir}/public +cp -pv packages/files/apache/icingaweb2.conf %{buildroot}/%{wwwconfigdir}/icingaweb2.conf +cp -pv packages/files/bin/icingacli %{buildroot}/%{bindir} +cp -pv packages/files/public/index.php %{buildroot}/%{basedir}/public +cp -prv etc/schema %{buildroot}/%{docsdir} +cp -prv packages/files/config/modules/{setup,translation} %{buildroot}/%{configdir}/modules %pre -# Add apacheuser in the icingacmd group -# If the group exists, add the apacheuser in the icingacmd group. -# It is not neccessary that icinga2-web is installed on the same system as -# icinga and only on systems with icinga installed the icingacmd -# group exists. In all other cases the user used for ssh access has -# to be added to the icingacmd group on the remote icinga server. -getent group icingacmd > /dev/null - -if [ $? -eq 0 ]; then -%{_sbindir}/usermod %{usermodparam} icingacmd %{apacheuser} -fi - -%preun - -%post +getent group icingacmd >/dev/null || groupadd -r icingacmd +%if 0%{?suse_version} && 0%{?suse_version} < 01200 +usermod -A icingacmd,%{icingawebgroup} %{wwwuser} +%else +usermod -a -G icingacmd,%{icingawebgroup} %{wwwuser} +%endif +exit 0 %clean -[ "%{buildroot}" != "/" ] && [ -d "%{buildroot}" ] && rm -rf %{buildroot} +rm -rf %{buildroot} %files -# main dirs %defattr(-,root,root) -%doc etc/schema doc packages/RPM.md -%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/public -%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/modules -# configs +%{basedir}/application/controllers +%{basedir}/application/fonts +%{basedir}/application/forms +%{basedir}/application/layouts +%{basedir}/application/views +%{basedir}/application/VERSION +%{basedir}/doc +%{basedir}/modules +%{basedir}/public +%config(noreplace) %{wwwconfigdir}/icingaweb2.conf +%attr(2775,root,%{icingawebgroup}) %dir %{logdir} +%attr(2770,root,%{icingawebgroup}) %config(noreplace) %dir %{configdir}/modules/setup +%attr(0660,root,%{icingawebgroup}) %config(noreplace) %{configdir}/modules/setup/config.ini +%attr(2770,root,%{icingawebgroup}) %config(noreplace) %dir %{configdir}/modules/translation +%attr(0660,root,%{icingawebgroup}) %config(noreplace) %{configdir}/modules/translation/config.ini +%{docsdir} +%docdir %{docsdir} + + +%pre common +getent group %{icingawebgroup} >/dev/null || groupadd -r %{icingawebgroup} +exit 0 + +%files common %defattr(-,root,root) -%config(noreplace) %attr(-,root,root) %{apacheconfdir}/icingaweb.conf -%config(noreplace) %attr(-,%{apacheuser},%{apachegroup}) %{configdir} -# logs -%attr(2775,%{apacheuser},%{apachegroup}) %dir %{logdir} -# shipped docs -%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/doc +%{basedir}/application/locale +%dir %{basedir}/modules +%attr(2770,root,%{icingawebgroup}) %config(noreplace) %dir %{configdir} +%attr(2770,root,%{icingawebgroup}) %config(noreplace) %dir %{configdir}/modules + %files -n php-Icinga -%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/application -%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/library +%defattr(-,root,root) +%{phpdir}/Icinga + %files -n icingacli -%attr(0755,root,root) /usr/bin/icingacli -%attr(0755,root,root) %{sharedir}/bin/icingacli -%attr(0755,root,root) %{sharedir}/bin/license_writer.py +%defattr(-,root,root) +%{basedir}/application/clicommands +%{_sysconfdir}/bash_completion.d/icingacli +%attr(0755,root,root) %{bindir}/icingacli -%changelog + +%files vendor-dompdf +%defattr(-,root,root) +%{basedir}/library/vendor/dompdf + + +%files vendor-HTMLPurifier +%defattr(-,root,root) +%{basedir}/library/vendor/HTMLPurifier + + +%files vendor-JShrink +%defattr(-,root,root) +%{basedir}/library/vendor/JShrink + + +%files vendor-lessphp +%defattr(-,root,root) +%{basedir}/library/vendor/lessphp + + +%files vendor-Parsedown +%defattr(-,root,root) +%{basedir}/library/vendor/Parsedown diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index 7f1667e5b..a8d185023 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -1,6 +1,5 @@ vendorDir = $baseDir . '/library/vendor'; $this->libDir = realpath(__DIR__ . '/../..'); + $this->setupAutoloader(); + if ($configDir === null) { if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; } else { - $configDir = '/etc/icingaweb'; + $configDir = Platform::isWindows() + ? $baseDir . '/config' + : '/etc/icingaweb2'; } } $canonical = realpath($configDir); $this->configDir = $canonical ? $canonical : $configDir; - $this->setupAutoloader(); - set_include_path( implode( PATH_SEPARATOR, @@ -333,7 +341,7 @@ abstract class ApplicationBootstrap /** * Setup Icinga auto loader * - * @return self + * @return $this */ public function setupAutoloader() { @@ -351,7 +359,7 @@ abstract class ApplicationBootstrap * * @return $this */ - protected function setupZendAutoloader() + public function setupZendAutoloader() { require_once 'Zend/Loader/Autoloader.php'; @@ -366,7 +374,7 @@ abstract class ApplicationBootstrap /** * Setup module manager * - * @return self + * @return $this */ protected function setupModuleManager() { @@ -378,25 +386,10 @@ abstract class ApplicationBootstrap return $this; } - /** - * Load all core modules - * - * @return self - */ - protected function loadCoreModules() - { - try { - $this->moduleManager->loadCoreModules(); - } catch (NotReadableError $e) { - Logger::error(new IcingaException('Cannot load core modules. An exception was thrown:', $e)); - } - return $this; - } - /** * Load all enabled modules * - * @return self + * @return $this */ protected function loadEnabledModules() { @@ -408,10 +401,47 @@ abstract class ApplicationBootstrap return $this; } + /** + * Load the setup module if Icinga Web 2 requires setup or the setup token exists + * + * @return $this + */ + protected function loadSetupModuleIfNecessary() + { + if (! @file_exists($this->config->resolvePath('authentication.ini'))) { + $this->requiresSetup = true; + $this->moduleManager->loadModule('setup'); + } elseif ($this->setupTokenExists()) { + // Load setup module but do not require setup + $this->moduleManager->loadModule('setup'); + } + return $this; + } + + /** + * Get whether Icinga Web 2 requires setup + * + * @return bool + */ + public function requiresSetup() + { + return $this->requiresSetup; + } + + /** + * Get whether the setup token exists + * + * @return bool + */ + public function setupTokenExists() + { + return @file_exists($this->config->resolvePath('setup.token')); + } + /** * Setup default logging * - * @return self + * @return $this */ protected function setupLogging() { @@ -428,7 +458,7 @@ abstract class ApplicationBootstrap /** * Load Configuration * - * @return self + * @return $this */ protected function loadConfig() { @@ -447,7 +477,7 @@ abstract class ApplicationBootstrap /** * Error handling configuration * - * @return self + * @return $this */ protected function setupErrorHandling() { @@ -473,24 +503,33 @@ abstract class ApplicationBootstrap /** * Set up logger * - * @return self + * @return $this */ protected function setupLogger() { if ($this->config->hasSection('logging')) { + $loggingConfig = $this->config->getSection('logging'); + try { - Logger::create($this->config->getSection('logging')); + Logger::create($loggingConfig); } catch (ConfigurationError $e) { - Logger::error($e); + Logger::getInstance()->registerConfigError($e->getMessage()); + + try { + Logger::getInstance()->setLevel($loggingConfig->get('level', Logger::ERROR)); + } catch (ConfigurationError $e) { + Logger::getInstance()->registerConfigError($e->getMessage()); + } } } + return $this; } /** * Set up the resource factory * - * @return self + * @return $this */ protected function setupResourceFactory() { @@ -506,6 +545,24 @@ abstract class ApplicationBootstrap return $this; } + /** + * Set up the user backend factory + * + * @return $this + */ + protected function setupUserBackendFactory() + { + try { + UserBackend::setConfig(Config::app('authentication')); + } catch (NotReadableError $e) { + Logger::error( + new IcingaException('Cannot load user backend configuration. An exception was thrown:', $e) + ); + } + + return $this; + } + /** * Detect the timezone * @@ -531,7 +588,6 @@ abstract class ApplicationBootstrap date_default_timezone_set($timezone); } } - DateTimeFactory::setConfig(array('timezone' => $timezone)); return $this; } diff --git a/library/Icinga/Application/Benchmark.php b/library/Icinga/Application/Benchmark.php index fd69dd510..bad2db268 100644 --- a/library/Icinga/Application/Benchmark.php +++ b/library/Icinga/Application/Benchmark.php @@ -1,6 +1,5 @@ setupLogger() ->setupResourceFactory() ->setupModuleManager() - ->loadCoreModules(); + ->setupUserBackendFactory() + ->loadSetupModuleIfNecessary(); } protected function setupLogging() diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php index dcb837fde..0538f01d4 100644 --- a/library/Icinga/Application/Config.php +++ b/library/Icinga/Application/Config.php @@ -1,19 +1,23 @@ config; + } + + /** + * Provide a query for the internal config object + * + * @return SimpleQuery + */ + public function select() + { + return $this->config->select(); + } + /** * Return the count of available sections * @@ -90,7 +114,7 @@ class Config implements Countable, Iterator */ public function count() { - return $this->config->count(); + return $this->select()->count(); } /** @@ -221,7 +245,7 @@ class Config implements Countable, Iterator * @param string $name * @param array|ConfigObject $config * - * @return self + * @return $this */ public function setSection($name, $config = null) { @@ -240,7 +264,7 @@ class Config implements Countable, Iterator * * @param string $name * - * @return self + * @return $this */ public function removeSection($name) { @@ -279,7 +303,7 @@ class Config implements Countable, Iterator * * @param string $file The file to parse * - * @throws NotReadableError When the file does not exist or cannot be read + * @throws NotReadableError When the file cannot be read */ public static function fromIni($file) { @@ -292,13 +316,52 @@ class Config implements Countable, Iterator $config = new static(new ConfigObject(parse_ini_file($filepath, true))); $config->setConfigFile($filepath); return $config; - } else { + } elseif (@file_exists($filepath)) { throw new NotReadableError(t('Cannot read config file "%s". Permission denied'), $filepath); } return $emptyConfig; } + /** + * Save configuration to the given INI file + * + * @param string|null $filePath The path to the INI file or null in case this config's path should be used + * @param int $fileMode The file mode to store the file with + * + * @throws LogicException In case this config has no path and none is passed in either + * @throws NotWritableError In case the INI file cannot be written + * + * @todo create basepath and throw NotWritableError in case its not possible + */ + public function saveIni($filePath = null, $fileMode = 0660) + { + if ($filePath === null && $this->configFile) { + $filePath = $this->configFile; + } elseif ($filePath === null) { + throw new LogicException('You need to pass $filePath or set a path using Config::setConfigFile()'); + } + + if (! file_exists($filePath)) { + File::create($filePath, $fileMode); + } + + $this->getIniWriter($filePath, $fileMode)->write(); + } + + /** + * Return a IniWriter for this config + * + * @param string|null $filePath + * @param int $fileMode + * + * @return IniWriter + */ + protected function getIniWriter($filePath = null, $fileMode = null) + { + return new IniWriter($this, $filePath, $fileMode); + } + /** * Prepend configuration base dir to the given relative path * @@ -322,7 +385,7 @@ class Config implements Countable, Iterator */ public static function app($configname = 'config', $fromDisk = false) { - if (!isset(self::$app[$configname]) || $fromDisk) { + if (! isset(self::$app[$configname]) || $fromDisk) { self::$app[$configname] = static::fromIni(static::resolvePath($configname . '.ini')); } @@ -341,17 +404,26 @@ class Config implements Countable, Iterator */ public static function module($modulename, $configname = 'config', $fromDisk = false) { - if (!isset(self::$modules[$modulename])) { + if (! isset(self::$modules[$modulename])) { self::$modules[$modulename] = array(); } $moduleConfigs = self::$modules[$modulename]; - if (!isset($moduleConfigs[$configname]) || $fromDisk) { + if (! isset($moduleConfigs[$configname]) || $fromDisk) { $moduleConfigs[$configname] = static::fromIni( static::resolvePath('modules/' . $modulename . '/' . $configname . '.ini') ); } - return $moduleConfigs[$configname]; } + + /** + * Return this config rendered as a INI structured string + * + * @return string + */ + public function __toString() + { + return $this->getIniWriter()->render(); + } } diff --git a/library/Icinga/Application/EmbeddedWeb.php b/library/Icinga/Application/EmbeddedWeb.php index 23cb365f0..b2d8ec046 100644 --- a/library/Icinga/Application/EmbeddedWeb.php +++ b/library/Icinga/Application/EmbeddedWeb.php @@ -1,15 +1,15 @@ @@ -19,20 +19,78 @@ use Icinga\Exception\ProgrammingError; */ class EmbeddedWeb extends ApplicationBootstrap { + /** + * Request object + * + * @var Request + */ + protected $request; + + /** + * Response + * + * @var Response + */ + protected $response; + + /** + * Get the request + * + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the response + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + /** * Embedded bootstrap parts * * @see ApplicationBootstrap::bootstrap - * @return self + * @return $this */ protected function bootstrap() { return $this ->setupZendAutoloader() - ->loadConfig() ->setupErrorHandling() + ->loadConfig() + ->setupRequest() + ->setupResponse() ->setupTimezone() ->setupModuleManager() ->loadEnabledModules(); } + + /** + * Set the request + * + * @return $this + */ + protected function setupRequest() + { + $this->request = new Request(); + return $this; + } + + /** + * Set the response + * + * @return $this + */ + protected function setupResponse() + { + $this->response = new Response(); + return $this; + } } diff --git a/library/Icinga/Application/Icinga.php b/library/Icinga/Application/Icinga.php index 1ab9c36f0..3a1e07bbc 100644 --- a/library/Icinga/Application/Icinga.php +++ b/library/Icinga/Application/Icinga.php @@ -1,6 +1,5 @@ level) !== null) { - if (is_numeric($level)) { - $level = (int) $level; - if (! isset(static::$levels[$level])) { - throw new ConfigurationError( - 'Can\'t set logging level %d. Logging level is not defined. Use one of %s or one of the' - . ' Logger\'s constants.', - $level, - implode(', ', array_keys(static::$levels)) - ); - } - $this->level = $level; - } else { - $level = strtoupper($level); - $levels = array_flip(static::$levels); - if (! isset($levels[$level])) { - throw new ConfigurationError( - 'Can\'t set logging level "%s". Logging level is not defined. Use one of %s.', - $level, - implode(', ', array_keys($levels)) - ); - } - $this->level = $levels[$level]; - } - } else { - $this->level = static::ERROR; - } + $this->setLevel($config->get('level', static::ERROR)); if (strtolower($config->get('log', 'syslog')) !== 'none') { $this->writer = $this->createWriter($config); } } + /** + * Set the logging level to use + * + * @param mixed $level + * + * @return $this + * + * @throws ConfigurationError In case the given level is invalid + */ + public function setLevel($level) + { + if (is_numeric($level)) { + $level = (int) $level; + if (! isset(static::$levels[$level])) { + throw new ConfigurationError( + 'Can\'t set logging level %d. Logging level is invalid. Use one of %s or one of the' + . ' Logger\'s constants.', + $level, + implode(', ', array_keys(static::$levels)) + ); + } + + $this->level = $level; + } else { + $level = strtoupper($level); + $levels = array_flip(static::$levels); + if (! isset($levels[$level])) { + throw new ConfigurationError( + 'Can\'t set logging level "%s". Logging level is invalid. Use one of %s.', + $level, + implode(', ', array_keys($levels)) + ); + } + + $this->level = $levels[$level]; + } + + return $this; + } + + /** + * Register the given message as config error + * + * Config errors are logged every time a log message is being logged. + * + * @param mixed $arg,... A string, exception or format-string + substitutions + * + * @return $this + */ + public function registerConfigError() + { + if (func_num_args() > 0) { + $this->configErrors[] = static::formatMessage(func_get_args()); + } + + return $this; + } + /** * Create a new logger object * @@ -157,6 +196,10 @@ class Logger public function log($level, $message) { if ($this->writer !== null && $this->level <= $level) { + foreach ($this->configErrors as $error_message) { + $this->writer->log(static::ERROR, $error_message); + } + $this->writer->log($level, $message); } } @@ -184,13 +227,7 @@ class Logger $messages = array(); $error = $message; do { - $messages[] = sprintf( - '%s in %s:%d with message: %s', - get_class($error), - $error->getFile(), - $error->getLine(), - $error->getMessage() - ); + $messages[] = IcingaException::describe($error); } while ($error = $error->getPrevious()); $message = implode(' <- ', $messages); } @@ -201,7 +238,9 @@ class Logger return vsprintf( array_shift($arguments), array_map( - function ($a) { return is_string($a) ? $a : json_encode($a); }, + function ($a) { + return is_string($a) ? $a : ($a instanceof Exception ? $a->getMessage() : json_encode($a)); + }, $arguments ) ); @@ -272,7 +311,7 @@ class Logger */ public static function writesToSyslog() { - return static::$instance && static::$instance instanceof SyslogWriter; + return static::$instance && static::$instance->getWriter() instanceof SyslogWriter; } /** @@ -282,7 +321,7 @@ class Logger */ public static function writesToFile() { - return static::$instance && static::$instance instanceof FileWriter; + return static::$instance && static::$instance->getWriter() instanceof FileWriter; } /** diff --git a/library/Icinga/Application/Logger/LogWriter.php b/library/Icinga/Application/Logger/LogWriter.php index b89fec62f..72bbbb976 100644 --- a/library/Icinga/Application/Logger/LogWriter.php +++ b/library/Icinga/Application/Logger/LogWriter.php @@ -1,6 +1,5 @@ ident = $config->get('application', 'icingaweb'); + $this->ident = $config->get('application', 'icingaweb2'); $this->facility = static::$facilities['user']; } @@ -68,6 +67,6 @@ class SyslogWriter extends LogWriter public function log($level, $message) { openlog($this->ident, LOG_PID, $this->facility); - syslog(static::$severityMap[$level], $message); + syslog(static::$severityMap[$level], str_replace("\n", ' ', $message)); } } diff --git a/library/Icinga/Application/Modules/Manager.php b/library/Icinga/Application/Modules/Manager.php index 6469d3929..f3bf4e016 100644 --- a/library/Icinga/Application/Modules/Manager.php +++ b/library/Icinga/Application/Modules/Manager.php @@ -1,6 +1,5 @@ enableDir; - if ($canonical === false || ! file_exists($canonical)) { - // TODO: I guess the check for false has something to do with a - // call to realpath no longer present + if (! file_exists($parent = dirname($this->enableDir))) { return; } - if (!is_dir($this->enableDir)) { + if (! is_readable($parent)) { + throw new NotReadableError( + 'Cannot read enabled modules. Module directory\'s parent directory "%s" is not readable', + $parent + ); + } + + if (! file_exists($this->enableDir)) { + return; + } + if (! is_dir($this->enableDir)) { throw new NotReadableError( 'Cannot read enabled modules. Module directory "%s" is not a directory', $this->enableDir ); } - if (!is_readable($this->enableDir)) { + if (! is_readable($this->enableDir)) { throw new NotReadableError( 'Cannot read enabled modules. Module directory "%s" is not readable', $this->enableDir ); } - if (($dh = opendir($canonical)) !== false) { + if (($dh = opendir($this->enableDir)) !== false) { $this->enabledDirs = array(); while (($file = readdir($dh)) !== false) { @@ -141,7 +141,7 @@ class Manager continue; } - $link = $this->enableDir . '/' . $file; + $link = $this->enableDir . DIRECTORY_SEPARATOR . $file; if (! is_link($link)) { Logger::warning( 'Found invalid module in enabledModule directory "%s": "%s" is not a symlink', @@ -152,7 +152,7 @@ class Manager } $dir = realpath($link); - if (!file_exists($dir) || !is_dir($dir)) { + if (! file_exists($dir) || !is_dir($dir)) { Logger::warning( 'Found invalid module in enabledModule directory "%s": "%s" points to non existing path "%s"', $this->enableDir, @@ -170,23 +170,9 @@ class Manager } /** - * Try to set all core modules in loaded state + * Try to set all enabled modules in loaded sate * - * @return self - * @see Manager::loadModule() - */ - public function loadCoreModules() - { - foreach ($this->coreModules as $name) { - $this->loadModule($name); - } - return $this; - } - - /** - * Try to set all enabled modules in loaded state - * - * @return self + * @return $this * @see Manager::loadModule() */ public function loadEnabledModules() @@ -203,7 +189,7 @@ class Manager * @param string $name The name of the module to load * @param mixed $basedir Optional module base directory * - * @return self + * @return $this */ public function loadModule($name, $basedir = null) { @@ -227,32 +213,35 @@ class Manager * * @param string $name The module to enable * - * @return self + * @return $this * @throws ConfigurationError When trying to enable a module that is not installed - * @throws NotFoundError In case the "enabledModules" directory does not exist * @throws SystemPermissionException When insufficient permissions for the application exist */ public function enableModule($name) { - if (!$this->hasInstalled($name)) { + if (! $this->hasInstalled($name)) { throw new ConfigurationError( 'Cannot enable module "%s". Module is not installed.', $name ); - } elseif (in_array($name, $this->coreModules)) { - return $this; } clearstatcache(true); $target = $this->installedBaseDirs[$name]; - $link = $this->enableDir . '/' . $name; + $link = $this->enableDir . DIRECTORY_SEPARATOR . $name; - if (! is_dir($this->enableDir)) { - throw new NotFoundError('Cannot enable module "%s". Path "%s" not found.', $name, $this->enableDir); - } elseif (!is_writable($this->enableDir)) { + if (! is_dir($this->enableDir) && !@mkdir($this->enableDir, 02770, true)) { + $error = error_get_last(); throw new SystemPermissionException( - 'Cannot enable module "%s". Insufficient system permissions for enabling modules.', - $name + 'Failed to create enabledModules directory "%s" (%s)', + $this->enableDir, + $error['message'] + ); + } elseif (! is_writable($this->enableDir)) { + throw new SystemPermissionException( + 'Cannot enable module "%s". Check the permissions for the enabledModules directory: %s', + $name, + $this->enableDir ); } @@ -260,71 +249,79 @@ class Manager return $this; } - if (!@symlink($target, $link)) { + if (! @symlink($target, $link)) { $error = error_get_last(); if (strstr($error["message"], "File exists") === false) { throw new SystemPermissionException( - 'Could not enable module "%s" due to file system errors. ' + 'Cannot enable module "%s" at %s due to file system errors. ' . 'Please check path and mounting points because this is not a permission error. ' . 'Primary error was: %s', $name, + $this->enableDir, $error['message'] ); } } $this->enabledDirs[$name] = $link; - $this->loadModule($name); - return $this; } /** - * Disable the given module and remove it's enabled state + * Disable the given module and remove its enabled state * * @param string $name The name of the module to disable * - * @return self + * @return $this * * @throws ConfigurationError When the module is not installed or it's not a symlink - * @throws SystemPermissionException When the module can't be disabled + * @throws SystemPermissionException When insufficient permissions for the application exist */ public function disableModule($name) { - if (!$this->hasEnabled($name)) { - return $this; - } - if (!is_writable($this->enableDir)) { - throw new SystemPermissionException( - 'Could not disable module. Module path is not writable.' - ); - } - $link = $this->enableDir . '/' . $name; - if (!file_exists($link)) { + if (! $this->hasEnabled($name)) { throw new ConfigurationError( - 'Could not disable module. The module %s was not found.', - $name - ); - } - if (!is_link($link)) { - throw new ConfigurationError( - 'Could not disable module. The module "%s" is not a symlink. ' - . 'It looks like you have installed this module manually and moved it to your module folder. ' - . 'In order to dynamically enable and disable modules, you have to create a symlink to ' - . 'the enabled_modules folder.', + 'Cannot disable module "%s". Module is not installed.', $name ); } + if (! is_writable($this->enableDir)) { + throw new SystemPermissionException( + 'Cannot disable module "%s". Check the permissions for the enabledModules directory: %s', + $name, + $this->enableDir + ); + } + + $link = $this->enableDir . DIRECTORY_SEPARATOR . $name; + if (! file_exists($link)) { + throw new ConfigurationError( + 'Cannot disable module "%s". Module is not installed.', + $name + ); + } + if (! is_link($link)) { + throw new ConfigurationError( + 'Cannot disable module %s at %s. ' + . 'It looks like you have installed this module manually and moved it to your module folder. ' + . 'In order to dynamically enable and disable modules, you have to create a symlink to ' + . 'the enabledModules folder.', + $name, + $this->enableDir + ); + } + if (file_exists($link) && is_link($link)) { - if (!@unlink($link)) { + if (! @unlink($link)) { $error = error_get_last(); throw new SystemPermissionException( - 'Could not disable module "%s" due to file system errors. ' + 'Cannot enable module "%s" at %s due to file system errors. ' . 'Please check path and mounting points because this is not a permission error. ' . 'Primary error was: %s', $name, + $this->enableDir, $error['message'] ); } @@ -414,22 +411,25 @@ class Manager } /** - * Return the module instance of the given module when it is loaded + * Get a module * - * @param string $name The module name to return + * @param string $name Name of the module + * @param bool $assertLoaded Whether or not to throw an exception if the module hasn't been loaded * * @return Module - * @throws ProgrammingError When the module hasn't been loaded + * @throws ProgrammingError If the module hasn't been loaded */ - public function getModule($name) + public function getModule($name, $assertLoaded = true) { - if (!$this->hasLoaded($name)) { - throw new ProgrammingError( - 'Cannot access module %s as it hasn\'t been loaded', - $name - ); + if ($this->hasLoaded($name)) { + return $this->loadedModules[$name]; + } elseif (! (bool) $assertLoaded) { + return new Module($this->app, $name, $this->getModuleDir($name)); } - return $this->loadedModules[$name]; + throw new ProgrammingError( + 'Can\'t access module %s because it hasn\'t been loaded', + $name + ); } /** @@ -458,7 +458,7 @@ class Manager } $installed = $this->listInstalledModules(); - foreach (array_diff($installed, $this->coreModules) as $name) { + foreach ($installed as $name) { $info[$name] = (object) array( 'name' => $name, 'path' => $this->installedBaseDirs[$name], @@ -520,7 +520,7 @@ class Manager * * @param array $availableDirs Installed modules location * - * @return self + * @return $this */ public function detectInstalledModules(array $availableDirs = null) { diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 8c01eb05e..9ec35b565 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -1,6 +1,5 @@ $title, - 'url' => $url - ); - - $this->searchUrls[] = $searchUrl; - } - - public function getSearchUrls() - { - $this->launchConfigScript(); - return $this->searchUrls; - } + protected $userBackends = array(); /** - * Get all Menu Items + * This module's user group backends * - * @return array + * @var array */ - public function getPaneItems() - { - $this->launchConfigScript(); - return $this->paneItems; - } - - /** - * Add a pane to dashboard - * - * @param $name - * @return Pane - */ - protected function dashboard($name) - { - $this->paneItems[$name] = new Pane($name); - return $this->paneItems[$name]; - } - - /** - * Get all Menu Items - * - * @return array - */ - public function getMenuItems() - { - $this->launchConfigScript(); - return $this->menuItems; - } - - /** - * Add a menu Section to the Sidebar menu - * - * @param $name - * @param array $properties - * @return mixed - */ - protected function menuSection($name, array $properties = array()) - { - if (array_key_exists($name, $this->menuItems)) { - $this->menuItems[$name]->setProperties($properties); - } else { - $this->menuItems[$name] = new Menu($name, new ConfigObject($properties)); - } - - return $this->menuItems[$name]; - } + protected $userGroupBackends = array(); /** * Create a new module object @@ -264,7 +235,7 @@ class Module $this->cssdir = $basedir . '/public/css'; $this->jsdir = $basedir . '/public/js'; $this->libdir = $basedir . '/library'; - $this->configdir = $basedir . '/config'; + $this->configdir = $app->getConfigDir('modules/' . $name); $this->localedir = $basedir . '/application/locale'; $this->formdir = $basedir . '/application/forms'; $this->controllerdir = $basedir . '/application/controllers'; @@ -273,6 +244,91 @@ class Module $this->metadataFile = $basedir . '/module.info'; } + /** + * Provide a search URL + * + * @param string $title + * @param string $url + * @param int $priority + * + * @return $this + */ + public function provideSearchUrl($title, $url, $priority = 0) + { + $this->searchUrls[] = (object) array( + 'title' => (string) $title, + 'url' => (string) $url, + 'priority' => (int) $priority + ); + + return $this; + } + + /** + * Get this module's search urls + * + * @return array + */ + public function getSearchUrls() + { + $this->launchConfigScript(); + return $this->searchUrls; + } + + /** + * Get all pane items + * + * @return array + */ + public function getPaneItems() + { + $this->launchConfigScript(); + return $this->paneItems; + } + + /** + * Add a pane to dashboard + * + * @param string $name + * + * @return Pane + */ + protected function dashboard($name) + { + $this->paneItems[$name] = new Pane($name); + return $this->paneItems[$name]; + } + + /** + * Get all menu items + * + * @return array + */ + public function getMenuItems() + { + $this->launchConfigScript(); + return $this->menuItems; + } + + /** + * Add or get a menu section + * + * @param string $name + * @param array $properties + * + * @return Menu + */ + protected function menuSection($name, array $properties = array()) + { + if (array_key_exists($name, $this->menuItems)) { + $this->menuItems[$name]->setProperties($properties); + } else { + $this->menuItems[$name] = new Menu($name, new ConfigObject($properties)); + } + + return $this->menuItems[$name]; + } + /** * Register module * @@ -280,6 +336,10 @@ class Module */ public function register() { + if ($this->registered) { + return true; + } + $this->registerAutoloader(); try { $this->launchRunScript(); @@ -293,15 +353,27 @@ class Module return false; } $this->registerWebIntegration(); + $this->registered = true; + return true; } + /** + * Get whether this module has been registered + * + * @return bool + */ + public function isRegistered() + { + return $this->registered; + } + /** * Test for an enabled module by name * - * @param string $name + * @param string $name * - * @return boolean + * @return bool */ public static function exists($name) { @@ -309,7 +381,7 @@ class Module } /** - * Get module by name + * Get a module by name * * @param string $name * @param bool $autoload @@ -330,6 +402,19 @@ class Module return $manager->getModule($name); } + /** + * Provide an additional CSS/LESS file + * + * @param string $path The path to the file, relative to self::$cssdir + * + * @return $this + */ + protected function provideCssFile($path) + { + $this->cssFiles[] = $this->cssdir . DIRECTORY_SEPARATOR . $path; + return $this; + } + /** * Test if module provides css * @@ -337,7 +422,12 @@ class Module */ public function hasCss() { - return file_exists($this->getCssFilename()); + if (file_exists($this->getCssFilename())) { + return true; + } + + $this->launchConfigScript(); + return !empty($this->cssFiles); } /** @@ -350,6 +440,32 @@ class Module return $this->cssdir . '/module.less'; } + /** + * Return the CSS/LESS files this module provides + * + * @return array + */ + public function getCssFiles() + { + $this->launchConfigScript(); + $files = $this->cssFiles; + $files[] = $this->getCssFilename(); + return $files; + } + + /** + * Provide an additional Javascript file + * + * @param string $path The path to the file, relative to self::$jsdir + * + * @return $this + */ + protected function provideJsFile($path) + { + $this->jsFiles[] = $this->jsdir . DIRECTORY_SEPARATOR . $path; + return $this; + } + /** * Test if module provides js * @@ -357,7 +473,12 @@ class Module */ public function hasJs() { - return file_exists($this->getJsFilename()); + if (file_exists($this->getJsFilename())) { + return true; + } + + $this->launchConfigScript(); + return !empty($this->jsFiles); } /** @@ -371,7 +492,20 @@ class Module } /** - * Getter for module name + * Return the Javascript files this module provides + * + * @return array + */ + public function getJsFiles() + { + $this->launchConfigScript(); + $files = $this->jsFiles; + $files[] = $this->getJsFilename(); + return $files; + } + + /** + * Get the module name * * @return string */ @@ -381,7 +515,7 @@ class Module } /** - * Getter for module version + * Get the module version * * @return string */ @@ -391,7 +525,7 @@ class Module } /** - * Get module description + * Get the module description * * @return string */ @@ -401,7 +535,7 @@ class Module } /** - * Get module title (short description) + * Get the module title (short description) * * @return string */ @@ -411,9 +545,9 @@ class Module } /** - * Getter for module version + * Get the module dependencies * - * @return Array + * @return array */ public function getDependencies() { @@ -508,7 +642,7 @@ class Module } /** - * Getter for css file name + * Get the module's CSS directory * * @return string */ @@ -518,17 +652,7 @@ class Module } /** - * Getter for base directory - * - * @return string - */ - public function getBaseDir() - { - return $this->basedir; - } - - /** - * Get the controller directory + * Get the module's controller directory * * @return string */ @@ -538,7 +662,17 @@ class Module } /** - * Getter for library directory + * Get the module's base directory + * + * @return string + */ + public function getBaseDir() + { + return $this->basedir; + } + + /** + * Get the module's library directory * * @return string */ @@ -548,7 +682,7 @@ class Module } /** - * Getter for configuration directory + * Get the module's configuration directory * * @return string */ @@ -558,7 +692,7 @@ class Module } /** - * Getter for form directory + * Get the module's form directory * * @return string */ @@ -568,21 +702,19 @@ class Module } /** - * Getter for module config object + * Get the module config * - * @param string $file + * @param string $file * - * @return Config + * @return Config */ - public function getConfig($file = null) + public function getConfig($file = 'config') { return $this->app->getConfig()->module($this->name, $file); } /** - * Retrieve provided permissions - * - * @param string $name Permission name + * Get provided permissions * * @return array */ @@ -593,9 +725,8 @@ class Module } /** - * Retrieve provided restrictions + * Get provided restrictions * - * @param string $name Restriction name * @return array */ public function getProvidedRestrictions() @@ -605,24 +736,11 @@ class Module } /** - * Whether the given permission name is supported + * Whether the module provides the given restriction * - * @param string $name Permission name + * @param string $name Restriction name * - * @return bool - */ - public function providesPermission($name) - { - $this->launchConfigScript(); - return array_key_exists($name, $this->permissionList); - } - - /** - * Whether the given restriction name is supported - * - * @param string $name Restriction name - * - * @return bool + * @return bool */ public function providesRestriction($name) { @@ -631,9 +749,22 @@ class Module } /** - * Retrieve this modules configuration tabs + * Whether the module provides the given permission * - * @return Icinga\Web\Widget\Tabs + * @param string $name Permission name + * + * @return bool + */ + public function providesPermission($name) + { + $this->launchConfigScript(); + return array_key_exists($name, $this->permissionList); + } + + /** + * Get the module configuration tabs + * + * @return \Icinga\Web\Widget\Tabs */ public function getConfigTabs() { @@ -642,7 +773,7 @@ class Module $tabs->add('info', array( 'url' => 'config/module', 'urlParams' => array('name' => $this->getName()), - 'title' => 'Module: ' . $this->getName() + 'label' => 'Module: ' . $this->getName() )); foreach ($this->configTabs as $name => $config) { $tabs->add($name, $config); @@ -651,9 +782,9 @@ class Module } /** - * Whether this module provides a setup wizard + * Whether the module provides a setup wizard * - * @return bool + * @return bool */ public function providesSetupWizard() { @@ -667,22 +798,44 @@ class Module } /** - * Return this module's setup wizard + * Get the module's setup wizard * - * @return SetupWizard + * @return SetupWizard */ public function getSetupWizard() { return new $this->setupWizard; } + /** + * Get the module's user backends + * + * @return array + */ + public function getUserBackends() + { + $this->launchConfigScript(); + return $this->userBackends; + } + + /** + * Get the module's user group backends + * + * @return array + */ + public function getUserGroupBackends() + { + $this->launchConfigScript(); + return $this->userGroupBackends; + } + /** * Provide a named permission * - * @param string $name Unique permission name - * @param string $name Permission description + * @param string $name Unique permission name + * @param string $description Permission description * - * @return void + * @throws IcingaException If the permission is already provided */ protected function providePermission($name, $description) { @@ -701,10 +854,10 @@ class Module /** * Provide a named restriction * - * @param string $name Unique restriction name - * @param string $description Restriction description + * @param string $name Unique restriction name + * @param string $description Restriction description * - * @return void + * @throws IcingaException If the restriction is already provided */ protected function provideRestriction($name, $description) { @@ -723,15 +876,16 @@ class Module /** * Provide a module config tab * - * @param string $name Unique tab name - * @param string $config Tab config + * @param string $name Unique tab name + * @param array $config Tab config * - * @return self + * @return $this + * @throws ProgrammingError If $config lacks the key 'url' */ protected function provideConfigTab($name, $config = array()) { if (! array_key_exists('url', $config)) { - throw new ProgrammingError('A module config tab MUST provide and "url"'); + throw new ProgrammingError('A module config tab MUST provide a "url"'); } $config['url'] = $this->getName() . '/' . ltrim($config['url'], '/'); $this->configTabs[$name] = $config; @@ -741,9 +895,9 @@ class Module /** * Provide a setup wizard * - * @param string $className The name of the class + * @param string $className The name of the class * - * @return self + * @return $this */ protected function provideSetupWizard($className) { @@ -752,31 +906,65 @@ class Module } /** - * Register new namespaces on the autoloader + * Provide a user backend capable of authenticating users * - * @return self + * @param string $identifier The identifier of the new backend type + * @param string $className The name of the class + * + * @return $this + */ + protected function provideUserBackend($identifier, $className) + { + $this->userBackends[strtolower($identifier)] = $className; + return $this; + } + + /** + * Provide a user group backend + * + * @param string $identifier The identifier of the new backend type + * @param string $className The name of the class + * + * @return $this + */ + protected function provideUserGroupBackend($identifier, $className) + { + $this->userGroupBackends[strtolower($identifier)] = $className; + return $this; + } + + /** + * Register module namespaces on the autoloader + * + * @return $this */ protected function registerAutoloader() { - $moduleName = ucfirst($this->getName()); - $moduleLibraryDir = $this->getLibDir(). '/'. $moduleName; - if (is_dir($this->getBaseDir()) && is_dir($this->getLibDir()) && is_dir($moduleLibraryDir)) { - $this->app->getLoader()->registerNamespace('Icinga\\Module\\' . $moduleName, $moduleLibraryDir); - if (is_dir($this->getFormDir())) { - $this->app->getLoader()->registerNamespace( - 'Icinga\\Module\\' . $moduleName. '\\Forms', - $this->getFormDir() - ); - } + if ($this->registeredAutoloader) { + return $this; } + $moduleName = ucfirst($this->getName()); + + $moduleLibraryDir = $this->getLibDir(). '/'. $moduleName; + if (is_dir($moduleLibraryDir)) { + $this->app->getLoader()->registerNamespace('Icinga\\Module\\' . $moduleName, $moduleLibraryDir); + } + + $moduleFormDir = $this->getFormDir(); + if (is_dir($moduleFormDir)) { + $this->app->getLoader()->registerNamespace('Icinga\\Module\\' . $moduleName. '\\Forms', $moduleFormDir); + } + + $this->registeredAutoloader = true; + return $this; } /** * Bind text domain for i18n * - * @return self + * @return $this */ protected function registerLocales() { @@ -787,7 +975,7 @@ class Module } /** - * return bool Whether this module has translations + * Get whether the module has translations */ public function hasLocales() { @@ -797,7 +985,7 @@ class Module /** * List all available locales * - * return array Locale list + * @return array Locale list */ public function listLocales() { @@ -823,7 +1011,7 @@ class Module * * Add controller directory to mvc * - * @return self + * @return $this */ protected function registerWebIntegration() { @@ -844,10 +1032,9 @@ class Module } /** - * Add routes for static content and any route added via addRoute() to the route chain + * Add routes for static content and any route added via {@link addRoute()} to the route chain * - * @return self - * @see addRoute() + * @return $this */ protected function registerRoutes() { @@ -886,7 +1073,7 @@ class Module /** * Run module bootstrap script * - * @return self + * @return $this */ protected function launchRunScript() { @@ -896,14 +1083,14 @@ class Module /** * Include a php script if it is readable * - * @param string $file File to include + * @param string $file File to include * - * @return self + * @return $this */ protected function includeScript($file) { - if (file_exists($file) && is_readable($file) === true) { - include($file); + if (file_exists($file) && is_readable($file)) { + include $file; } return $this; @@ -911,28 +1098,27 @@ class Module /** * Run module config script + * + * @return $this */ protected function launchConfigScript() { if ($this->triedToLaunchConfigScript) { - return; + return $this; } $this->triedToLaunchConfigScript = true; - if (! file_exists($this->configScript) - || ! is_readable($this->configScript)) { - return; - } - include($this->configScript); + $this->registerAutoloader(); + return $this->includeScript($this->configScript); } /** * Register hook * - * @param string $name - * @param string $class - * @param string $key + * @param string $name + * @param string $class + * @param string $key * - * @return self + * @return $this */ protected function registerHook($name, $class, $key = null) { @@ -951,7 +1137,7 @@ class Module * @param string $name Name of the route * @param Zend_Controller_Router_Route_Abstract $route Instance of the route * - * @return self + * @return $this * @see registerRoutes() */ protected function addRoute($name, Zend_Controller_Router_Route_Abstract $route) @@ -961,12 +1147,8 @@ class Module } /** - * Translate a string with the global mt() - * - * @param $string - * @param null $context - * - * @return mixed|string + * (non-PHPDoc) + * @see Translator::translate() For the function documentation. */ protected function translate($string, $context = null) { diff --git a/library/Icinga/Application/Platform.php b/library/Icinga/Application/Platform.php index 259219435..cc72857f9 100644 --- a/library/Icinga/Application/Platform.php +++ b/library/Icinga/Application/Platform.php @@ -1,6 +1,5 @@ 2) { + return 'linux'; + } + + foreach (array( + 'fedora' => '/etc/fedora-release', + 'centos' => '/etc/centos-release' + ) as $distro => $releaseFile) { + if (! (false === ( + $release = @file_get_contents($releaseFile) + ) || false === strpos(strtolower($release), $distro))) { + return $distro; + } + } + + if (false !== ($release = @file_get_contents('/etc/redhat-release'))) { + $release = strtolower($release); + if (false !== strpos($release, 'red hat enterprise linux')) { + return 'rhel'; + } + foreach (array('fedora', 'centos') as $distro) { + if (false !== strpos($release, $distro)) { + return $distro; + } + } + return $reliable < 2 ? 'redhat' : 'linux'; + } + + if (false !== ($release = @file_get_contents('/etc/SuSE-release'))) { + $release = strtolower($release); + foreach (array( + 'opensuse' => 'opensuse', + 'sles' => 'suse linux enterprise server', + 'sled' => 'suse linux enterprise desktop' + ) as $distro => $name) { + if (false !== strpos($release, $name)) { + return $distro; + } + } + return $reliable < 2 ? 'suse' : 'linux'; + } + + if ($reliable < 1) { + if (false === ($procVersion = @file_get_contents('/proc/version'))) { + return false; + } + $procVersion = strtolower($procVersion); + foreach (array( + 'redhat' => 'red hat', + 'suse' => 'suse linux', + 'ubuntu' => 'ubuntu', + 'debian' => 'debian' + ) as $distro => $name) { + if (false !== strpos($procVersion, $name)) { + return $distro; + } + } + } + + return 'linux'; + } + /** * Test of CLI environment * @@ -183,18 +298,47 @@ class Platform } /** - * Return whether the given Zend framework class exists + * Return whether the given class exists * * @param string $name The name of the class to check * * @return bool */ - public static function zendClassExists($name) + public static function classExists($name) { - if (class_exists($name)) { + if (@class_exists($name)) { return true; } - return (@include str_replace('_', '/', $name) . '.php') !== false; + if (strpos($name, '_') !== false) { + // Assume it's a Zend-Framework class + return (@include str_replace('_', '/', $name) . '.php') !== false; + } + + return false; + } + + /** + * Return whether it's possible to connect to a MySQL database + * + * Checks whether the mysql pdo extension has been loaded and the Zend framework adapter for MySQL is available + * + * @return bool + */ + public static function hasMysqlSupport() + { + return static::extensionLoaded('mysql') && static::classExists('Zend_Db_Adapter_Pdo_Mysql'); + } + + /** + * Return whether it's possible to connect to a PostgreSQL database + * + * Checks whether the pgsql pdo extension has been loaded and the Zend framework adapter for PostgreSQL is available + * + * @return bool + */ + public static function hasPostgresqlSupport() + { + return static::extensionLoaded('pgsql') && static::classExists('Zend_Db_Adapter_Pdo_Pgsql'); } } diff --git a/library/Icinga/Application/Version.php b/library/Icinga/Application/Version.php new file mode 100644 index 000000000..65b6138d9 --- /dev/null +++ b/library/Icinga/Application/Version.php @@ -0,0 +1,37 @@ +getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION' + ))) { + return false; + } + + $matches = array(); + if (false === ($res = preg_match( + '/(?\w+)(?:\s*\(.*?(?:(?<=[\(,])\s*tag\s*:\s*v(?P.+?)\s*(?=[\),]).*?)?\))?\s*(?P\S+)/ms', + $appVersion, + $matches + )) || $res === 0) { + return false; + } + + foreach ($matches as $key => $value) { + if (is_int($key) || $value === '') { + unset($matches[$key]); + } + } + return $matches; + } +} diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index 8c103e01c..8386efc6b 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -1,45 +1,35 @@ - * use Icinga\Application\EmbeddedWeb; - * EmbeddedWeb::start(); + * use Icinga\Application\Web; + * Web::start(); * */ -class Web extends ApplicationBootstrap +class Web extends EmbeddedWeb { /** * View object @@ -55,13 +45,6 @@ class Web extends ApplicationBootstrap */ private $frontController; - /** - * Request object - * - * @var Request - */ - private $request; - /** * Session object * @@ -86,27 +69,29 @@ class Web extends ApplicationBootstrap /** * Initialize all together * - * @return self + * @return $this */ protected function bootstrap() { return $this ->setupZendAutoloader() - ->detectCookieSupport() ->setupLogging() ->setupErrorHandling() ->loadConfig() ->setupResourceFactory() ->setupSession() + ->setupNotifications() + ->setupRequest() + ->setupResponse() ->setupUser() ->setupTimezone() ->setupLogger() ->setupInternationalization() - ->setupRequest() ->setupZendMvc() ->setupFormNamespace() ->setupModuleManager() - ->loadCoreModules() + ->setupUserBackendFactory() + ->loadSetupModuleIfNecessary() ->loadEnabledModules() ->setupRoute() ->setupPagination(); @@ -115,7 +100,7 @@ class Web extends ApplicationBootstrap /** * Prepare routing * - * @return self + * @return $this */ private function setupRoute() { @@ -158,50 +143,47 @@ class Web extends ApplicationBootstrap */ public function dispatch() { - $this->frontController->dispatch($this->request, new Response()); + $this->frontController->dispatch($this->getRequest(), $this->getResponse()); } /** * Prepare Zend MVC Base * - * @return self + * @return $this */ private function setupZendMvc() { - // TODO: Replace Zend_Application: Zend_Layout::startMvc( array( 'layout' => 'layout', 'layoutPath' => $this->getApplicationDir('/layouts/scripts') ) ); - $this->setupFrontController(); $this->setupViewRenderer(); - return $this; } /** * Create user object * - * @return self + * @return $this */ private function setupUser() { - $authenticationManager = AuthenticationManager::getInstance(); - - if ($authenticationManager->isAuthenticated() === true) { - $this->user = $authenticationManager->getUser(); + $auth = Auth::getInstance(); + if ($auth->isAuthenticated()) { + $user = $auth->getUser(); + $this->getRequest()->setUser($user); + $this->user = $user; } - return $this; } /** * Initialize a session provider * - * @return self + * @return $this */ private function setupSession() { @@ -210,84 +192,67 @@ class Web extends ApplicationBootstrap } /** - * Inject dependencies into request + * Initialize notifications to remove them immediately from session * - * @return self + * @return $this */ - private function setupRequest() + private function setupNotifications() { - $this->request = new Request(); - - if ($this->user instanceof User) { - $this->request->setUser($this->user); - } - + Notification::getInstance(); return $this; } /** * Instantiate front controller * - * @return self + * @return $this */ private function setupFrontController() { $this->frontController = Zend_Controller_Front::getInstance(); - - $this->frontController->setRequest($this->request); - + $this->frontController->setRequest($this->getRequest()); $this->frontController->setControllerDirectory($this->getApplicationDir('/controllers')); - $this->frontController->setParams( array( 'displayExceptions' => true ) ); - return $this; } /** * Register helper paths and views for renderer * - * @return self + * @return $this */ private function setupViewRenderer() { + $view = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); /** @var \Zend_Controller_Action_Helper_ViewRenderer $view */ - $view = ActionHelperBroker::getStaticHelper('viewRenderer'); $view->setView(new View()); - $view->view->addHelperPath($this->getApplicationDir('/views/helpers')); - $view->view->setEncoding('UTF-8'); $view->view->headTitle()->prepend($this->config->get('global', 'project', 'Icinga')); - $view->view->headTitle()->setSeparator(' :: '); - $this->viewRenderer = $view; - return $this; } /** * Configure pagination settings * - * @return self + * @return $this */ private function setupPagination() { - Zend_Paginator::addScrollingStylePrefixPath( 'Icinga_Web_Paginator_ScrollingStyle', 'Icinga/Web/Paginator/ScrollingStyle' ); - Zend_Paginator::setDefaultScrollingStyle('SlidingWithBorder'); Zend_View_Helper_PaginationControl::setDefaultViewPartial( array('mixedPagination.phtml', 'default') ); - return $this; } @@ -297,7 +262,7 @@ class Web extends ApplicationBootstrap */ protected function detectTimezone() { - $auth = Manager::getInstance(); + $auth = Auth::getInstance(); if (! $auth->isAuthenticated() || ($timezone = $auth->getUser()->getPreferences()->getValue('icingaweb', 'timezone')) === null ) { @@ -310,26 +275,30 @@ class Web extends ApplicationBootstrap /** * Setup internationalization using gettext * - * Uses the preferred user language or the configured default and system default, respectively. + * Uses the preferred user language or the browser suggested language or our default. * - * @return self + * @return string Detected locale code + * + * @see Translator::DEFAULT_LOCALE For the the default locale code. */ protected function detectLocale() { - $auth = Manager::getInstance(); - if (! $auth->isAuthenticated() - || ($locale = $auth->getUser()->getPreferences()->getValue('icingaweb', 'language')) === null - && isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) + $auth = Auth::getInstance(); + if ($auth->isAuthenticated() + && ($locale = $auth->getUser()->getPreferences()->getValue('icingaweb', 'language')) !== null ) { - $locale = Translator::getPreferredLocaleCode($_SERVER['HTTP_ACCEPT_LANGUAGE']); + return $locale; } - return $locale; + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + return Translator::getPreferredLocaleCode($_SERVER['HTTP_ACCEPT_LANGUAGE']); + } + return Translator::DEFAULT_LOCALE; } /** * Setup an autoloader namespace for Icinga\Forms * - * @return self + * @return $this */ private function setupFormNamespace() { @@ -339,20 +308,4 @@ class Web extends ApplicationBootstrap ); return $this; } - - /** - * Check cookie support - * - * @return $this - */ - protected function detectCookieSupport() - { - if (! Cookie::isSupported()) { - echo 'Cookies must be enabled to run this application.'; - exit(1); - } - - return $this; - } } -// @codeCoverageIgnoreEnd diff --git a/library/Icinga/Application/functions.php b/library/Icinga/Application/functions.php index 304c75093..3ae0c7ba9 100644 --- a/library/Icinga/Application/functions.php +++ b/library/Icinga/Application/functions.php @@ -1,6 +1,5 @@ initFromRequest(); - echo $pie->render(); + $pie->toSvg(); } elseif ($path === 'png/chart.php') { if (!array_key_exists('data', $_GET)) { diff --git a/library/Icinga/Authentication/AdmissionLoader.php b/library/Icinga/Authentication/AdmissionLoader.php index d97fc534e..81b5cafb2 100644 --- a/library/Icinga/Authentication/AdmissionLoader.php +++ b/library/Icinga/Authentication/AdmissionLoader.php @@ -1,6 +1,5 @@ user === null && ! $ignoreSession) { + $this->authenticateFromSession(); + } + if ($this->user === null && ! $this->authExternal()) { + return $this->authHttp(); + } + return true; + } + public function setAuthenticated(User $user, $persist = true) { $username = $user->getUsername(); @@ -56,25 +104,28 @@ class Manager } catch (NotReadableError $e) { Logger::error( new IcingaException( - 'Cannot load preferences for user "%s". An exception was thrown', + 'Cannot load preferences for user "%s". An exception was thrown: %s', $username, $e ) ); $config = new Config(); } - if ($config->hasSection('preferences')) { - $preferencesConfig = $config->getSection('preferences'); + if ($config->get('global', 'config_backend', 'ini') !== 'none') { + $preferencesConfig = new ConfigObject(array( + 'store' => $config->get('global', 'config_backend', 'ini'), + 'resource' => $config->get('global', 'config_resource') + )); try { $preferencesStore = PreferencesStore::create( $preferencesConfig, $user ); $preferences = new Preferences($preferencesStore->load()); - } catch (NotReadableError $e) { + } catch (Exception $e) { Logger::error( new IcingaException( - 'Cannot load preferences for user "%s". An exception was thrown', + 'Cannot load preferences for user "%s". An exception was thrown: %s', $username, $e ) @@ -85,14 +136,14 @@ class Manager $preferences = new Preferences(); } $user->setPreferences($preferences); - $groups = array(); + $groups = $user->getGroups(); foreach (Config::app('groups') as $name => $config) { try { $groupBackend = UserGroupBackend::create($name, $config); $groupsFromBackend = $groupBackend->getMemberships($user); } catch (Exception $e) { Logger::error( - 'Can\'t get group memberships for user \'%s\' from backend \'%s\'. An exception was thrown:', + 'Can\'t get group memberships for user \'%s\' from backend \'%s\'. An exception was thrown: %s', $username, $name, $e @@ -117,58 +168,40 @@ class Manager } /** - * Writes the current user to the session + * Getter for groups belonged to authenticated user + * + * @return array + * @see User::getGroups */ - public function persistCurrentUser() + public function getGroups() { - Session::getSession()->set('user', $this->user)->refreshId(); + return $this->user->getGroups(); } /** - * Try to authenticate the user with the current session + * Get the request * - * Authentication for externally-authenticated users will be revoked if the username changed or external - * authentication is no longer in effect + * @return \Icinga\Web\Request */ - public function authenticateFromSession() + public function getRequest() { - $this->user = Session::getSession()->get('user'); - if ($this->user !== null && $this->user->isRemoteUser() === true) { - list($originUsername, $field) = $this->user->getRemoteUserInformation(); - if (! array_key_exists($field, $_SERVER) || $_SERVER[$field] !== $originUsername) { - $this->removeAuthorization(); - } + if ($this->request === null) { + $this->request = Icinga::app()->getRequest(); } + return $this->request; } /** - * Whether the user is authenticated + * Get the response * - * @param bool $ignoreSession True to prevent session authentication - * - * @return bool + * @return \Icinga\Web\Response */ - public function isAuthenticated($ignoreSession = false) + public function getResponse() { - if ($this->user === null && ! $ignoreSession) { - $this->authenticateFromSession(); + if ($this->response === null) { + $this->response = Icinga::app()->getResponse(); } - return is_object($this->user); - } - - /** - * Whether an authenticated user has a given permission - * - * @param string $permission Permission name - * - * @return bool True if the user owns the given permission, false if not or if not authenticated - */ - public function hasPermission($permission) - { - if (! $this->isAuthenticated()) { - return false; - } - return $this->user->can($permission); + return $this->response; } /** @@ -188,15 +221,6 @@ class Manager return $this->user->getRestrictions($restriction); } - /** - * Purges the current authorization information and session - */ - public function removeAuthorization() - { - $this->user = null; - Session::getSession()->purge(); - } - /** * Returns the current user or null if no user is authenticated * @@ -208,13 +232,124 @@ class Manager } /** - * Getter for groups belonged to authenticated user + * Try to authenticate the user with the current session * - * @return array - * @see User::getGroups + * Authentication for externally-authenticated users will be revoked if the username changed or external + * authentication is no longer in effect */ - public function getGroups() + public function authenticateFromSession() { - return $this->user->getGroups(); + $this->user = Session::getSession()->get('user'); + if ($this->user !== null && $this->user->isExternalUser() === true) { + list($originUsername, $field) = $this->user->getExternalUserInformation(); + if (! array_key_exists($field, $_SERVER) || $_SERVER[$field] !== $originUsername) { + $this->removeAuthorization(); + } + } + } + + /** + * Attempt to authenticate a user from external user backends + * + * @return bool + */ + protected function authExternal() + { + $user = new User(''); + foreach ($this->getAuthChain() as $userBackend) { + if ($userBackend instanceof ExternalBackend) { + if ($userBackend->authenticate($user)) { + $this->setAuthenticated($user); + return true; + } + } + } + return false; + } + + /** + * Attempt to authenticate a user using HTTP authentication + * + * Supports only the Basic HTTP authentication scheme. XHR will be ignored. + * + * @return bool + */ + protected function authHttp() + { + if ($this->getRequest()->isXmlHttpRequest()) { + return false; + } + if (($header = $this->getRequest()->getHeader('Authorization')) === false) { + return false; + } + if (empty($header)) { + $this->challengeHttp(); + } + list($scheme) = explode(' ', $header, 2); + if ($scheme !== 'Basic') { + $this->challengeHttp(); + } + $authorization = substr($header, strlen('Basic ')); + $credentials = base64_decode($authorization); + $credentials = array_filter(explode(':', $credentials, 2)); + if (count($credentials) !== 2) { + // Deny empty username and/or password + $this->challengeHttp(); + } + $user = new User($credentials[0]); + $password = $credentials[1]; + if ($this->getAuthChain()->setSkipExternalBackends(true)->authenticate($user, $password)) { + $this->setAuthenticated($user, false); + $user->setIsHttpUser(true); + return true; + } else { + $this->challengeHttp(); + } + } + + /** + * Challenge client immediately for HTTP authentication + * + * Sends the response w/ the 401 Unauthorized status code and WWW-Authenticate header. + */ + protected function challengeHttp() + { + $response = $this->getResponse(); + $response->setHttpResponseCode(401); + $response->setHeader('WWW-Authenticate', 'Basic realm="Icinga Web 2"'); + $response->sendHeaders(); + exit(); + } + + /** + * Whether an authenticated user has a given permission + * + * @param string $permission Permission name + * + * @return bool True if the user owns the given permission, false if not or if not authenticated + */ + public function hasPermission($permission) + { + if (! $this->isAuthenticated()) { + return false; + } + return $this->user->can($permission); + } + + /** + * Writes the current user to the session + */ + public function persistCurrentUser() + { + Session::getSession()->set('user', $this->user)->refreshId(); + } + + /** + * Purges the current authorization information and session + */ + public function removeAuthorization() + { + $this->user = null; + Session::getSession()->purge(); } } diff --git a/library/Icinga/Authentication/AuthChain.php b/library/Icinga/Authentication/AuthChain.php index 24c12c9de..84e2a3eac 100644 --- a/library/Icinga/Authentication/AuthChain.php +++ b/library/Icinga/Authentication/AuthChain.php @@ -1,48 +1,183 @@ config = $config; + if ($config === null) { + try { + $this->config = Config::app(static::AUTHENTICATION_CONFIG); + } catch (NotReadableError $e) { + $this->config = new Config(); + $this->error = static::EPERM; + } + } else { + $this->config = $config; + } + } + + /** + * {@inheritdoc} + */ + public function authenticate(User $user, $password) + { + $this->error = null; + $backendsTried = 0; + $backendsWithError = 0; + foreach ($this as $backend) { + ++$backendsTried; + try { + $authenticated = $backend->authenticate($user, $password); + } catch (AuthenticationException $e) { + Logger::error($e); + ++$backendsWithError; + continue; + } + if ($authenticated) { + return true; + } + } + if ($backendsTried === 0) { + $this->error = static::EEMPTY; + } elseif ($backendsTried === $backendsWithError) { + $this->error = static::EFAIL; + } elseif ($backendsWithError) { + $this->error = static::ENOTALL; + } + return false; + } + + /** + * Get the last error code + * + * @return int|null + */ + public function getError() + { + return $this->error; + } + + /** + * Whether authentication had errors + * + * @return bool + */ + public function hasError() + { + return $this->error !== null; + } + + /** + * Get whether to skip external user backends on iteration + * + * @return bool + */ + public function getSkipExternalBackends() + { + return $this->skipExternalBackends; + } + + /** + * Set whether to skip external user backends on iteration + * + * @param bool $skipExternalBackends + * + * @return $this + */ + public function setSkipExternalBackends($skipExternalBackends = true) + { + $this->skipExternalBackends = (bool) $skipExternalBackends; + return $this; } /** * Rewind the chain * - * @return ConfigObject + * @return ConfigObject */ public function rewind() { @@ -51,9 +186,9 @@ class AuthChain implements Iterator } /** - * Return the current user backend + * Get the current user backend * - * @return UserBackend + * @return UserBackendInterface */ public function current() { @@ -61,9 +196,9 @@ class AuthChain implements Iterator } /** - * Return the key of the current user backend config + * Get the key of the current user backend config * - * @return string + * @return string */ public function key() { @@ -73,7 +208,7 @@ class AuthChain implements Iterator /** * Move forward to the next user backend config * - * @return ConfigObject + * @return ConfigObject */ public function next() { @@ -81,19 +216,20 @@ class AuthChain implements Iterator } /** - * Check if the current user backend is valid, i.e. it's enabled and the config is valid + * Check whether the current user backend is valid, i.e. it's enabled, not an external user backend and whether its + * config is valid * - * @return bool + * @return bool */ public function valid() { - if (!$this->config->valid()) { + if (! $this->config->valid()) { // Stop when there are no more backends to check return false; } $backendConfig = $this->config->current(); - if ((bool) $backendConfig->get('disabled', false) === true) { + if ((bool) $backendConfig->get('disabled', false)) { $this->next(); return $this->valid(); } @@ -104,15 +240,20 @@ class AuthChain implements Iterator } catch (ConfigurationError $e) { Logger::error( new ConfigurationError( - 'Cannot create authentication backend "%s". An exception was thrown:', - $name, - $e + 'Can\'t create authentication backend "%s". An exception was thrown:', $name, $e ) ); $this->next(); return $this->valid(); } + if ($this->getSkipExternalBackends() + && $backend instanceof ExternalBackend + ) { + $this->next(); + return $this->valid(); + } + $this->currentBackend = $backend; return true; } diff --git a/library/Icinga/Authentication/Authenticatable.php b/library/Icinga/Authentication/Authenticatable.php new file mode 100644 index 000000000..8c53e2e34 --- /dev/null +++ b/library/Icinga/Authentication/Authenticatable.php @@ -0,0 +1,21 @@ +conn = $conn; - } - - /** - * Test whether the given user exists - * - * @param User $user - * - * @return bool - */ - public function hasUser(User $user) - { - $select = new Zend_Db_Select($this->conn->getDbAdapter()); - $row = $select->from('icingaweb_user', array(new Zend_Db_Expr(1))) - ->where('name = ?', $user->getUsername()) - ->query()->fetchObject(); - - return ($row !== false) ? true : false; - } - - /** - * Add a new user - * - * @param string $username The name of the new user - * @param string $password The new user's password - * @param bool $active Whether the user is active - */ - public function addUser($username, $password, $active = true) - { - $passwordHash = $this->hashPassword($password); - - $stmt = $this->conn->getDbAdapter()->prepare( - 'INSERT INTO icingaweb_user VALUES (:name, :active, :password_hash, now(), DEFAULT);' - ); - $stmt->bindParam(':name', $username, PDO::PARAM_STR); - $stmt->bindParam(':active', $active, PDO::PARAM_INT); - $stmt->bindParam(':password_hash', $passwordHash, PDO::PARAM_LOB); - $stmt->execute(); - } - - /** - * Fetch the hashed password for the given user - * - * @param string $username The name of the user - * - * @return string - */ - protected function getPasswordHash($username) - { - $stmt = $this->conn->getDbAdapter()->prepare( - 'SELECT password_hash FROM icingaweb_user WHERE name = :name AND active = 1' - ); - $stmt->execute(array(':name' => $username)); - $stmt->bindColumn(1, $lob, PDO::PARAM_LOB); - $stmt->fetch(PDO::FETCH_BOUND); - return is_resource($lob) ? stream_get_contents($lob) : $lob; - } - - /** - * Authenticate the given user and return true on success, false on failure and throw an exception on error - * - * @param User $user - * @param string $password - * - * @return bool - * - * @throws AuthenticationException - */ - public function authenticate(User $user, $password) - { - try { - $passwordHash = $this->getPasswordHash($user->getUsername()); - $passwordSalt = $this->getSalt($passwordHash); - $hashToCompare = $this->hashPassword($password, $passwordSalt); - return $hashToCompare === $passwordHash; - } catch (Exception $e) { - throw new AuthenticationException( - 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', - $user->getUsername(), - $this->getName(), - $e - ); - } - } - - /** - * Extract salt from the given password hash - * - * @param string $hash The hashed password - * - * @return string - */ - protected function getSalt($hash) - { - return substr($hash, strlen(self::HASH_ALGORITHM), self::SALT_LENGTH); - } - - /** - * Return a random salt - * - * The returned salt is safe to be used for hashing a user's password - * - * @return string - */ - protected function generateSalt() - { - return openssl_random_pseudo_bytes(self::SALT_LENGTH); - } - - /** - * Hash a password - * - * @param string $password - * @param string $salt - * - * @return string - */ - protected function hashPassword($password, $salt = null) - { - return crypt($password, self::HASH_ALGORITHM . ($salt !== null ? $salt : $this->generateSalt())); - } - - /** - * Get the number of users available - * - * @return int - */ - public function count() - { - $select = new Zend_Db_Select($this->conn->getDbAdapter()); - $row = $select->from( - 'icingaweb_user', - array('count' => 'COUNT(*)') - )->query()->fetchObject(); - - return ($row !== false) ? $row->count : 0; - } - - /** - * Return the names of all available users - * - * @return array - */ - public function listUsers() - { - $query = $this->conn->select()->from('icingaweb_user', array('name')); - - $users = array(); - foreach ($query->fetchAll() as $row) { - $users[] = $row->name; - } - - return $users; - } -} diff --git a/library/Icinga/Authentication/Backend/DbUserGroupBackend.php b/library/Icinga/Authentication/Backend/DbUserGroupBackend.php deleted file mode 100644 index 15a77d77d..000000000 --- a/library/Icinga/Authentication/Backend/DbUserGroupBackend.php +++ /dev/null @@ -1,63 +0,0 @@ -conn = $conn; - } - - /** - * (non-PHPDoc) - * @see UserGroupBackend::getMemberships() For the method documentation. - */ - public function getMemberships(User $user) - { - $groups = array(); - $groupsStmt = $this->conn->getDbAdapter() - ->select() - ->from($this->conn->getTablePrefix() . 'group', array('name', 'parent')) - ->query(); - foreach ($groupsStmt as $group) { - $groups[$group->name] = $group->parent; - } - $memberships = array(); - $membershipsStmt = $this->conn->getDbAdapter() - ->select() - ->from($this->conn->getTablePrefix() . 'group_membership', array('group_name')) - ->where('username = ?', $user->getUsername()) - ->query(); - foreach ($membershipsStmt as $membership) { - $memberships[] = $membership->group_name; - $parent = $groups[$membership->group_name]; - while (isset($parent)) { - $memberships[] = $parent; - $parent = $groups[$parent]; - } - } - return $memberships; - } -} diff --git a/library/Icinga/Authentication/Backend/IniUserGroupBackend.php b/library/Icinga/Authentication/Backend/IniUserGroupBackend.php deleted file mode 100644 index 45d36e833..000000000 --- a/library/Icinga/Authentication/Backend/IniUserGroupBackend.php +++ /dev/null @@ -1,65 +0,0 @@ -config = $config; - } - - /** - * (non-PHPDoc) - * @see UserGroupBackend::getMemberships() For the method documentation. - */ - public function getMemberships(User $user) - { - $username = strtolower($user->getUsername()); - $groups = array(); - foreach ($this->config as $name => $section) { - if (empty($section->users)) { - throw new ConfigurationError( - 'Membership section \'%s\' in \'%s\' is missing the \'users\' section', - $name, - $this->config->getConfigFile() - ); - } - if (empty($section->groups)) { - throw new ConfigurationError( - 'Membership section \'%s\' in \'%s\' is missing the \'groups\' section', - $name, - $this->config->getConfigFile() - ); - } - $users = array_map('strtolower', String::trimSplit($section->users)); - if (in_array($username, $users)) { - $groups = array_merge($groups, array_diff(String::trimSplit($section->groups), $groups)); - } - } - return $groups; - } -} diff --git a/library/Icinga/Authentication/Backend/LdapUserBackend.php b/library/Icinga/Authentication/Backend/LdapUserBackend.php deleted file mode 100644 index 519dd3c48..000000000 --- a/library/Icinga/Authentication/Backend/LdapUserBackend.php +++ /dev/null @@ -1,232 +0,0 @@ -conn = $conn; - $this->baseDn = trim($baseDn) !== '' ? $baseDn : $conn->getDN(); - $this->userClass = $userClass; - $this->userNameAttribute = $userNameAttribute; - $this->groupOptions = $groupOptions; - } - - /** - * @return \Icinga\Protocol\Ldap\Query - */ - protected function selectUsers() - { - return $this->conn->select()->setBase($this->baseDn)->from( - $this->userClass, - array( - $this->userNameAttribute - ) - ); - } - - /** - * Create query - * - * @param string $username - * - * @return \Icinga\Protocol\Ldap\Query - **/ - protected function selectUser($username) - { - return $this->selectUsers()->where( - $this->userNameAttribute, - str_replace('*', '', $username) - ); - } - - /** - * Probe the backend to test if authentication is possible - * - * Try to bind to the backend and query all available users to check if: - *
      - *
    • User connection credentials are correct and the bind is possible
    • - *
    • At least one user exists
    • - *
    • The specified userClass has the property specified by userNameAttribute
    • - *
    - * - * @throws AuthenticationException When authentication is not possible - */ - public function assertAuthenticationPossible() - { - try { - $q = $this->conn->select()->setBase($this->baseDn)->from($this->userClass); - $result = $q->fetchRow(); - } catch (LdapException $e) { - throw new AuthenticationException('Connection not possible.', $e); - } - - if (! isset($result)) { - throw new AuthenticationException( - 'No objects with objectClass="%s" in DN="%s" found.', - $this->userClass, - $this->baseDn - ); - } - - if (! isset($result->{$this->userNameAttribute})) { - throw new AuthenticationException( - 'UserNameAttribute "%s" not existing in objectClass="%s"', - $this->userNameAttribute, - $this->userClass - ); - } - } - - /** - * Retrieve the user groups - * - * @TODO: Subject to change, see #7343 - * - * @param string $dn - * - * @return array - */ - public function getGroups($dn) - { - if (empty($this->groupOptions) || ! isset($this->groupOptions['group_base_dn'])) { - return array(); - } - - $q = $this->conn->select() - ->setBase($this->groupOptions['group_base_dn']) - ->from( - $this->groupOptions['group_class'], - array($this->groupOptions['group_attribute']) - ) - ->where( - $this->groupOptions['group_member_attribute'], - $dn - ); - - $result = $this->conn->fetchAll($q); - - $groups = array(); - - foreach ($result as $group) { - $groups[] = $group->{$this->groupOptions['group_attribute']}; - } - - return $groups; - } - - /** - * Test whether the given user exists - * - * @param User $user - * - * @return bool - * @throws AuthenticationException - */ - public function hasUser(User $user) - { - $username = $user->getUsername(); - return $this->conn->fetchOne($this->selectUser($username)) === $username; - } - - /** - * Authenticate the given user and return true on success, false on failure and null on error - * - * @param User $user - * @param string $password - * @param boolean $healthCheck Perform additional health checks to generate more useful exceptions in case - * of a configuration or backend error - * - * @return bool True when the authentication was successful, false when the username - * or password was invalid - * @throws AuthenticationException When an error occurred during authentication and authentication is not possible - */ - public function authenticate(User $user, $password, $healthCheck = true) - { - if ($healthCheck) { - try { - $this->assertAuthenticationPossible(); - } catch (AuthenticationException $e) { - // Authentication not possible - throw new AuthenticationException( - 'Authentication against backend "%s" not possible.', - $this->getName(), - $e - ); - } - } - if (! $this->hasUser($user)) { - return false; - } - try { - $userDn = $this->conn->fetchDN($this->selectUser($user->getUsername())); - $authenticated = $this->conn->testCredentials( - $userDn, - $password - ); - if ($authenticated) { - $groups = $this->getGroups($userDn); - if ($groups !== null) { - $user->setGroups($groups); - } - } - return $authenticated; - } catch (LdapException $e) { - // Error during authentication of this specific user - throw new AuthenticationException( - 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', - $user->getUsername(), - $this->getName(), - $e - ); - } - } - - /** - * Get the number of users available - * - * @return int - */ - public function count() - { - return $this->conn->count($this->selectUsers()); - } - - /** - * Return the names of all available users - * - * @return array - */ - public function listUsers() - { - $users = array(); - foreach ($this->selectUsers()->fetchAll() as $row) { - $users[] = $row->{$this->userNameAttribute}; - } - return $users; - } -} diff --git a/library/Icinga/Authentication/User/DbUserBackend.php b/library/Icinga/Authentication/User/DbUserBackend.php new file mode 100644 index 000000000..48662a987 --- /dev/null +++ b/library/Icinga/Authentication/User/DbUserBackend.php @@ -0,0 +1,273 @@ + array( + 'user' => 'name COLLATE utf8_general_ci', + 'user_name' => 'name', + 'is_active' => 'active', + 'created_at' => 'UNIX_TIMESTAMP(ctime)', + 'last_modified' => 'UNIX_TIMESTAMP(mtime)' + ) + ); + + /** + * The statement columns being provided + * + * @var array + */ + protected $statementColumns = array( + 'user' => array( + 'password' => 'password_hash', + 'created_at' => 'ctime', + 'last_modified' => 'mtime' + ) + ); + + /** + * The columns which are not permitted to be queried + * + * @var array + */ + protected $filterColumns = array('user'); + + /** + * The default sort rules to be applied on a query + * + * @var array + */ + protected $sortRules = array( + 'user_name' => array( + 'columns' => array( + 'is_active desc', + 'user_name' + ) + ) + ); + + /** + * The value conversion rules to apply on a query or statement + * + * @var array + */ + protected $conversionRules = array( + 'user' => array( + 'password' + ) + ); + + /** + * Initialize this database user backend + */ + protected function init() + { + if (! $this->ds->getTablePrefix()) { + $this->ds->setTablePrefix('icingaweb_'); + } + } + + /** + * Insert a table row with the given data + * + * @param string $table + * @param array $bind + */ + public function insert($table, array $bind) + { + $bind['created_at'] = date('Y-m-d H:i:s'); + $this->ds->insert( + $this->prependTablePrefix($table), + $this->requireStatementColumns($table, $bind), + array( + 'active' => PDO::PARAM_INT, + 'password_hash' => PDO::PARAM_LOB + ) + ); + } + + /** + * Update table rows with the given data, optionally limited by using a filter + * + * @param string $table + * @param array $bind + * @param Filter $filter + */ + public function update($table, array $bind, Filter $filter = null) + { + $bind['last_modified'] = date('Y-m-d H:i:s'); + if ($filter) { + $filter = $this->requireFilter($table, $filter); + } + + $this->ds->update( + $this->prependTablePrefix($table), + $this->requireStatementColumns($table, $bind), + $filter, + array( + 'active' => PDO::PARAM_INT, + 'password_hash' => PDO::PARAM_LOB + ) + ); + } + + /** + * Hash and return the given password + * + * @param string $value + * + * @return string + */ + protected function persistPassword($value) + { + return $this->hashPassword($value); + } + + /** + * Fetch the hashed password for the given user + * + * @param string $username The name of the user + * + * @return string + */ + protected function getPasswordHash($username) + { + if ($this->ds->getDbType() === 'pgsql') { + // Since PostgreSQL version 9.0 the default value for bytea_output is 'hex' instead of 'escape' + $columns = array('password_hash' => 'ENCODE(password_hash, \'escape\')'); + } else { + $columns = array('password_hash'); + } + + $query = $this->ds->select() + ->from($this->prependTablePrefix('user'), $columns) + ->where('name', $username) + ->where('active', true); + $statement = $this->ds->getDbAdapter()->prepare($query->getSelectQuery()); + $statement->execute(); + $statement->bindColumn(1, $lob, PDO::PARAM_LOB); + $statement->fetch(PDO::FETCH_BOUND); + if (is_resource($lob)) { + $lob = stream_get_contents($lob); + } + + return $this->ds->getDbType() === 'pgsql' ? pg_unescape_bytea($lob) : $lob; + } + + /** + * Authenticate the given user + * + * @param User $user + * @param string $password + * + * @return bool True on success, false on failure + * + * @throws AuthenticationException In case authentication is not possible due to an error + */ + public function authenticate(User $user, $password) + { + try { + $passwordHash = $this->getPasswordHash($user->getUsername()); + $passwordSalt = $this->getSalt($passwordHash); + $hashToCompare = $this->hashPassword($password, $passwordSalt); + return $hashToCompare === $passwordHash; + } catch (Exception $e) { + throw new AuthenticationException( + 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', + $user->getUsername(), + $this->getName(), + $e + ); + } + } + + /** + * Extract salt from the given password hash + * + * @param string $hash The hashed password + * + * @return string + */ + protected function getSalt($hash) + { + return substr($hash, strlen(self::HASH_ALGORITHM), self::SALT_LENGTH); + } + + /** + * Return a random salt + * + * The returned salt is safe to be used for hashing a user's password + * + * @return string + */ + protected function generateSalt() + { + return openssl_random_pseudo_bytes(self::SALT_LENGTH); + } + + /** + * Hash a password + * + * @param string $password + * @param string $salt + * + * @return string + */ + protected function hashPassword($password, $salt = null) + { + return crypt($password, self::HASH_ALGORITHM . ($salt !== null ? $salt : $this->generateSalt())); + } + + /** + * Inspect this object to gain extended information about its health + * + * @return Inspection The inspection result + */ + public function inspect() + { + $insp = new Inspection('Db User Backend'); + $insp->write($this->ds->inspect()); + try { + $users = $this->select()->where('is_active', true)->count(); + if ($users >= 1) { + $insp->write(sprintf('%s active users', $users)); + } else { + return $insp->error('0 active users', $users); + } + } catch (Exception $e) { + $insp->error(sprintf('Query failed: %s', $e->getMessage())); + } + return $insp; + } +} diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/User/ExternalBackend.php similarity index 60% rename from library/Icinga/Authentication/Backend/AutoLoginBackend.php rename to library/Icinga/Authentication/User/ExternalBackend.php index b4a70bd4f..62b1ea40e 100644 --- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php +++ b/library/Icinga/Authentication/User/ExternalBackend.php @@ -1,27 +1,32 @@ name = $name; + return $this; } /** - * Test whether the given user exists - * - * @param User $user - * - * @return bool + * {@inheritdoc} */ - public function hasUser(User $user) + public function getName() + { + return $this->name; + } + + + /** + * {@inheritdoc} + */ + public function authenticate(User $user, $password = null) { if (isset($_SERVER['REMOTE_USER'])) { $username = $_SERVER['REMOTE_USER']; - $user->setRemoteUserInformation($username, 'REMOTE_USER'); + $user->setExternalUserInformation($username, 'REMOTE_USER'); + if ($this->stripUsernameRegexp) { $stripped = preg_replace($this->stripUsernameRegexp, '', $username); if ($stripped !== false) { @@ -62,23 +70,11 @@ class AutoLoginBackend extends UserBackend $username = $stripped; } } + $user->setUsername($username); return true; } return false; } - - /** - * Authenticate - * - * @param User $user - * @param string $password - * - * @return bool - */ - public function authenticate(User $user, $password = null) - { - return $this->hasUser($user); - } } diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php new file mode 100644 index 000000000..df2c0a8e0 --- /dev/null +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -0,0 +1,409 @@ + array( + 'columns' => array( + 'is_active desc', + 'user_name' + ) + ) + ); + + /** + * Set the base DN to use for a query + * + * @param string $baseDn + * + * @return $this + */ + public function setBaseDn($baseDn) + { + if (($baseDn = trim($baseDn))) { + $this->baseDn = $baseDn; + } + + return $this; + } + + /** + * Return the base DN to use for a query + * + * @return string + */ + public function getBaseDn() + { + return $this->baseDn; + } + + /** + * Set the objectClass where to look for users + * + * Sets also the base table name for the underlying repository. + * + * @param string $userClass + * + * @return $this + */ + public function setUserClass($userClass) + { + $this->baseTable = $this->userClass = $this->getNormedAttribute($userClass); + return $this; + } + + /** + * Return the objectClass where to look for users + * + * @return string + */ + public function getUserClass() + { + return $this->userClass; + } + + /** + * Set the attribute name where to find a user's name + * + * @param string $userNameAttribute + * + * @return $this + */ + public function setUserNameAttribute($userNameAttribute) + { + $this->userNameAttribute = $this->getNormedAttribute($userNameAttribute); + return $this; + } + + /** + * Return the attribute name where to find a user's name + * + * @return string + */ + public function getUserNameAttribute() + { + return $this->userNameAttribute; + } + + /** + * Set the custom LDAP filter to apply on search queries + * + * @param string $filter + * + * @return $this + */ + public function setFilter($filter) + { + if (($filter = trim($filter))) { + if ($filter[0] === '(') { + $filter = substr($filter, 1, -1); + } + + $this->filter = $filter; + } + + return $this; + } + + /** + * Return the custom LDAP filter to apply on search queries + * + * @return string + */ + public function getFilter() + { + return $this->filter; + } + + /** + * Apply the given configuration to this backend + * + * @param ConfigObject $config + * + * @return $this + */ + public function setConfig(ConfigObject $config) + { + return $this + ->setBaseDn($config->base_dn) + ->setUserClass($config->user_class) + ->setUserNameAttribute($config->user_name_attribute) + ->setFilter($config->filter); + } + + /** + * Return a new query for the given columns + * + * @param array $columns The desired columns, if null all columns will be queried + * + * @return RepositoryQuery + */ + public function select(array $columns = null) + { + $query = parent::select($columns); + $query->getQuery()->setBase($this->baseDn); + if ($this->filter) { + $query->getQuery()->where(new Expression($this->filter)); + } + + return $query; + } + + /** + * Initialize this repository's query columns + * + * @return array + * + * @throws ProgrammingError In case either $this->userNameAttribute or $this->userClass has not been set yet + */ + protected function initializeQueryColumns() + { + if ($this->userClass === null) { + throw new ProgrammingError('It is required to set the objectClass where to look for users first'); + } + if ($this->userNameAttribute === null) { + throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first'); + } + + if ($this->ds->getCapabilities()->isActiveDirectory()) { + $isActiveAttribute = 'userAccountControl'; + $createdAtAttribute = 'whenCreated'; + $lastModifiedAttribute = 'whenChanged'; + } else { + // TODO(jom): Elaborate whether it is possible to add dynamic support for the ppolicy + $isActiveAttribute = 'shadowExpire'; + + $createdAtAttribute = 'createTimestamp'; + $lastModifiedAttribute = 'modifyTimestamp'; + } + + return array( + $this->userClass => array( + 'user' => $this->userNameAttribute, + 'user_name' => $this->userNameAttribute, + 'is_active' => $isActiveAttribute, + 'created_at' => $createdAtAttribute, + 'last_modified' => $lastModifiedAttribute + ) + ); + } + + /** + * Initialize this repository's conversion rules + * + * @return array + * + * @throws ProgrammingError In case $this->userClass has not been set yet + */ + protected function initializeConversionRules() + { + if ($this->userClass === null) { + throw new ProgrammingError('It is required to set the objectClass where to look for users first'); + } + + if ($this->ds->getCapabilities()->isActiveDirectory()) { + $stateConverter = 'user_account_control'; + } else { + $stateConverter = 'shadow_expire'; + } + + return array( + $this->userClass => array( + 'is_active' => $stateConverter, + 'created_at' => 'generalized_time', + 'last_modified' => 'generalized_time' + ) + ); + } + + /** + * Return whether the given userAccountControl value defines that a user is permitted to login + * + * @param string|null $value + * + * @return bool + */ + protected function retrieveUserAccountControl($value) + { + if ($value === null) { + return $value; + } + + $ADS_UF_ACCOUNTDISABLE = 2; + return ((int) $value & $ADS_UF_ACCOUNTDISABLE) === 0; + } + + /** + * Return whether the given shadowExpire value defines that a user is permitted to login + * + * @param string|null $value + * + * @return bool + */ + protected function retrieveShadowExpire($value) + { + if ($value === null) { + return $value; + } + + $now = new DateTime(); + $bigBang = clone $now; + $bigBang->setTimestamp(0); + return ((int) $value) >= $bigBang->diff($now)->days; + } + + /** + + * @param Inspection $info Optional inspection to fill with diagnostic info + * + * @throws AuthenticationException When authentication is not possible + */ + public function assertAuthenticationPossible(Inspection $insp = null) + { + + } + + /** + * Authenticate the given user + * + * @param User $user + * @param string $password + * + * @return bool True on success, false on failure + * + * @throws AuthenticationException In case authentication is not possible due to an error + */ + public function authenticate(User $user, $password) + { + try { + $userDn = $this + ->select() + ->where('user_name', str_replace('*', '', $user->getUsername())) + ->getQuery() + ->setUsePagedResults(false) + ->fetchDn(); + + if ($userDn === null) { + return false; + } + + return $this->ds->testCredentials($userDn, $password); + } catch (LdapException $e) { + throw new AuthenticationException( + 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', + $user->getUsername(), + $this->getName(), + $e + ); + } + } + + /** + * Inspect if this LDAP User Backend is working as expected by probing the backend + * and testing if thea uthentication is possible + * + * Try to bind to the backend and fetch a single user to check if: + *
      + *
    • Connection credentials are correct and the bind is possible
    • + *
    • At least one user exists
    • + *
    • The specified userClass has the property specified by userNameAttribute
    • + *
    + * + * @return Inspection Inspection result + */ + public function inspect() + { + $result = new Inspection('Ldap User Backend'); + + // inspect the used connection to get more diagnostic info in case the connection is not working + $result->write($this->ds->inspect()); + try { + try { + $res = $this->select()->fetchRow(); + } catch (LdapException $e) { + throw new AuthenticationException('Connection not possible', $e); + } + $result->write('Searching for: ' . sprintf( + 'objectClass "%s" in DN "%s" (Filter: %s)', + $this->userClass, + $this->baseDn ?: $this->ds->getDn(), + $this->filter ?: 'None' + )); + if ($res === false) { + throw new AuthenticationException('Error, no users found in backend'); + } + $result->write(sprintf('%d users found in backend', $this->select()->count())); + if (! isset($res->user_name)) { + throw new AuthenticationException( + 'UserNameAttribute "%s" not existing in objectClass "%s"', + $this->userNameAttribute, + $this->userClass + ); + } + } catch (AuthenticationException $e) { + if (($previous = $e->getPrevious()) !== null) { + $result->error($previous->getMessage()); + } else { + $result->error($e->getMessage()); + } + } catch (Exception $e) { + $result->error(sprintf('Unable to validate authentication: %s', $e->getMessage())); + } + return $result; + } +} diff --git a/library/Icinga/Authentication/User/UserBackend.php b/library/Icinga/Authentication/User/UserBackend.php new file mode 100644 index 000000000..3b8e21071 --- /dev/null +++ b/library/Icinga/Authentication/User/UserBackend.php @@ -0,0 +1,234 @@ +getModuleManager()->getLoadedModules() as $module) { + foreach ($module->getUserBackends() as $identifier => $className) { + if (array_key_exists($identifier, $providedBy)) { + Logger::warning( + 'Cannot register user backend of type "%s" provided by module "%s".' + . ' The type is already provided by module "%s"', + $identifier, + $module->getName(), + $providedBy[$identifier] + ); + } elseif (in_array($identifier, static::$defaultBackends)) { + Logger::warning( + 'Cannot register user backend of type "%s" provided by module "%s".' + . ' The type is a default type provided by Icinga Web 2', + $identifier, + $module->getName() + ); + } else { + $providedBy[$identifier] = $module->getName(); + static::$customBackends[$identifier] = $className; + } + } + } + } + + /** + * Return the class for the given custom user backend + * + * @param string $identifier The identifier of the custom user backend + * + * @return string|null The name of the class or null in case there was no + * backend found with the given identifier + * + * @throws ConfigurationError In case the class associated to the given identifier does not exist + */ + protected static function getCustomUserBackend($identifier) + { + static::registerCustomUserBackends(); + if (array_key_exists($identifier, static::$customBackends)) { + $className = static::$customBackends[$identifier]; + if (! class_exists($className)) { + throw new ConfigurationError( + 'Cannot utilize user backend of type "%s". Class "%s" does not exist', + $identifier, + $className + ); + } + + return $className; + } + } + + /** + * Create and return a user backend with the given name and given configuration applied to it + * + * @param string $name + * @param ConfigObject $backendConfig + * + * @return UserBackendInterface + * + * @throws ConfigurationError + */ + public static function create($name, ConfigObject $backendConfig = null) + { + if ($backendConfig === null) { + self::assertBackendsExist(); + if (self::$backends->hasSection($name)) { + $backendConfig = self::$backends->getSection($name); + } else { + throw new ConfigurationError('User backend "%s" does not exist', $name); + } + } + + if ($backendConfig->name !== null) { + $name = $backendConfig->name; + } + + if (! ($backendType = strtolower($backendConfig->backend))) { + throw new ConfigurationError( + 'Authentication configuration for user backend "%s" is missing the \'backend\' directive', + $name + ); + } + if ($backendType === 'external') { + $backend = new ExternalBackend($backendConfig); + $backend->setName($name); + return $backend; + } + if (in_array($backendType, static::$defaultBackends)) { + // The default backend check is the first one because of performance reasons: + // Do not attempt to load a custom user backend unless it's actually required + } elseif (($customClass = static::getCustomUserBackend($backendType)) !== null) { + $backend = new $customClass($backendConfig); + if (! is_a($backend, 'Icinga\Authentication\User\UserBackendInterface')) { + throw new ConfigurationError( + 'Cannot utilize user backend of type "%s". Class "%s" does not implement UserBackendInterface', + $backendType, + $customClass + ); + } + + $backend->setName($name); + return $backend; + } else { + throw new ConfigurationError( + 'Authentication configuration for user backend "%s" defines an invalid backend type.' + . ' Backend type "%s" is not supported', + $name, + $backendType + ); + } + + if ($backendConfig->resource === null) { + throw new ConfigurationError( + 'Authentication configuration for user backend "%s" is missing the \'resource\' directive', + $name + ); + } + + $resource = ResourceFactory::create($backendConfig->resource); + switch ($backendType) { + case 'db': + $backend = new DbUserBackend($resource); + break; + case 'msldap': + $backend = new LdapUserBackend($resource); + $backend->setBaseDn($backendConfig->base_dn); + $backend->setUserClass($backendConfig->get('user_class', 'user')); + $backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'sAMAccountName')); + $backend->setFilter($backendConfig->filter); + break; + case 'ldap': + $backend = new LdapUserBackend($resource); + $backend->setBaseDn($backendConfig->base_dn); + $backend->setUserClass($backendConfig->get('user_class', 'inetOrgPerson')); + $backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid')); + $backend->setFilter($backendConfig->filter); + break; + } + + $backend->setName($name); + return $backend; + } +} diff --git a/library/Icinga/Authentication/User/UserBackendInterface.php b/library/Icinga/Authentication/User/UserBackendInterface.php new file mode 100644 index 000000000..6903e880d --- /dev/null +++ b/library/Icinga/Authentication/User/UserBackendInterface.php @@ -0,0 +1,29 @@ +name = $name; - return $this; - } - - /** - * Getter for the backend's name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - public static function create($name, ConfigObject $backendConfig) - { - if ($backendConfig->name !== null) { - $name = $backendConfig->name; - } - if (isset($backendConfig->class)) { - // Use a custom backend class, this is only useful for testing - if (!class_exists($backendConfig->class)) { - throw new ConfigurationError( - 'Authentication configuration for backend "%s" defines an invalid backend class.' - . ' Backend class "%s" not found', - $name, - $backendConfig->class - ); - } - return new $backendConfig->class($backendConfig); - } - if (($backendType = $backendConfig->backend) === null) { - throw new ConfigurationError( - 'Authentication configuration for backend "%s" is missing the backend directive', - $name - ); - } - $backendType = strtolower($backendType); - if ($backendType === 'autologin') { - $backend = new AutoLoginBackend($backendConfig); - $backend->setName($name); - return $backend; - } - if ($backendConfig->resource === null) { - throw new ConfigurationError( - 'Authentication configuration for backend "%s" is missing the resource directive', - $name - ); - } - try { - $resourceConfig = ResourceFactory::getResourceConfig($backendConfig->resource); - } catch (ProgrammingError $e) { - throw new ConfigurationError( - 'Resources not set up. Please contact your Icinga Web administrator' - ); - } - $resource = ResourceFactory::createResource($resourceConfig); - switch ($backendType) { - case 'db': - $backend = new DbUserBackend($resource); - break; - case 'msldap': - $groupOptions = array( - 'group_base_dn' => $backendConfig->group_base_dn, - 'group_attribute' => $backendConfig->group_attribute, - 'group_member_attribute' => $backendConfig->group_member_attribute, - 'group_class' => $backendConfig->group_class - ); - $backend = new LdapUserBackend( - $resource, - $backendConfig->get('user_class', 'user'), - $backendConfig->get('user_name_attribute', 'sAMAccountName'), - $backendConfig->get('base_dn', $resource->getDN()), - $groupOptions - ); - break; - case 'ldap': - if ($backendConfig->user_class === null) { - throw new ConfigurationError( - 'Authentication configuration for backend "%s" is missing the user_class directive', - $name - ); - } - if ($backendConfig->user_name_attribute === null) { - throw new ConfigurationError( - 'Authentication configuration for backend "%s" is missing the user_name_attribute directive', - $name - ); - } - $groupOptions = array( - 'group_base_dn' => $backendConfig->group_base_dn, - 'group_attribute' => $backendConfig->group_attribute, - 'group_member_attribute' => $backendConfig->group_member_attribute, - 'group_class' => $backendConfig->group_class - ); - $backend = new LdapUserBackend( - $resource, - $backendConfig->user_class, - $backendConfig->user_name_attribute, - $backendConfig->get('base_dn', $resource->getDN()), - $groupOptions - ); - break; - default: - throw new ConfigurationError( - 'Authentication configuration for backend "%s" defines an invalid backend type.' - . ' Backend type "%s" is not supported', - $name, - $backendType - ); - } - $backend->setName($name); - return $backend; - } - - /** - * Test whether the given user exists - * - * @param User $user - * - * @return bool - */ - abstract public function hasUser(User $user); - - /** - * Authenticate - * - * @param User $user - * @param string $password - * - * @return bool - */ - abstract public function authenticate(User $user, $password); -} diff --git a/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php new file mode 100644 index 000000000..0fd3fda14 --- /dev/null +++ b/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php @@ -0,0 +1,264 @@ + array( + 'group_id' => 'g.id', + 'group' => 'g.name COLLATE utf8_general_ci', + 'group_name' => 'g.name', + 'parent' => 'g.parent', + 'created_at' => 'UNIX_TIMESTAMP(g.ctime)', + 'last_modified' => 'UNIX_TIMESTAMP(g.mtime)' + ), + 'group_membership' => array( + 'group_id' => 'gm.group_id', + 'user' => 'gm.username COLLATE utf8_general_ci', + 'user_name' => 'gm.username', + 'created_at' => 'UNIX_TIMESTAMP(gm.ctime)', + 'last_modified' => 'UNIX_TIMESTAMP(gm.mtime)' + ) + ); + + /** + * The table aliases being applied + * + * @var array + */ + protected $tableAliases = array( + 'group' => 'g', + 'group_membership' => 'gm' + ); + + /** + * The statement columns being provided + * + * @var array + */ + protected $statementColumns = array( + 'group' => array( + 'group_id' => 'id', + 'group_name' => 'name', + 'parent' => 'parent', + 'created_at' => 'ctime', + 'last_modified' => 'mtime' + ), + 'group_membership' => array( + 'group_id' => 'group_id', + 'group_name' => 'group_id', + 'user_name' => 'username', + 'created_at' => 'ctime', + 'last_modified' => 'mtime' + ) + ); + + /** + * The columns which are not permitted to be queried + * + * @var array + */ + protected $filterColumns = array('group', 'user'); + + /** + * The value conversion rules to apply on a query or statement + * + * @var array + */ + protected $conversionRules = array( + 'group' => array( + 'parent' => 'group_id' + ), + 'group_membership' => array( + 'group_name' => 'group_id' + ) + ); + + /** + * Initialize this database user group backend + */ + protected function init() + { + if (! $this->ds->getTablePrefix()) { + $this->ds->setTablePrefix('icingaweb_'); + } + } + + /** + * Insert a table row with the given data + * + * @param string $table + * @param array $bind + */ + public function insert($table, array $bind) + { + $bind['created_at'] = date('Y-m-d H:i:s'); + parent::insert($table, $bind); + } + + /** + * Update table rows with the given data, optionally limited by using a filter + * + * @param string $table + * @param array $bind + * @param Filter $filter + */ + public function update($table, array $bind, Filter $filter = null) + { + $bind['last_modified'] = date('Y-m-d H:i:s'); + parent::update($table, $bind, $filter); + } + + /** + * Delete table rows, optionally limited by using a filter + * + * @param string $table + * @param Filter $filter + */ + public function delete($table, Filter $filter = null) + { + if ($table === 'group') { + parent::delete('group_membership', $filter); + $idQuery = $this->select(array('group_id')); + if ($filter !== null) { + $idQuery->applyFilter($filter); + } + + $this->update('group', array('parent' => null), Filter::where('parent', $idQuery->fetchColumn())); + } + + parent::delete($table, $filter); + } + + /** + * Return the groups the given user is a member of + * + * @param User $user + * + * @return array + */ + public function getMemberships(User $user) + { + $groupQuery = $this->ds + ->select() + ->from( + array('g' => $this->prependTablePrefix('group')), + array( + 'group_name' => 'g.name', + 'parent_name' => 'gg.name' + ) + )->joinLeft( + array('gg' => $this->prependTablePrefix('group')), + 'g.parent = gg.id', + array() + ); + + $groups = array(); + foreach ($groupQuery as $group) { + $groups[$group->group_name] = $group->parent_name; + } + + $membershipQuery = $this + ->select() + ->from('group_membership', array('group_name')) + ->where('user_name', $user->getUsername()); + + $memberships = array(); + foreach ($membershipQuery as $membership) { + $memberships[] = $membership->group_name; + $parent = $groups[$membership->group_name]; + while ($parent !== null) { + $memberships[] = $parent; + // Usually a parent is an existing group, but since we do not have a constraint on our table.. + $parent = isset($groups[$parent]) ? $groups[$parent] : null; + } + } + + return $memberships; + } + + /** + * Join group into group_membership + * + * @param RepositoryQuery $query + */ + protected function joinGroup(RepositoryQuery $query) + { + $query->getQuery()->join( + $this->requireTable('group'), + 'gm.group_id = g.id', + array() + ); + } + + /** + * Join group_membership into group + * + * @param RepositoryQuery $query + */ + protected function joinGroupMembership(RepositoryQuery $query) + { + $query->getQuery()->join( + $this->requireTable('group_membership'), + 'g.id = gm.group_id', + array() + ); + } + + /** + * Fetch and return the corresponding id for the given group's name + * + * @param string|array $groupName + * + * @return int + * + * @throws NotFoundError + */ + protected function persistGroupId($groupName) + { + if (! $groupName || empty($groupName) || is_numeric($groupName)) { + return $groupName; + } + + if (is_array($groupName)) { + if (is_numeric($groupName[0])) { + return $groupName; // In case the array contains mixed types... + } + + $groupIds = $this->ds + ->select() + ->from($this->prependTablePrefix('group'), array('id')) + ->where('name', $groupName) + ->fetchColumn(); + if (empty($groupIds)) { + throw new NotFoundError('No groups found matching one of: %s', implode(', ', $groupName)); + } + + return $groupIds; + } + + $groupId = $this->ds + ->select() + ->from($this->prependTablePrefix('group'), array('id')) + ->where('name', $groupName) + ->fetchOne(); + if ($groupId === false) { + throw new NotFoundError('Group "%s" does not exist', $groupName); + } + + return $groupId; + } +} diff --git a/library/Icinga/Authentication/UserGroup/IniUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/IniUserGroupBackend.php new file mode 100644 index 000000000..53eadc147 --- /dev/null +++ b/library/Icinga/Authentication/UserGroup/IniUserGroupBackend.php @@ -0,0 +1,121 @@ + array( + 'group' => 'name', + 'group_name' => 'name', + 'parent' => 'parent', + 'created_at' => 'ctime', + 'last_modified' => 'mtime', + 'users' + ) + ); + + /** + * The columns which are not permitted to be queried + * + * @var array + */ + protected $filterColumns = array('group'); + + /** + * The value conversion rules to apply on a query or statement + * + * @var array + */ + protected $conversionRules = array( + 'groups' => array( + 'created_at' => 'date_time', + 'last_modified' => 'date_time', + 'users' => 'comma_separated_string' + ) + ); + + /** + * Initialize this ini user group backend + */ + protected function init() + { + $this->ds->getConfigObject()->setKeyColumn('name'); + } + + /** + * Add a new group to this backend + * + * @param string $target + * @param array $data + * + * @throws StatementException In case the operation has failed + */ + public function insert($target, array $data) + { + $data['created_at'] = time(); + parent::insert($target, $data); + } + + /** + * Update groups of this backend, optionally limited using a filter + * + * @param string $target + * @param array $data + * @param Filter $filter + * + * @throws StatementException In case the operation has failed + */ + public function update($target, array $data, Filter $filter = null) + { + $data['last_modified'] = time(); + parent::update($target, $data, $filter); + } + + /** + * Return the groups the given user is a member of + * + * @param User $user + * + * @return array + */ + public function getMemberships(User $user) + { + $result = $this->select()->fetchAll(); + + $groups = array(); + foreach ($result as $group) { + $groups[$group->group_name] = $group->parent; + } + + $username = strtolower($user->getUsername()); + $memberships = array(); + foreach ($result as $group) { + if ($group->users && !in_array($group->group_name, $memberships)) { + $users = array_map('strtolower', String::trimSplit($group->users)); + if (in_array($username, $users)) { + $memberships[] = $group->group_name; + $parent = $groups[$group->group_name]; + while ($parent !== null) { + $memberships[] = $parent; + $parent = isset($groups[$parent]) ? $groups[$parent] : null; + } + } + } + } + + return $memberships; + } +} diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php new file mode 100644 index 000000000..8015b04a0 --- /dev/null +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -0,0 +1,631 @@ + array( + 'order' => 'asc' + ) + ); + + /** + * Normed attribute names based on known LDAP environments + * + * @var array + */ + protected $normedAttributes = array( + 'uid' => 'uid', + 'gid' => 'gid', + 'user' => 'user', + 'group' => 'group', + 'member' => 'member', + 'inetorgperson' => 'inetOrgPerson', + 'samaccountname' => 'sAMAccountName' + ); + + /** + * The name of this repository + * + * @var string + */ + protected $name; + + /** + * The datasource being used + * + * @var Connection + */ + protected $ds; + + /** + * Create a new LDAP repository object + * + * @param Connection $ds The data source to use + */ + public function __construct($ds) + { + $this->ds = $ds; + } + + /** + * Return the given attribute name normed to known LDAP enviroments, if possible + * + * @param string $name + * + * @return string + */ + protected function getNormedAttribute($name) + { + $loweredName = strtolower($name); + if (array_key_exists($loweredName, $this->normedAttributes)) { + return $this->normedAttributes[$loweredName]; + } + + return $name; + } + + /** + * Set this repository's name + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Return this repository's name + * + * In case no name has been explicitly set yet, the class name is returned. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set the base DN to use for a user query + * + * @param string $baseDn + * + * @return $this + */ + public function setUserBaseDn($baseDn) + { + if (($baseDn = trim($baseDn))) { + $this->userBaseDn = $baseDn; + } + + return $this; + } + + /** + * Return the base DN to use for a user query + * + * @return string + */ + public function getUserBaseDn() + { + return $this->userBaseDn; + } + + /** + * Set the base DN to use for a group query + * + * @param string $baseDn + * + * @return $this + */ + public function setGroupBaseDn($baseDn) + { + if (($baseDn = trim($baseDn))) { + $this->groupBaseDn = $baseDn; + } + + return $this; + } + + /** + * Return the base DN to use for a group query + * + * @return string + */ + public function getGroupBaseDn() + { + return $this->groupBaseDn; + } + + /** + * Set the objectClass where to look for users + * + * @param string $userClass + * + * @return $this + */ + public function setUserClass($userClass) + { + $this->userClass = $this->getNormedAttribute($userClass); + return $this; + } + + /** + * Return the objectClass where to look for users + * + * @return string + */ + public function getUserClass() + { + return $this->userClass; + } + + /** + * Set the objectClass where to look for groups + * + * Sets also the base table name for the underlying repository. + * + * @param string $groupClass + * + * @return $this + */ + public function setGroupClass($groupClass) + { + $this->baseTable = $this->groupClass = $this->getNormedAttribute($groupClass); + return $this; + } + + /** + * Return the objectClass where to look for groups + * + * @return string + */ + public function getGroupClass() + { + return $this->groupClass; + } + + /** + * Set the attribute name where to find a user's name + * + * @param string $userNameAttribute + * + * @return $this + */ + public function setUserNameAttribute($userNameAttribute) + { + $this->userNameAttribute = $this->getNormedAttribute($userNameAttribute); + return $this; + } + + /** + * Return the attribute name where to find a user's name + * + * @return string + */ + public function getUserNameAttribute() + { + return $this->userNameAttribute; + } + + /** + * Set the attribute name where to find a group's name + * + * @param string $groupNameAttribute + * + * @return $this + */ + public function setGroupNameAttribute($groupNameAttribute) + { + $this->groupNameAttribute = $this->getNormedAttribute($groupNameAttribute); + return $this; + } + + /** + * Return the attribute name where to find a group's name + * + * @return string + */ + public function getGroupNameAttribute() + { + return $this->groupNameAttribute; + } + + /** + * Set the attribute name where to find a group's member + * + * @param string $groupMemberAttribute + * + * @return $this + */ + public function setGroupMemberAttribute($groupMemberAttribute) + { + $this->groupMemberAttribute = $this->getNormedAttribute($groupMemberAttribute); + return $this; + } + + /** + * Return the attribute name where to find a group's member + * + * @return string + */ + public function getGroupMemberAttribute() + { + return $this->groupMemberAttribute; + } + + /** + * Set the custom LDAP filter to apply on a user query + * + * @param string $filter + * + * @return $this + */ + public function setUserFilter($filter) + { + if (($filter = trim($filter))) { + if ($filter[0] === '(') { + $filter = substr($filter, 1, -1); + } + + $this->userFilter = $filter; + } + + return $this; + } + + /** + * Return the custom LDAP filter to apply on a user query + * + * @return string + */ + public function getUserFilter() + { + return $this->userFilter; + } + + /** + * Set the custom LDAP filter to apply on a group query + * + * @param string $filter + * + * @return $this + */ + public function setGroupFilter($filter) + { + if (($filter = trim($filter))) { + $this->groupFilter = $filter; + } + + return $this; + } + + /** + * Return the custom LDAP filter to apply on a group query + * + * @return string + */ + public function getGroupFilter() + { + return $this->groupFilter; + } + + /** + * Return a new query for the given columns + * + * @param array $columns The desired columns, if null all columns will be queried + * + * @return RepositoryQuery + */ + public function select(array $columns = null) + { + $query = parent::select($columns); + $query->getQuery()->setBase($this->groupBaseDn); + if ($this->groupFilter) { + // TODO(jom): This should differentiate between groups and their memberships + $query->getQuery()->where(new Expression($this->groupFilter)); + } + + return $query; + } + + /** + * Initialize this repository's query columns + * + * @return array + * + * @throws ProgrammingError In case either $this->groupNameAttribute or $this->groupClass has not been set yet + */ + protected function initializeQueryColumns() + { + if ($this->groupClass === null) { + throw new ProgrammingError('It is required to set the objectClass where to look for groups first'); + } + if ($this->groupNameAttribute === null) { + throw new ProgrammingError('It is required to set a attribute name where to find a group\'s name first'); + } + + if ($this->ds->getCapabilities()->isActiveDirectory()) { + $createdAtAttribute = 'whenCreated'; + $lastModifiedAttribute = 'whenChanged'; + } else { + $createdAtAttribute = 'createTimestamp'; + $lastModifiedAttribute = 'modifyTimestamp'; + } + + // TODO(jom): Fetching memberships does not work currently, we'll need some aggregate functionality! + $columns = array( + 'group' => $this->groupNameAttribute, + 'group_name' => $this->groupNameAttribute, + 'user' => $this->groupMemberAttribute, + 'user_name' => $this->groupMemberAttribute, + 'created_at' => $createdAtAttribute, + 'last_modified' => $lastModifiedAttribute + ); + return array('group' => $columns, 'group_membership' => $columns); + } + + /** + * Initialize this repository's conversion rules + * + * @return array + * + * @throws ProgrammingError In case $this->groupClass has not been set yet + */ + protected function initializeConversionRules() + { + if ($this->groupClass === null) { + throw new ProgrammingError('It is required to set the objectClass where to look for groups first'); + } + + return array( + $this->groupClass => array( + 'created_at' => 'generalized_time', + 'last_modified' => 'generalized_time' + ) + ); + } + + /** + * Validate that the requested table exists + * + * This will return $this->groupClass in case $table equals "group" or "group_membership". + * + * @param string $table The table to validate + * @param RepositoryQuery $query An optional query to pass as context + * (unused by the base implementation) + * + * @return string + * + * @throws ProgrammingError In case the given table does not exist + */ + public function requireTable($table, RepositoryQuery $query = null) + { + $table = parent::requireTable($table, $query); + if ($table === 'group' || $table === 'group_membership') { + $table = $this->groupClass; + } + + return $table; + } + + /** + * Return the groups the given user is a member of + * + * @param User $user + * + * @return array + */ + public function getMemberships(User $user) + { + $userQuery = $this->ds + ->select() + ->from($this->userClass) + ->where($this->userNameAttribute, $user->getUsername()) + ->setBase($this->userBaseDn) + ->setUsePagedResults(false); + if ($this->userFilter) { + $userQuery->where(new Expression($this->userFilter)); + } + + if (($userDn = $userQuery->fetchDn()) === null) { + return array(); + } + + $groupQuery = $this->ds + ->select() + ->from($this->groupClass, array($this->groupNameAttribute)) + ->where($this->groupMemberAttribute, $userDn) + ->setBase($this->groupBaseDn); + if ($this->groupFilter) { + $groupQuery->where(new Expression($this->groupFilter)); + } + + $groups = array(); + foreach ($groupQuery as $row) { + $groups[] = $row->{$this->groupNameAttribute}; + } + + return $groups; + } + + /** + * Apply the given configuration on this backend + * + * @param ConfigObject $config + * + * @return $this + * + * @throws ConfigurationError In case a linked user backend does not exist or is invalid + */ + public function setConfig(ConfigObject $config) + { + if ($config->backend === 'ldap') { + $defaults = $this->getOpenLdapDefaults(); + } elseif ($config->backend === 'msldap') { + $defaults = $this->getActiveDirectoryDefaults(); + } else { + $defaults = new ConfigObject(); + } + + if ($config->user_backend && $config->user_backend !== 'none') { + $userBackend = UserBackend::create($config->user_backend); + if (! $userBackend instanceof LdapUserBackend) { + throw new ConfigurationError('User backend "%s" is not of type LDAP', $config->user_backend); + } + + if ( + $this->ds->getHostname() !== $userBackend->getDataSource()->getHostname() + || $this->ds->getPort() !== $userBackend->getDataSource()->getPort() + ) { + // TODO(jom): Elaborate whether it makes sense to link directories on different hosts + throw new ConfigurationError( + 'It is required that a linked user backend refers to the ' + . 'same directory as it\'s user group backend counterpart' + ); + } + + $defaults->merge(array( + 'user_base_dn' => $userBackend->getBaseDn(), + 'user_class' => $userBackend->getUserClass(), + 'user_name_attribute' => $userBackend->getUserNameAttribute(), + 'user_filter' => $userBackend->getFilter() + )); + } + + return $this + ->setGroupBaseDn($config->base_dn) + ->setUserBaseDn($config->get('user_base_dn', $this->getGroupBaseDn())) + ->setGroupClass($config->get('group_class', $defaults->group_class)) + ->setUserClass($config->get('user_class', $defaults->user_class)) + ->setGroupNameAttribute($config->get('group_name_attribute', $defaults->group_name_attribute)) + ->setUserNameAttribute($config->get('user_name_attribute', $defaults->user_name_attribute)) + ->setGroupMemberAttribute($config->get('group_member_attribute', $defaults->group_member_attribute)) + ->setGroupFilter($config->filter) + ->setUserFilter($config->user_filter); + } + + /** + * Return the configuration defaults for an OpenLDAP environment + * + * @return ConfigObject + */ + public function getOpenLdapDefaults() + { + return new ConfigObject(array( + 'group_class' => 'group', + 'user_class' => 'inetOrgPerson', + 'group_name_attribute' => 'gid', + 'user_name_attribute' => 'uid', + 'group_member_attribute' => 'member' + )); + } + + /** + * Return the configuration defaults for an ActiveDirectory environment + * + * @return ConfigObject + */ + public function getActiveDirectoryDefaults() + { + return new ConfigObject(array( + 'group_class' => 'group', + 'user_class' => 'user', + 'group_name_attribute' => 'sAMAccountName', + 'user_name_attribute' => 'sAMAccountName', + 'group_member_attribute' => 'member' + )); + } +} diff --git a/library/Icinga/Authentication/UserGroup/UserGroupBackend.php b/library/Icinga/Authentication/UserGroup/UserGroupBackend.php new file mode 100644 index 000000000..978860a37 --- /dev/null +++ b/library/Icinga/Authentication/UserGroup/UserGroupBackend.php @@ -0,0 +1,171 @@ +getModuleManager()->getLoadedModules() as $module) { + foreach ($module->getUserGroupBackends() as $identifier => $className) { + if (array_key_exists($identifier, $providedBy)) { + Logger::warning( + 'Cannot register user group backend of type "%s" provided by module "%s".' + . ' The type is already provided by module "%s"', + $identifier, + $module->getName(), + $providedBy[$identifier] + ); + } elseif (in_array($identifier, static::$defaultBackends)) { + Logger::warning( + 'Cannot register user group backend of type "%s" provided by module "%s".' + . ' The type is a default type provided by Icinga Web 2', + $identifier, + $module->getName() + ); + } else { + $providedBy[$identifier] = $module->getName(); + static::$customBackends[$identifier] = $className; + } + } + } + } + + /** + * Return the class for the given custom user group backend + * + * @param string $identifier The identifier of the custom user group backend + * + * @return string|null The name of the class or null in case there was no + * backend found with the given identifier + * + * @throws ConfigurationError In case the class associated to the given identifier does not exist + */ + protected static function getCustomUserGroupBackend($identifier) + { + static::registerCustomUserGroupBackends(); + if (array_key_exists($identifier, static::$customBackends)) { + $className = static::$customBackends[$identifier]; + if (! class_exists($className)) { + throw new ConfigurationError( + 'Cannot utilize user group backend of type "%s". Class "%s" does not exist', + $identifier, + $className + ); + } + + return $className; + } + } + + /** + * Create and return a user group backend with the given name and given configuration applied to it + * + * @param string $name + * @param ConfigObject $backendConfig + * + * @return UserGroupBackendInterface + * + * @throws ConfigurationError + */ + public static function create($name, ConfigObject $backendConfig) + { + if ($backendConfig->name !== null) { + $name = $backendConfig->name; + } + + if (! ($backendType = strtolower($backendConfig->backend))) { + throw new ConfigurationError( + 'Configuration for user group backend "%s" is missing the \'backend\' directive', + $name + ); + } + if (in_array($backendType, static::$defaultBackends)) { + // The default backend check is the first one because of performance reasons: + // Do not attempt to load a custom user group backend unless it's actually required + } elseif (($customClass = static::getCustomUserGroupBackend($backendType)) !== null) { + $backend = new $customClass($backendConfig); + if (! is_a($backend, 'Icinga\Authentication\UserGroup\UserGroupBackendInterface')) { + throw new ConfigurationError( + 'Cannot utilize user group backend of type "%s".' + . ' Class "%s" does not implement UserGroupBackendInterface', + $backendType, + $customClass + ); + } + + $backend->setName($name); + return $backend; + } else { + throw new ConfigurationError( + 'Configuration for user group backend "%s" defines an invalid backend type.' + . ' Backend type "%s" is not supported', + $name, + $backendType + ); + } + + if ($backendConfig->resource === null) { + throw new ConfigurationError( + 'Configuration for user group backend "%s" is missing the \'resource\' directive', + $name + ); + } + $resource = ResourceFactory::create($backendConfig->resource); + + switch ($backendType) { + case 'db': + $backend = new DbUserGroupBackend($resource); + break; + case 'ini': + $backend = new IniUserGroupBackend($resource); + break; + case 'ldap': + case 'msldap': + $backend = new LdapUserGroupBackend($resource); + $backend->setConfig($backendConfig); + break; + } + + $backend->setName($name); + return $backend; + } +} diff --git a/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php b/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php new file mode 100644 index 000000000..a567d1f0a --- /dev/null +++ b/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php @@ -0,0 +1,37 @@ +name = (string) $name; - return $this; - } - - /** - * Get the backend name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * Create a user group backend - * - * @param string $name - * @param ConfigObject $backendConfig - * - * @return DbUserGroupBackend|IniUserGroupBackend - * @throws ConfigurationError If the backend configuration is invalid - */ - public static function create($name, ConfigObject $backendConfig) - { - if ($backendConfig->name !== null) { - $name = $backendConfig->name; - } - if (($backendType = $backendConfig->backend) === null) { - throw new ConfigurationError( - 'Configuration for user group backend \'%s\' is missing the \'backend\' directive', - $name - ); - } - $backendType = strtolower($backendType); - if (($resourceName = $backendConfig->resource) === null) { - throw new ConfigurationError( - 'Configuration for user group backend \'%s\' is missing the \'resource\' directive', - $name - ); - } - $resourceName = strtolower($resourceName); - try { - $resource = ResourceFactory::create($resourceName); - } catch (IcingaException $e) { - throw new ConfigurationError( - 'Can\'t create user group backend \'%s\'. An exception was thrown: %s', - $resourceName, - $e - ); - } - switch ($backendType) { - case 'db': - $backend = new DbUserGroupBackend($resource); - break; - case 'ini': - $backend = new IniUserGroupBackend($resource); - break; - default: - throw new ConfigurationError( - 'Can\'t create user group backend \'%s\'. Invalid backend type \'%s\'.', - $name, - $backendType - ); - } - $backend->setName($name); - return $backend; - } - - /** - * Get the groups the given user is a member of - * - * @param User $user - * - * @return array - */ - abstract public function getMemberships(User $user); -} diff --git a/library/Icinga/Chart/Axis.php b/library/Icinga/Chart/Axis.php index 00a5f5679..2715f3b2d 100644 --- a/library/Icinga/Chart/Axis.php +++ b/library/Icinga/Chart/Axis.php @@ -1,6 +1,5 @@ - *
  • LABEL_ROTATE_HORIZONTAL: Labels will be displayed horizontally
  • - *
  • LABEL_ROTATE_DIAGONAL: Labels will be rotated by 45°
  • - * - * - * @param $style The rotation mode + * @var int */ - public function setHorizontalLabelRotationStyle($style) - { - $this->labelRotationStyle = $style; - } + public $minUnitsPerTick = 15; + + /** + * If the displayed labels should be aligned horizontally or diagonally + */ + protected $labelRotationStyle = self::LABEL_ROTATE_HORIZONTAL; /** * Inform the axis about an added dataset @@ -117,7 +115,7 @@ class Axis implements Drawable * * @param AxisUnit $unit The AxisUnit implementation to use for the x axis * - * @return self This Axis Object + * @return $this This Axis Object * @see Axis::CalendarUnit * @see Axis::LinearUnit */ @@ -132,7 +130,7 @@ class Axis implements Drawable * * @param AxisUnit $unit The AxisUnit implementation to use for the y axis * - * @return self This Axis Object + * @return $this This Axis Object * @see Axis::CalendarUnit * @see Axis::LinearUnit */ @@ -160,58 +158,74 @@ class Axis implements Drawable */ private function renderHorizontalAxis(RenderContext $ctx, DOMElement $group) { + $steps = $this->ticksPerX($this->xUnit->getTicks(), $ctx->getNrOfUnitsX(), $this->minUnitsPerStep); + $ticks = $this->ticksPerX($this->xUnit->getTicks(), $ctx->getNrOfUnitsX(), $this->minUnitsPerTick); + + // Steps should always be ticks + if ($ticks !== $steps) { + $steps = $ticks * 5; + } + + // Check whether there is enough room for regular labels + $labelRotationStyle = $this->labelRotationStyle; + if ($this->labelsOversized($this->xUnit, 6)) { + $labelRotationStyle = self::LABEL_ROTATE_DIAGONAL; + } + + /* $line = new Line(0, 100, 100, 100); $line->setStrokeWidth(2); $group->appendChild($line->toSvg($ctx)); + */ // contains the approximate end position of the last label $lastLabelEnd = -1; $shift = 0; + $i = 0; foreach ($this->xUnit as $label => $pos) { - if ($this->labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) { - // If the last label would overlap this label we shift the y axis a bit - if ($lastLabelEnd > $pos) { - $shift = ($shift + 5) % 10; - } else { - $shift = 0; + + if ($i % $ticks === 0) { + /* + $tick = new Line($pos, 100, $pos, 101); + $group->appendChild($tick->toSvg($ctx)); + */ + } + + if ($i % $steps === 0) { + if ($labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) { + // If the last label would overlap this label we shift the y axis a bit + if ($lastLabelEnd > $pos) { + $shift = ($shift + 5) % 10; + } else { + $shift = 0; + } } + + $labelField = new Text($pos + 0.5, ($this->xLabel ? 107 : 105) + $shift, $label); + if ($labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) { + $labelField->setAlignment(Text::ALIGN_MIDDLE) + ->setFontSize('2.5em'); + } else { + $labelField->setFontSize('2.5em'); + } + + if ($labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) { + $labelField = new Rotator($labelField, 45); + } + $labelField = $labelField->toSvg($ctx); + + $group->appendChild($labelField); + + if ($this->drawYGrid) { + $bgLine = new Line($pos, 0, $pos, 100); + $bgLine->setStrokeWidth(0.5) + ->setStrokeColor('#BFBFBF'); + $group->appendChild($bgLine->toSvg($ctx)); + } + $lastLabelEnd = $pos + strlen($label) * 1.2; } - - $tick = new Line($pos, 100, $pos, 102); - $group->appendChild($tick->toSvg($ctx)); - - $labelField = new Text($pos + 0.5, ($this->xLabel ? 107 : 105) + $shift, $label); - if ($this->labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) { - $labelField->setAlignment(Text::ALIGN_MIDDLE) - ->setFontSize('1.8em'); - } else { - $labelField->setFontSize('2.5em'); - } - - if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) { - $labelField = new Rotator($labelField, 45); - } - $labelField = $labelField->toSvg($ctx); - - $group->appendChild($labelField); - - if ($this->drawYGrid) { - $bgLine = new Line($pos, 0, $pos, 100); - $bgLine->setStrokeWidth(0.5) - ->setStrokeColor('#232'); - $group->appendChild($bgLine->toSvg($ctx)); - } - $lastLabelEnd = $pos + strlen($label) * 1.2; - } - - // render the label for this axis - if ($this->xLabel) { - $axisLabel = new Text(50, 104, $this->xLabel); - $axisLabel->setFontSize('2em') - ->setFontWeight('bold') - ->setAlignment(Text::ALIGN_MIDDLE); - $group->appendChild($axisLabel->toSvg($ctx)); + $i++; } } @@ -223,34 +237,59 @@ class Axis implements Drawable */ private function renderVerticalAxis(RenderContext $ctx, DOMElement $group) { + $steps = $this->ticksPerX($this->yUnit->getTicks(), $ctx->getNrOfUnitsY(), $this->minUnitsPerStep); + $ticks = $this->ticksPerX($this->yUnit->getTicks(), $ctx->getNrOfUnitsY(), $this->minUnitsPerTick); + + // Steps should always be ticks + if ($ticks !== $steps) { + $steps = $ticks * 5; + } + /* $line = new Line(0, 0, 0, 100); $line->setStrokeWidth(2); $group->appendChild($line->toSvg($ctx)); + */ + $i = 0; foreach ($this->yUnit as $label => $pos) { $pos = 100 - $pos; - $tick = new Line(0, $pos, -1, $pos); - $group->appendChild($tick->toSvg($ctx)); - $labelField = new Text(-0.5, $pos+0.5, $label); - $labelField->setFontSize('1.8em') - ->setAlignment(Text::ALIGN_END); - - $group->appendChild($labelField->toSvg($ctx)); - if ($this->drawXGrid) { - $bgLine = new Line(0, $pos, 100, $pos); - $bgLine->setStrokeWidth(0.5) - ->setStrokeColor('#343'); - $group->appendChild($bgLine->toSvg($ctx)); + if ($i % $ticks === 0) { + // draw a tick + //$tick = new Line(0, $pos, -1, $pos); + //$group->appendChild($tick->toSvg($ctx)); } + + if ($i % $steps === 0) { + // draw a step + $labelField = new Text(-0.5, $pos + 0.5, $label); + $labelField->setFontSize('2.5em') + ->setAlignment(Text::ALIGN_END); + + $group->appendChild($labelField->toSvg($ctx)); + if ($this->drawXGrid) { + $bgLine = new Line(0, $pos, 100, $pos); + $bgLine->setStrokeWidth(0.5) + ->setStrokeColor('#BFBFBF'); + $group->appendChild($bgLine->toSvg($ctx)); + } + } + $i++; } - if ($this->yLabel) { - $axisLabel = new Text(-8, 50, $this->yLabel); + if ($this->yLabel || $this->xLabel) { + if ($this->yLabel && $this->xLabel) { + $txt = $this->yLabel . ' / ' . $this->xLabel; + } else if ($this->xLabel) { + $txt = $this->xLabel; + } else { + $txt = $this->yLabel; + } + + $axisLabel = new Text(50, -3, $txt); $axisLabel->setFontSize('2em') ->setFontWeight('bold') ->setAlignment(Text::ALIGN_MIDDLE); - $axisLabel = new Rotator($axisLabel, 90); $group->appendChild($axisLabel->toSvg($ctx)); } @@ -292,7 +331,7 @@ class Axis implements Drawable * * @param string $label The label to use for the y axis * - * @return self Fluid interface + * @return $this Fluid interface */ public function setYLabel($label) { @@ -307,7 +346,7 @@ class Axis implements Drawable * * @param int $xMin The minimum value to use for the x axis * - * @return self Fluid interface + * @return $this Fluid interface */ public function setXMin($xMin) { @@ -322,7 +361,7 @@ class Axis implements Drawable * * @param int $yMin The minimum value to use for the x axis * - * @return self Fluid interface + * @return $this Fluid interface */ public function setYMin($yMin) { @@ -337,7 +376,7 @@ class Axis implements Drawable * * @param int $xMax The minimum value to use for the x axis * - * @return self Fluid interface + * @return $this Fluid interface */ public function setXMax($xMax) { @@ -352,7 +391,7 @@ class Axis implements Drawable * * @param int $yMax The minimum value to use for the y axis * - * @return self Fluid interface + * @return $this Fluid interface */ public function setYMax($yMax) { @@ -416,4 +455,32 @@ class Axis implements Drawable $this->renderVerticalAxis($ctx, $group); return $group; } + + protected function ticksPerX($ticks, $units, $min) + { + $per = 1; + while ($per * $units / $ticks < $min) { + $per++; + } + return $per; + } + + /** + * Returns whether at least one label of the given Axis + * is bigger than the given maxLength + * + * @param AxisUnit $axis The axis that contains the labels that will be checked + * + * @return boolean Whether at least one label is bigger than maxLength + */ + private function labelsOversized(AxisUnit $axis, $maxLength = 5) + { + $oversized = false; + foreach ($axis as $label => $pos) { + if (strlen($label) > $maxLength) { + $oversized = true; + } + } + return $oversized; + } } diff --git a/library/Icinga/Chart/Chart.php b/library/Icinga/Chart/Chart.php index 3cfa4b39a..842bb56e8 100644 --- a/library/Icinga/Chart/Chart.php +++ b/library/Icinga/Chart/Chart.php @@ -1,10 +1,9 @@ renderer->setXAspectRatioAlignment(SVGRenderer::X_ASPECT_RATIO_MIN); $this->renderer->setYAspectRatioAlignment(SVGRenderer::Y_ASPECT_RATIO_MIN); } + + $this->renderer->setAriaDescription($this->description); + $this->renderer->setAriaTitle($this->title); + $this->renderer->getCanvas()->setAriaRole('presentation'); + $this->renderer->getCanvas()->addElement($this); return $this->renderer->render(); } + /** + * Return this graph rendered as PNG + * + * @param int $width The width of the PNG in pixel + * @param int $height The height of the PNG in pixel + * + * @return string A PNG binary string + * + * @throws IcingaException In case ImageMagick is not available + */ + public function toPng($width, $height) + { + if (! class_exists('Imagick')) { + throw new IcingaException('Cannot render PNGs without ImageMagick'); + } + + $image = new Imagick(); + $image->readImageBlob($this->render()); + $image->setImageFormat('png24'); + $image->resizeImage($width, $height, imagick::FILTER_LANCZOS, 1); + return $image; + } + /** * Align the chart to the top left corner instead of centering it * * @param bool $align */ - public function alignTopLeft ($align = true) + public function alignTopLeft($align = true) { $this->align = $align; } diff --git a/library/Icinga/Chart/Format.php b/library/Icinga/Chart/Format.php index 80b48ecb5..6eb8c5f52 100644 --- a/library/Icinga/Chart/Format.php +++ b/library/Icinga/Chart/Format.php @@ -1,6 +1,5 @@ order = $order; $this->dataSet = $dataSet; + $this->tooltips = $tooltips; + foreach ($this->tooltips as $value) { + $ts[] = $value; + } + $this->tooltips = $ts; + $this->graphs = $graphs; } @@ -122,6 +127,14 @@ class BarGraph extends Styleable implements Drawable $doc = $ctx->getDocument(); $group = $doc->createElement('g'); $idx = 0; + + if (count($this->dataSet) > 15) { + $this->barWidth = 2; + } + if (count($this->dataSet) > 25) { + $this->barWidth = 1; + } + foreach ($this->dataSet as $x => $point) { // add white background bar, to prevent other bars from altering transparency effects $bar = $this->drawSingleBar($point, $idx++, 'white', $this->strokeWidth, $idx)->toSvg($ctx); diff --git a/library/Icinga/Chart/Graph/LineGraph.php b/library/Icinga/Chart/Graph/LineGraph.php index d12d4eed9..7b831d625 100644 --- a/library/Icinga/Chart/Graph/LineGraph.php +++ b/library/Icinga/Chart/Graph/LineGraph.php @@ -1,6 +1,5 @@ dataset = $dataset; + $this->graphs = $graphs; + + $this->tooltips = $tooltips; + foreach ($this->tooltips as $value) { + $ts[] = $value; + } + $this->tooltips = $ts; + $this->order = $order; } /** @@ -136,14 +161,41 @@ class LineGraph extends Styleable implements Drawable $path->setAdditionalStyle('clip-path: url(#clip);'); $path->setId($this->id); $group = $path->toSvg($ctx); - if ($this->showDataPoints === true) { - foreach ($this->dataset as $point) { - $dot = new Circle($point[0], $point[1], $this->strokeWidth*5); - $dot->setFill('black'); - $group->appendChild($dot->toSvg($ctx)); + foreach ($this->dataset as $x => $point) { + + if ($this->showDataPoints === true) { + $dot = new Circle($point[0], $point[1], $this->dotWith); + $dot->setFill($this->strokeColor); + $group->appendChild($dot->toSvg($ctx)); + } + + // Draw invisible circle for tooltip hovering + $invisible = new Circle($point[0], $point[1], 20); + $invisible->setFill($this->strokeColor); + $invisible->setAdditionalStyle('opacity: 0.0;'); + $invisible->setAttribute('class', 'chart-data'); + if (isset($this->tooltips[$x])) { + $data = array( + 'label' => isset($this->graphs[$this->order]['label']) ? + strtolower($this->graphs[$this->order]['label']) : '', + 'color' => isset($this->graphs[$this->order]['color']) ? + strtolower($this->graphs[$this->order]['color']) : '#fff' + ); + $format = isset($this->graphs[$this->order]['tooltip']) + ? $this->graphs[$this->order]['tooltip'] : null; + $invisible->setAttribute( + 'title', + $this->tooltips[$x]->renderNoHtml($this->order, $data, $format) + ); + $invisible->setAttribute( + 'data-title-rich', + $this->tooltips[$x]->render($this->order, $data, $format) + ); + } + $group->appendChild($invisible->toSvg($ctx)); } - } + return $group; } } diff --git a/library/Icinga/Chart/Graph/StackedGraph.php b/library/Icinga/Chart/Graph/StackedGraph.php index 4339b8c8d..8fd20ae1d 100644 --- a/library/Icinga/Chart/Graph/StackedGraph.php +++ b/library/Icinga/Chart/Graph/StackedGraph.php @@ -1,6 +1,5 @@ {title}
    {value} of {sum} {label}' + $format = '{title}: {value} {label}' ) { $this->data = array_merge($this->data, $data); $this->defaultFormat = $format; @@ -141,4 +140,4 @@ class Tooltip { return strip_tags($this->render($order, $data, $format)); } -} \ No newline at end of file +} diff --git a/library/Icinga/Chart/GridChart.php b/library/Icinga/Chart/GridChart.php index 190e09bf9..57ca9f469 100644 --- a/library/Icinga/Chart/GridChart.php +++ b/library/Icinga/Chart/GridChart.php @@ -1,6 +1,5 @@ title = t('Grid Chart'); + $this->description = t('Contains data in a bar or line chart.'); + parent::__construct(); + } + /** * Check if the current dataset has the proper structure for this chart. * @@ -126,7 +132,7 @@ class GridChart extends Chart * * @param array $axis,... The line definitions to draw * - * @return self Fluid interface + * @return $this Fluid interface */ public function drawLines(array $axis) { @@ -140,7 +146,7 @@ class GridChart extends Chart * Refer to the graphs.md for a detailed list of allowed attributes * * @param array $axis - * @return self + * @return $this */ public function drawBars(array $axis) { @@ -209,7 +215,7 @@ class GridChart extends Chart * @param string $yAxisLabel The label to use for the y axis * @param string $axisName The name of the axis, for now 'default' * - * @return self Fluid interface + * @return $this Fluid interface */ public function setAxisLabel($xAxisLabel, $yAxisLabel, $axisName = 'default') { @@ -223,7 +229,7 @@ class GridChart extends Chart * @param AxisUnit $unit The unit for the x axis * @param string $axisName The name of the axis to set the label for, currently only 'default' * - * @return self Fluid interface + * @return $this Fluid interface */ public function setXAxis(AxisUnit $unit, $axisName = 'default') { @@ -237,7 +243,7 @@ class GridChart extends Chart * @param AxisUnit $unit The unit for the y axis * @param string $axisName The name of the axis to set the label for, currently only 'default' * - * @return self Fluid interface + * @return $this Fluid interface */ public function setYAxis(AxisUnit $unit, $axisName = 'default') { @@ -270,7 +276,7 @@ class GridChart extends Chart * @param Axis $axis The new axis to use * @param string $name The name of the axis, currently only 'default' * - * @return self Fluid interface + * @return $this Fluid interface */ public function setAxis(Axis $axis, $name = 'default') { @@ -284,7 +290,7 @@ class GridChart extends Chart * @param Axis $axis The axis object to add * @param string $name The name of the axis * - * @return self Fluid interface + * @return $this Fluid interface */ public function addAxis(Axis $axis, $name) { @@ -301,7 +307,7 @@ class GridChart extends Chart * @param int $yMin The minimum value for the y axis or null to use a dynamic value * @param string $axisName The name of the axis to set the minimum, currently only 'default' * - * @return self Fluid interface + * @return $this Fluid interface */ public function setAxisMin($xMin = null, $yMin = null, $axisName = 'default') { @@ -318,7 +324,7 @@ class GridChart extends Chart * @param int $yMax The maximum value for the y axis or null to use a dynamic value * @param string $axisName The name of the axis to set the maximum, currently only 'default' * - * @return self Fluid interface + * @return $this Fluid interface */ public function setAxisMax($xMax = null, $yMax = null, $axisName = 'default') { @@ -396,7 +402,12 @@ class GridChart extends Chart ); break; case self::TYPE_LINE: - $graphObj = new LineGraph($axis->transform($graph['data'])); + $graphObj = new LineGraph( + $axis->transform($graph['data']), + $graphs, + $dataset, + $this->tooltips + ); break; default: continue; diff --git a/library/Icinga/Chart/Inline/Inline.php b/library/Icinga/Chart/Inline/Inline.php index 8faa388bf..71f460593 100644 --- a/library/Icinga/Chart/Inline/Inline.php +++ b/library/Icinga/Chart/Inline/Inline.php @@ -1,6 +1,5 @@ alignTopLeft(); @@ -23,23 +18,24 @@ class PieChart extends Inline $pie->drawPie(array( 'data' => $this->data, 'colors' => $this->colors, 'labels' => $this->labels )); + return $pie; + } + + public function toSvg($output = true) + { if ($output) { - echo $pie->render(); + echo $this->getChart()->render(); } else { - return $pie->render(); + return $this->getChart()->render(); } } - public function toPng() + public function toPng($output = true) { - if (! class_exists('Imagick')) { - // TODO: This is quick & dirty. 404? - throw new IcingaException('Cannot render PNGs without Imagick'); + if ($output) { + echo $this->getChart()->toPng($this->width, $this->height); + } else { + return $this->getChart()->toPng($this->width, $this->height); } - $image = new Imagick(); - $image->readImageBlob($this->render(false)); - $image->setImageFormat('png24'); - $image->resizeImage($this->width, $this->height, imagick::FILTER_LANCZOS, 1); - echo $image; } } diff --git a/library/Icinga/Chart/Legend.php b/library/Icinga/Chart/Legend.php index 46ba8081c..f545b2f14 100644 --- a/library/Icinga/Chart/Legend.php +++ b/library/Icinga/Chart/Legend.php @@ -1,6 +1,5 @@ getLayout()->setPadding(2, 2, 2, 2); $nrOfColumns = 4; - $leftstep = 100 / $nrOfColumns; $topstep = 10 / $nrOfColumns + 2; $top = 0; $left = 0; $lastLabelEndPos = -1; foreach ($this->dataset as $color => $text) { + $leftstep = 100 / $nrOfColumns + strlen($text); + // Make sure labels don't overlap each other while ($lastLabelEndPos >= $left) { $left += $leftstep; diff --git a/library/Icinga/Chart/Palette.php b/library/Icinga/Chart/Palette.php index ef413e73e..2753e4c85 100644 --- a/library/Icinga/Chart/Palette.php +++ b/library/Icinga/Chart/Palette.php @@ -1,6 +1,5 @@ title = t('Pie Chart'); + $this->description = t('Contains data in a pie chart.'); + parent::__construct(); + } + /** * Test if the given pies have the correct format * @@ -101,7 +107,7 @@ class PieChart extends Chart * * @param array $dataSet,... The pie definition, see graphs.md for further details concerning the format * - * @return self Fluent interface + * @return $this Fluent interface */ public function drawPie(array $dataSet) { @@ -266,7 +272,7 @@ class PieChart extends Chart * * @param string $type Either self::STACKED or self::ROW * - * @return self Fluent interface + * @return $this Fluent interface */ public function setType($type) { @@ -277,7 +283,7 @@ class PieChart extends Chart /** * Hide the caption from this PieChart * - * @return self Fluent interface + * @return $this Fluent interface */ public function disableLegend() { diff --git a/library/Icinga/Chart/Primitive/Animatable.php b/library/Icinga/Chart/Primitive/Animatable.php index 3d4d24f00..c976a7e64 100644 --- a/library/Icinga/Chart/Primitive/Animatable.php +++ b/library/Icinga/Chart/Primitive/Animatable.php @@ -1,6 +1,5 @@ appendChild($child->toSvg($ctx)); } + if (isset($this->ariaRole)) { + $outer->setAttribute('role', $this->ariaRole); + } return $outer; } + + /** + * Set the aria role used to determine the meaning of this canvas in the accessibility tree + * + * The role 'presentation' will indicate that the purpose of this canvas is entirely decorative, while the role + * 'img' will indicate that the canvas contains an image, with a possible title or a description. For other + * possible roles, see http://www.w3.org/TR/wai-aria/roles + * + * @param $role string The aria role to set + */ + public function setAriaRole($role) + { + $this->ariaRole = $role; + } } diff --git a/library/Icinga/Chart/Primitive/Circle.php b/library/Icinga/Chart/Primitive/Circle.php index 058211bf7..be18a4c43 100644 --- a/library/Icinga/Chart/Primitive/Circle.php +++ b/library/Icinga/Chart/Primitive/Circle.php @@ -1,6 +1,5 @@ getDocument()->createElement('circle'); $circle->setAttribute('cx', Format::formatSVGNumber($coords[0])); $circle->setAttribute('cy', Format::formatSVGNumber($coords[1])); - $circle->setAttribute('r', 5); + $circle->setAttribute('r', $this->radius); $circle->setAttribute('style', $this->getStyle()); $this->applyAttributes($circle); return $circle; diff --git a/library/Icinga/Chart/Primitive/Drawable.php b/library/Icinga/Chart/Primitive/Drawable.php index ea5dca077..256bd7e8c 100644 --- a/library/Icinga/Chart/Primitive/Drawable.php +++ b/library/Icinga/Chart/Primitive/Drawable.php @@ -1,6 +1,5 @@ element->toSvg($ctx); return $this->rotate($ctx, $el, $this->degrees); } -} \ No newline at end of file +} diff --git a/library/Icinga/Chart/SVGRenderer.php b/library/Icinga/Chart/SVGRenderer.php index da6015ceb..98eaa86d6 100644 --- a/library/Icinga/Chart/SVGRenderer.php +++ b/library/Icinga/Chart/SVGRenderer.php @@ -1,6 +1,5 @@ document->createElement('svg'); $svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg'); $svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $svg->setAttribute('role', $this->ariaRole); $svg->setAttribute('width', '100%'); $svg->setAttribute('height', '100%'); $svg->setAttribute( @@ -151,6 +172,42 @@ class SVGRenderer return $svg; } + /** + * Add aria title and description + * + * Adds an aria title and desc element to the given SVG node, which are used to describe this SVG by accessibility + * tools such as screen readers. + * + * @param DOMNode $svg The SVG DOMNode to which the aria attributes should be attached + * @param $title The title text + * @param $description The description text + */ + private function addAriaDescription (DOMNode $svg, $titleText, $descriptionText) + { + $doc = $svg->ownerDocument; + + $titleId = $descId = ''; + if (isset ($this->ariaTitle)) { + $titleId = 'aria-title-' . $this->stripNonAlphanumeric($titleText); + $title = $doc->createElement('title'); + $title->setAttribute('id', $titleId); + + $title->appendChild($doc->createTextNode($titleText)); + $svg->appendChild($title); + } + + if (isset ($this->ariaDescription)) { + $descId = 'aria-desc-' . $this->stripNonAlphanumeric($descriptionText); + $desc = $doc->createElement('desc'); + $desc->setAttribute('id', $descId); + + $desc->appendChild($doc->createTextNode($descriptionText)); + $svg->appendChild($desc); + } + + $svg->setAttribute('aria-labelledby', join(' ', array($titleId, $descId))); + } + /** * Initialises the XML-document, SVG-element and this figure's root canvas * @@ -173,6 +230,7 @@ class SVGRenderer { $this->createRootDocument(); $ctx = $this->createRenderContext(); + $this->addAriaDescription($this->svg, $this->ariaTitle, $this->ariaDescription); $this->svg->appendChild($this->rootCanvas->toSvg($ctx)); $this->document->formatOutput = true; return $this->document->saveXML(); @@ -233,4 +291,40 @@ class SVGRenderer { $this->yAspectRatio = $alignment; } + + /** + * Set the aria description, that is used as a title for this SVG in screen readers + * + * @param $text + */ + public function setAriaTitle($text) + { + $this->ariaTitle = $text; + } + + /** + * Set the aria description, that is used to describe this SVG in screen readers + * + * @param $text + */ + public function setAriaDescription($text) + { + $this->ariaDescription = $text; + } + + /** + * Set the aria role, that is used to describe the purpose of this SVG in screen readers + * + * @param $text + */ + public function setAriaRole($text) + { + $this->ariaRole = $text; + } + + + private function stripNonAlphanumeric($str) + { + return preg_replace('/[^A-Za-z]+/', '', $str); + } } diff --git a/library/Icinga/Chart/Unit/AxisUnit.php b/library/Icinga/Chart/Unit/AxisUnit.php index dc57f99e0..79af2fe9d 100644 --- a/library/Icinga/Chart/Unit/AxisUnit.php +++ b/library/Icinga/Chart/Unit/AxisUnit.php @@ -1,6 +1,5 @@ getMax() - $this->getMin(); @@ -111,7 +110,7 @@ class CalendarUnit extends LinearUnit * @param array $dataset The dataset to update * @param int $idx The index to use for determining the data * - * @return self Fluid interface + * @return $this Fluid interface */ public function addValues(array $dataset, $idx = 0) { diff --git a/library/Icinga/Chart/Unit/LinearUnit.php b/library/Icinga/Chart/Unit/LinearUnit.php index d776fe304..bf27ed8a3 100644 --- a/library/Icinga/Chart/Unit/LinearUnit.php +++ b/library/Icinga/Chart/Unit/LinearUnit.php @@ -1,6 +1,5 @@ staticMin) { $this->min = min($this->min, $datapoints[0]); } - if (!$this->staticMin || !$this->staticMax) { - $this->updateMaxValue(); - } $this->currentTick = 0; $this->currentValue = $this->min; - return $this; - } - - /** - * Refresh the range depending on the current values of min, max and nrOfTicks - */ - private function updateMaxValue() - { - $this->max = $this->calculateTickRange($this->max - $this->min, $this->nrOfTicks) * - $this->nrOfTicks + $this->min; - } - - /** - * Determine the minimum tick range that is necessary to display the given value range - * correctly - * - * @param int range The range to display - * @param int ticks The amount of ticks to use - * - * @return int The value for each tick - */ - private function calculateTickRange($range, $ticks) - { - $factor = 1; - $steps = array(1, 2, 5); - $step = 0; - while ($range / ($factor * $steps[$step]) > $ticks) { - $step++; - if ($step === count($steps)) { - $step = 0; - $factor *= 10; - } + if ($this->max === $this->min) { + $this->max = $this->min + 10; } - return $steps[$step] * $factor; + $this->nrOfTicks = $this->max - $this->min; + return $this; } /** @@ -149,7 +115,7 @@ class LinearUnit implements AxisUnit } elseif ($value > $this->max) { return 100; } else { - return 100 * ($value - $this->min) / $this->max - $this->min; + return 100 * ($value - $this->min) / $this->nrOfTicks; } } @@ -211,7 +177,6 @@ class LinearUnit implements AxisUnit if ($max !== null) { $this->max = $max; $this->staticMax = true; - $this->updateMaxValue(); } } @@ -225,7 +190,6 @@ class LinearUnit implements AxisUnit if ($min !== null) { $this->min = $min; $this->staticMin = true; - $this->updateMaxValue(); } } @@ -248,4 +212,14 @@ class LinearUnit implements AxisUnit { return $this->max; } + + /** + * Get the amount of ticks necessary to display this AxisUnit + * + * @return int + */ + public function getTicks() + { + return $this->nrOfTicks; + } } diff --git a/library/Icinga/Chart/Unit/LogarithmicUnit.php b/library/Icinga/Chart/Unit/LogarithmicUnit.php new file mode 100644 index 000000000..0e87e0433 --- /dev/null +++ b/library/Icinga/Chart/Unit/LogarithmicUnit.php @@ -0,0 +1,263 @@ + + * this article for a more detailed description. + */ +class LogarithmicUnit implements AxisUnit +{ + /** + * @var int + */ + protected $base; + + /** + * @var + */ + protected $currentTick; + + /** + * @var + */ + protected $minExp; + + /** + * @var + */ + protected $maxExp; + + /** + * True when the minimum value is static and isn't affected by the data set + * + * @var bool + */ + protected $staticMin = false; + + /** + * True when the maximum value is static and isn't affected by the data set + * + * @var bool + */ + protected $staticMax = false; + + /** + * Create and initialize this AxisUnit + * + * @param int $nrOfTicks The number of ticks to use + */ + public function __construct($base = 10) + {; + $this->base = $base; + $this->minExp = PHP_INT_MAX; + $this->maxExp = ~PHP_INT_MAX; + } + + /** + * Add a dataset and calculate the minimum and maximum value for this AxisUnit + * + * @param array $dataset The dataset to add + * @param int $idx The idx (0 for x, 1 for y) + * + * @return $this Fluent interface + */ + public function addValues(array $dataset, $idx = 0) + { + $datapoints = array(); + + foreach ($dataset['data'] as $points) { + $datapoints[] = $points[$idx]; + } + if (empty($datapoints)) { + return $this; + } + sort($datapoints); + if (!$this->staticMax) { + $this->maxExp = max($this->maxExp, $this->logCeil($datapoints[count($datapoints) - 1])); + } + if (!$this->staticMin) { + $this->minExp = min($this->minExp, $this->logFloor($datapoints[0])); + } + $this->currentTick = 0; + + return $this; + } + + /** + * Transform the absolute value to an axis relative value + * + * @param int $value The absolute coordinate from the data set + * @return float|int The axis relative coordinate (between 0 and 100) + */ + public function transform($value) + { + if ($value < $this->pow($this->minExp)) { + return 0; + } elseif ($value > $this->pow($this->maxExp)) { + return 100; + } else { + return 100 * ($this->log($value) - $this->minExp) / $this->getTicks(); + } + } + + /** + * Return the position of the current tick + * + * @return int + */ + public function current() + { + return $this->currentTick * (100 / $this->getTicks()); + } + + /** + * Calculate the next tick and tick value + */ + public function next() + { + ++ $this->currentTick; + } + + /** + * Return the label for the current tick + * + * @return string The label for the current tick + */ + public function key() + { + $currentBase = $this->currentTick + $this->minExp; + if (abs($currentBase) > 4) { + return $this->base . 'E' . $currentBase; + } + return (string) intval($this->pow($currentBase)); + } + + /** + * True when we're at a valid tick (iterator interface) + * + * @return bool + */ + public function valid() + { + return $this->currentTick >= 0 && $this->currentTick < $this->getTicks(); + } + + /** + * Reset the current tick and label value + */ + public function rewind() + { + $this->currentTick = 0; + } + + /** + * Perform a log-modulo transformation + * + * @param $value The value to transform + * + * @return double The transformed value + */ + protected function log($value) + { + $sign = $value > 0 ? 1 : -1; + return $sign * log1p($sign * $value) / log($this->base); + } + + /** + * Calculate the biggest exponent necessary to display the given data point + * + * @param $value + * + * @return float + */ + protected function logCeil($value) + { + return ceil($this->log($value)) + 1; + } + + /** + * Calculate the smallest exponent necessary to display the given data point + * + * @param $value + * + * @return float + */ + protected function logFloor($value) + { + return floor($this->log($value)); + } + + /** + * Inverse function to the log-modulo transformation + * + * @param $value + * + * @return double + */ + protected function pow($value) + { + if ($value == 0) { + return 0; + } + $sign = $value > 0 ? 1 : -1; + return $sign * (pow($this->base, $sign * $value)); + } + + /** + * Set the axis minimum value to a fixed value + * + * @param int $min The new minimum value + */ + public function setMin($min) + { + $this->minExp = $this->logFloor($min); + $this->staticMin = true; + } + + /** + * Set the axis maximum value to a fixed value + * + * @param int $max The new maximum value + */ + public function setMax($max) + { + $this->maxExp = $this->logCeil($max); + $this->staticMax = true; + } + + /** + * Return the current minimum value of the axis + * + * @return int The minimum set for this axis + */ + public function getMin() + { + return $this->pow($this->minExp); + } + + /** + * Return the current maximum value of the axis + * + * @return int The maximum set for this axis + */ + public function getMax() + { + return $this->pow($this->maxExp); + } + + /** + * Get the amount of ticks necessary to display this AxisUnit + * + * @return int + */ + public function getTicks() + { + return $this->maxExp - $this->minExp; + } +} diff --git a/library/Icinga/Chart/Unit/StaticAxis.php b/library/Icinga/Chart/Unit/StaticAxis.php index 6458ae599..bc75516ef 100644 --- a/library/Icinga/Chart/Unit/StaticAxis.php +++ b/library/Icinga/Chart/Unit/StaticAxis.php @@ -1,6 +1,5 @@ items); } + + /** + * Get the amount of ticks of this axis + * + * @return int + */ + public function getTicks() + { + return count($this->items); + } } diff --git a/library/Icinga/Cli/AnsiScreen.php b/library/Icinga/Cli/AnsiScreen.php index f3b2b6654..eeb5c4c7b 100644 --- a/library/Icinga/Cli/AnsiScreen.php +++ b/library/Icinga/Cli/AnsiScreen.php @@ -1,6 +1,5 @@ showTrace()) { echo $this->formatTrace($e->getTrace()); } - $this->fail($e->getMessage()); + + $this->fail(IcingaException::describe($e)); } } diff --git a/library/Icinga/Cli/Params.php b/library/Icinga/Cli/Params.php index 5c6cbfe8b..85d5cd26f 100644 --- a/library/Icinga/Cli/Params.php +++ b/library/Icinga/Cli/Params.php @@ -1,9 +1,10 @@ params[$matches[1]] = $matches[2]; + } elseif (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') { $this->params[$key] = true; } elseif (array_key_exists($key, $this->params)) { if (!is_array($this->params[$key])) { @@ -156,13 +162,36 @@ class Params return $default; } + /** + * Require a parameter + * + * @param string $name Name of the parameter + * @param bool $strict Whether the parameter's value must not be the empty string + * + * @return mixed + * + * @throws MissingParameterException If the parameter was not given + */ + public function getRequired($name, $strict = true) + { + if ($this->has($name)) { + $value = $this->get($name); + if (! $strict || strlen($value) > 0) { + return $value; + } + } + $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name); + $e->setParameter($name); + throw $e; + } + /** * Set a value for the given option * * @param string $key The option name * @param mixed $value The value to set * - * @return self + * @return $this */ public function set($key, $value) { @@ -175,7 +204,7 @@ class Params * * @param string|array $keys The option or options to remove * - * @return self + * @return $this */ public function remove($keys = array()) { @@ -234,12 +263,36 @@ class Params return $result; } + /** + * Require and remove a parameter + * + * @param string $name Name of the parameter + * @param bool $strict Whether the parameter's value must not be the empty string + * + * @return mixed + * + * @throws MissingParameterException If the parameter was not given + */ + public function shiftRequired($name, $strict = true) + { + if ($this->has($name)) { + $value = $this->get($name); + if (! $strict || strlen($value) > 0) { + $this->shift($name); + return $value; + } + } + $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name); + $e->setParameter($name); + throw $e; + } + /** * Put the given value onto the argument stack * * @param mixed $key The argument * - * @return self + * @return $this */ public function unshift($key) { diff --git a/library/Icinga/Cli/Screen.php b/library/Icinga/Cli/Screen.php index 5b687f3ab..2737583c4 100644 --- a/library/Icinga/Cli/Screen.php +++ b/library/Icinga/Cli/Screen.php @@ -1,6 +1,5 @@ data = array(); - - foreach ($data as $key => $value) { + // Convert all embedded arrays to ConfigObjects as well + foreach ($data as & $value) { if (is_array($value)) { - $this->data[$key] = new static($value); - } else { - $this->data[$key] = $value; + $value = new static($value); } } + + parent::__construct($data); } /** @@ -56,16 +47,6 @@ class ConfigObject implements Countable, Iterator, ArrayAccess $this->data = $array; } - /** - * Return the count of available sections and properties - * - * @return int - */ - public function count() - { - return count($this->data); - } - /** * Reset the current position of $this->data * @@ -192,13 +173,15 @@ class ConfigObject implements Countable, Iterator, ArrayAccess /** * Add a new property or section * - * @param string $key The name of the new property or section - * @param mixed $value The value to set for the new property or section + * @param string $key The name of the new property or section + * @param mixed $value The value to set for the new property or section + * + * @throws ProgrammingError If the key is null */ public function offsetSet($key, $value) { if ($key === null) { - throw new LogicException('Appending values without an explicit key is not supported'); + throw new ProgrammingError('Appending values without an explicit key is not supported'); } $this->$key = $value; @@ -273,9 +256,9 @@ class ConfigObject implements Countable, Iterator, ArrayAccess /** * Merge the given data with this config * - * @param array|Config $data An array or a config + * @param array|ConfigObject $data An array or a config * - * @return self + * @return $this */ public function merge($data) { diff --git a/library/Icinga/Data/ConnectionInterface.php b/library/Icinga/Data/ConnectionInterface.php index dfdb4a6f2..1906a85f8 100644 --- a/library/Icinga/Data/ConnectionInterface.php +++ b/library/Icinga/Data/ConnectionInterface.php @@ -1,6 +1,5 @@ data = (array) $array; + $this->data = $data; } /** - * Instantiate a Query object + * Set the name of the column to map array keys on * - * @return SimpleQuery + * @param string $name + * + * @return $this + */ + public function setKeyColumn($name) + { + $this->keyColumn = $name; + return $this; + } + + /** + * Return the name of the column to map array keys on + * + * @return string + */ + public function getKeyColumn() + { + return $this->keyColumn; + } + + /** + * Provide a query for this data source + * + * @return SimpleQuery */ public function select() { return new SimpleQuery($this); } + /** + * Fetch and return all rows of the given query's result set using an iterator + * + * @param SimpleQuery $query + * + * @return ArrayIterator + */ + public function query(SimpleQuery $query) + { + return new ArrayIterator($this->fetchAll($query)); + } + + /** + * Fetch and return a column of all rows of the result set as an array + * + * @param SimpleQuery $query + * + * @return array + */ public function fetchColumn(SimpleQuery $query) { $result = array(); @@ -40,9 +109,17 @@ class ArrayDatasource implements Selectable $arr = (array) $row; $result[] = array_shift($arr); } + return $result; } + /** + * Fetch and return all rows of the given query's result as a flattened key/value based array + * + * @param SimpleQuery $query + * + * @return array + */ public function fetchPairs(SimpleQuery $query) { $result = array(); @@ -54,104 +131,164 @@ class ArrayDatasource implements Selectable $keys[1] = $keys[0]; } } + $result[$row->{$keys[0]}] = $row->{$keys[1]}; } + return $result; } + /** + * Fetch and return the first row of the given query's result + * + * @param SimpleQuery $query + * + * @return object|false The row or false in case the result is empty + */ public function fetchRow(SimpleQuery $query) { $result = $this->getResult($query); if (empty($result)) { return false; } - return $result[0]; + + return array_shift($result); } + /** + * Fetch and return all rows of the given query's result as an array + * + * @param SimpleQuery $query + * + * @return array + */ public function fetchAll(SimpleQuery $query) { return $this->getResult($query); } + /** + * Count all rows of the given query's result + * + * @param SimpleQuery $query + * + * @return int + */ public function count(SimpleQuery $query) { - $this->createResult($query); - return count($this->result); + if ($this->count === null) { + $this->count = count($this->createResult($query)); + } + + return $this->count; } + /** + * Create and return the result for the given query + * + * @param SimpleQuery $query + * + * @return array + */ protected function createResult(SimpleQuery $query) { - if ($this->hasResult()) { - return $this; - } - $result = array(); - $columns = $query->getColumns(); $filter = $query->getFilter(); - foreach ($this->data as & $row) { + $offset = $query->hasOffset() ? $query->getOffset() : 0; + $limit = $query->hasLimit() ? $query->getLimit() : 0; + + $foundStringKey = false; + $result = array(); + $skipped = 0; + foreach ($this->data as $key => $row) { + if (is_string($key) && $this->keyColumn !== null && !isset($row->{$this->keyColumn})) { + $row = clone $row; // Make sure that this won't affect the actual data + $row->{$this->keyColumn} = $key; + } if (! $filter->matches($row)) { continue; + } elseif ($skipped < $offset) { + $skipped++; + continue; } // Get only desired columns if asked so - if (empty($columns)) { - $result[] = $row; - } else { - $c_row = (object) array(); - foreach ($columns as $alias => $key) { - if (is_int($alias)) { - $alias = $key; + if (! empty($columns)) { + $filteredRow = (object) array(); + foreach ($columns as $alias => $name) { + if (! is_string($alias)) { + $alias = $name; } - if (isset($row->$key)) { - $c_row->$alias = $row->$key; + + if (isset($row->$name)) { + $filteredRow->$alias = $row->$name; } else { - $c_row->$alias = null; + $filteredRow->$alias = null; } } - $result[] = $c_row; + } else { + $filteredRow = $row; + } + + $foundStringKey |= is_string($key); + $result[$key] = $filteredRow; + + if (count($result) === $limit) { + break; } } // Sort the result - if ($query->hasOrder()) { - usort($result, array($query, 'compare')); - } - - $this->setResult($result); - return $this; - } - - protected function getLimitedResult($query) - { - if ($query->hasLimit()) { - if ($query->hasOffset()) { - $offset = $query->getOffset(); + if ($foundStringKey) { + uasort($result, array($query, 'compare')); } else { - $offset = 0; + usort($result, array($query, 'compare')); } - return array_slice($this->result, $offset, $query->getLimit()); - } else { - return $this->result; + } elseif (! $foundStringKey) { + $result = array_values($result); } + + return $result; } + /** + * Return whether a query result exists + * + * @return bool + */ protected function hasResult() { return $this->result !== null; } - protected function setResult($result) + /** + * Set the current result + * + * @param array $result + * + * @return $this + */ + protected function setResult(array $result) { - return $this->result = $result; + $this->result = $result; + return $this; } + /** + * Return the result for the given query + * + * @param SimpleQuery $query + * + * @return array + */ protected function getResult(SimpleQuery $query) { if (! $this->hasResult()) { - $this->createResult($query); + $this->setResult($this->createResult($query)); } - return $this->getLimitedResult($query); + + return $this->result; } } diff --git a/library/Icinga/Data/Db/DbConnection.php b/library/Icinga/Data/Db/DbConnection.php index 38153a1cf..ba7a3c6f0 100644 --- a/library/Icinga/Data/Db/DbConnection.php +++ b/library/Icinga/Data/Db/DbConnection.php @@ -1,22 +1,32 @@ config = $config; - if (isset($config->prefix)) { - $this->tablePrefix = $config->prefix; - } $this->connect(); } /** * Provide a query on this connection * - * @return Query + * @return DbQuery */ public function select() { return new DbQuery($this); } + /** + * Fetch and return all rows of the given query's result set using an iterator + * + * @param DbQuery $query + * + * @return Iterator + */ + public function query(DbQuery $query) + { + return $query->getSelectQuery()->query(); + } + /** * Getter for database type * @@ -184,7 +203,7 @@ class DbConnection implements Selectable * * @param string $prefix * - * @return self + * @return $this */ public function setTablePrefix($prefix) { @@ -192,6 +211,18 @@ class DbConnection implements Selectable return $this; } + /** + * Count all rows of the result set + * + * @param DbQuery $query + * + * @return int + */ + public function count(DbQuery $query) + { + return (int) $this->dbAdapter->fetchOne($query->getCountQuery()); + } + /** * Retrieve an array containing all rows of the result set * @@ -201,10 +232,7 @@ class DbConnection implements Selectable */ public function fetchAll(DbQuery $query) { - Benchmark::measure('DB is fetching All'); - $result = $this->dbAdapter->fetchAll($query->getSelectQuery()); - Benchmark::measure('DB fetch done'); - return $result; + return $this->dbAdapter->fetchAll($query->getSelectQuery()); } /** @@ -216,21 +244,17 @@ class DbConnection implements Selectable */ public function fetchRow(DbQuery $query) { - Benchmark::measure('DB is fetching row'); - $result = $this->dbAdapter->fetchRow($query->getSelectQuery()); - Benchmark::measure('DB row done'); - return $result; + return $this->dbAdapter->fetchRow($query->getSelectQuery()); } /** - * Fetch a column of all rows of the result set as an array + * Fetch the first column of all rows of the result set as an array * * @param DbQuery $query - * @param int $columnIndex Index of the column to fetch * * @return array */ - public function fetchColumn(DbQuery $query, $columnIndex = 0) + public function fetchColumn(DbQuery $query) { return $this->dbAdapter->fetchCol($query->getSelectQuery()); } @@ -260,4 +284,196 @@ class DbConnection implements Selectable { return $this->dbAdapter->fetchPairs($query->getSelectQuery()); } + + /** + * Insert a table row with the given data + * + * Pass an array with a column name (the same as in $bind) and a PDO::PARAM_* constant as value + * as third parameter $types to define a different type than string for a particular column. + * + * @param string $table + * @param array $bind + * @param array $types + * + * @return int The number of affected rows + */ + public function insert($table, array $bind, array $types = array()) + { + $values = array(); + foreach ($bind as $column => $_) { + $values[] = ':' . $column; + } + + $sql = 'INSERT INTO ' . $table + . ' (' . join(', ', array_keys($bind)) . ') ' + . 'VALUES (' . join(', ', $values) . ')'; + $statement = $this->dbAdapter->prepare($sql); + + foreach ($bind as $column => $value) { + $type = isset($types[$column]) ? $types[$column] : PDO::PARAM_STR; + $statement->bindValue(':' . $column, $value, $type); + } + + $statement->execute(); + return $statement->rowCount(); + } + + /** + * Update table rows with the given data, optionally limited by using a filter + * + * Pass an array with a column name (the same as in $bind) and a PDO::PARAM_* constant as value + * as fourth parameter $types to define a different type than string for a particular column. + * + * @param string $table + * @param array $bind + * @param Filter $filter + * @param array $types + * + * @return int The number of affected rows + */ + public function update($table, array $bind, Filter $filter = null, array $types = array()) + { + $set = array(); + foreach ($bind as $column => $_) { + $set[] = $column . ' = :' . $column; + } + + $sql = 'UPDATE ' . $table + . ' SET ' . join(', ', $set) + . ($filter ? ' WHERE ' . $this->renderFilter($filter) : ''); + $statement = $this->dbAdapter->prepare($sql); + + foreach ($bind as $column => $value) { + $type = isset($types[$column]) ? $types[$column] : PDO::PARAM_STR; + $statement->bindValue(':' . $column, $value, $type); + } + + $statement->execute(); + return $statement->rowCount(); + } + + /** + * Delete table rows, optionally limited by using a filter + * + * @param string $table + * @param Filter $filter + * + * @return int The number of affected rows + */ + public function delete($table, Filter $filter = null) + { + return $this->dbAdapter->delete($table, $filter ? $this->renderFilter($filter) : ''); + } + + /** + * Render and return the given filter as SQL-WHERE clause + * + * @param Filter $filter + * + * @return string + */ + public function renderFilter(Filter $filter, $level = 0) + { + // TODO: This is supposed to supersede DbQuery::renderFilter() + $where = ''; + if ($filter->isChain()) { + if ($filter instanceof FilterAnd) { + $operator = ' AND '; + } elseif ($filter instanceof FilterOr) { + $operator = ' OR '; + } elseif ($filter instanceof FilterNot) { + $operator = ' AND '; + $where .= ' NOT '; + } else { + throw new ProgrammingError('Cannot render filter: %s', get_class($filter)); + } + + if (! $filter->isEmpty()) { + $parts = array(); + foreach ($filter->filters() as $filterPart) { + $part = $this->renderFilter($filterPart, $level + 1); + if ($part) { + $parts[] = $part; + } + } + + if (! empty($parts)) { + if ($level > 0) { + $where .= ' (' . implode($operator, $parts) . ') '; + } else { + $where .= implode($operator, $parts); + } + } + } else { + return ''; // Explicitly return the empty string due to the FilterNot case + } + } else { + $where .= $this->renderFilterExpression($filter); + } + + return $where; + } + + /** + * Render and return the given filter expression + * + * @param Filter $filter + * + * @return string + */ + protected function renderFilterExpression(Filter $filter) + { + $column = $filter->getColumn(); + $sign = $filter->getSign(); + $value = $filter->getExpression(); + + if (is_array($value) && $sign === '=') { + // TODO: Should we support this? Doesn't work for blub* + return $column . ' IN (' . $this->dbAdapter->quote($value) . ')'; + } elseif ($sign === '=' && strpos($value, '*') !== false) { + return $column . ' LIKE ' . $this->dbAdapter->quote(preg_replace('~\*~', '%', $value)); + } elseif ($sign === '!=' && strpos($value, '*') !== false) { + return $column . ' NOT LIKE ' . $this->dbAdapter->quote(preg_replace('~\*~', '%', $value)); + } else { + return $column . ' ' . $sign . ' ' . $this->dbAdapter->quote($value); + } + } + + public function inspect() + { + $insp = new Inspection('Db Connection'); + try { + $this->getDbAdapter()->getConnection(); + $config = $this->dbAdapter->getConfig(); + $insp->write(sprintf( + 'Connection to %s as %s on %s:%s successful', + $config['dbname'], + $config['username'], + $config['host'], + $config['port'] + )); + switch ($this->dbType) { + case 'mysql': + $rows = $this->dbAdapter->query( + 'SHOW VARIABLES WHERE variable_name ' . + 'IN (\'version\', \'protocol_version\', \'version_compile_os\');' + )->fetchAll(); + $sqlinsp = new Inspection('MySQL'); + foreach ($rows as $row) { + $sqlinsp->write($row->variable_name . ': ' . $row->value); + } + $insp->write($sqlinsp); + break; + case 'pgsql': + $row = $this->dbAdapter->query('SELECT version();')->fetchAll(); + $sqlinsp = new Inspection('PostgreSQL'); + $sqlinsp->write($row[0]->version); + $insp->write($sqlinsp); + break; + } + } catch (Exception $e) { + return $insp->error(sprintf('Connection failed %s', $e->getMessage())); + } + return $insp; + } } diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php index bad1261aa..28c55efe8 100644 --- a/library/Icinga/Data/Db/DbQuery.php +++ b/library/Icinga/Data/Db/DbQuery.php @@ -1,18 +1,16 @@ db = $this->ds->getDbAdapter(); + $this->select = $this->db->select(); parent::init(); } + /** + * Get whether or not the query is a sub query + */ + public function getIsSubQuery() + { + return $this->isSubQuery; + } + + /** + * Set whether or not the query is a sub query + * + * @param bool $isSubQuery + * + * @return $this + */ + public function setIsSubQuery($isSubQuery = true) + { + $this->isSubQuery = (bool) $isSubQuery; + return $this; + } + public function setUseSubqueryCount($useSubqueryCount = true) { $this->useSubqueryCount = $useSubqueryCount; return $this; } + public function from($target, array $fields = null) + { + parent::from($target, $fields); + $this->select->from($this->target, array()); + return $this; + } + public function where($condition, $value = null) { // $this->count = $this->select = null; @@ -86,12 +112,19 @@ class DbQuery extends SimpleQuery protected function dbSelect() { - if ($this->select === null) { - $this->select = $this->db->select()->from($this->target, array()); - } return clone $this->select; } + /** + * Return the underlying select + * + * @return Zend_Db_Select + */ + public function select() + { + return $this->select; + } + /** * Get the select query * @@ -107,14 +140,15 @@ class DbQuery extends SimpleQuery && $this->getDatasource()->getDbType() === 'pgsql' && $select->getPart(Zend_Db_Select::DISTINCT) === true) { foreach ($this->getOrder() as $fieldAndDirection) { - if (array_search($fieldAndDirection[0], $this->columns) === false) { + if (array_search($fieldAndDirection[0], $this->columns, true) === false) { $this->columns[] = $fieldAndDirection[0]; } } } - if ($this->group) { - $select->group($this->group); + $group = $this->getGroup(); + if ($group) { + $select->group($group); } $select->columns($this->columns); @@ -154,7 +188,7 @@ class DbQuery extends SimpleQuery $op = ' AND '; $str .= ' NOT '; } else { - throw new IcingaException( + throw new QueryException( 'Cannot render filter: %s', $filter ); @@ -167,10 +201,12 @@ class DbQuery extends SimpleQuery $parts[] = $filterPart; } } - if ($level > 0) { - $str .= ' (' . implode($op, $parts) . ') '; - } else { - $str .= implode($op, $parts); + if (! empty($parts)) { + if ($level > 0) { + $str .= ' (' . implode($op, $parts) . ') '; + } else { + $str .= implode($op, $parts); + } } } } else { @@ -213,7 +249,7 @@ class DbQuery extends SimpleQuery if (! $value) { /* NOTE: It's too late to throw exceptions, we might finish in __toString - throw new IcingaException(sprintf( + throw new QueryException(sprintf( '"%s" is not a valid time expression', $value )); @@ -251,12 +287,27 @@ class DbQuery extends SimpleQuery if ($this->isTimestamp($col)) { $expression = $this->valueToTimestamp($expression); } + if (is_array($expression) && $sign === '=') { // TODO: Should we support this? Doesn't work for blub* return $col . ' IN (' . $this->escapeForSql($expression) . ')'; } elseif ($sign === '=' && strpos($expression, '*') !== false) { + if ($expression === '*') { + // We'll ignore such filters as it prevents index usage and because "*" means anything, anything means + // all whereas all means that whether we use a filter to match anything or no filter at all makes no + // difference, except for performance reasons... + return ''; + } + return $col . ' LIKE ' . $this->escapeForSql($this->escapeWildcards($expression)); } elseif ($sign === '!=' && strpos($expression, '*') !== false) { + if ($expression === '*') { + // We'll ignore such filters as it prevents index usage and because "*" means nothing, so whether we're + // using a real column with a valid comparison here or just an expression which cannot be evaluated to + // true makes no difference, except for performance reasons... + return $this->escapeForSql(0); + } + return $col . ' NOT LIKE ' . $this->escapeForSql($this->escapeWildcards($expression)); } else { return $col . ' ' . $sign . ' ' . $this->escapeForSql($expression); @@ -272,18 +323,16 @@ class DbQuery extends SimpleQuery { // TODO: there may be situations where we should clone the "select" $count = $this->dbSelect(); - if ($this->group) { - $count->group($this->group); - } $this->applyFilterSql($count); - if ($this->useSubqueryCount || $this->group) { + $group = $this->getGroup(); + if ($this->useSubqueryCount || $group) { $count->columns($this->columns); + if ($group) { + $count->group($group); + } $columns = array('cnt' => 'COUNT(*)'); return $this->db->select()->from($count, $columns); } - if ($this->maxCount !== null) { - return $this->db->select()->from($count->limit($this->maxCount)); - } $count->columns(array('cnt' => 'COUNT(*)')); return $count; @@ -297,10 +346,9 @@ class DbQuery extends SimpleQuery public function count() { if ($this->count === null) { - Benchmark::measure('DB is counting'); - $this->count = $this->db->fetchOne($this->getCountQuery()); - Benchmark::measure('DB finished count'); + $this->count = parent::count(); } + return $this->count; } @@ -320,9 +368,8 @@ class DbQuery extends SimpleQuery public function __clone() { - if ($this->select) { - $this->select = clone $this->select; - } + parent::__clone(); + $this->select = clone $this->select; } /** @@ -330,7 +377,8 @@ class DbQuery extends SimpleQuery */ public function __toString() { - return (string) $this->getSelectQuery(); + $select = (string) $this->getSelectQuery(); + return $this->getIsSubQuery() ? ('(' . $select . ')') : $select; } /** @@ -345,4 +393,176 @@ class DbQuery extends SimpleQuery $this->group = $group; return $this; } + + /** + * Return the GROUP BY clause + * + * @return string|array + */ + public function getGroup() + { + return $this->group; + } + + /** + * Return whether the given table has been joined + * + * @param string $table + * + * @return bool + */ + public function hasJoinedTable($table) + { + $fromPart = $this->select->getPart(Zend_Db_Select::FROM); + if (isset($fromPart[$table])) { + return true; + } + + foreach ($fromPart as $options) { + if ($options['tableName'] === $table && $options['joinType'] !== Zend_Db_Select::FROM) { + return true; + } + } + + return false; + } + + /** + * Return the alias used for joining the given table + * + * @param string $table + * + * @return string|null null in case no alias is being used + * + * @throws ProgrammingError In case the given table has not been joined + */ + public function getJoinedTableAlias($table) + { + $fromPart = $this->select->getPart(Zend_Db_Select::FROM); + if (isset($fromPart[$table])) { + if ($fromPart[$table]['joinType'] === Zend_Db_Select::FROM) { + throw new ProgrammingError('Table "%s" has not been joined', $table); + } + + return; // No alias in use + } + + foreach ($fromPart as $alias => $options) { + if ($options['tableName'] === $table && $options['joinType'] !== Zend_Db_Select::FROM) { + return $alias; + } + } + + throw new ProgrammingError('Table "%s" has not been joined', $table); + } + + /** + * Add an INNER JOIN table and colums to the query + * + * @param array|string|Zend_Db_Expr $name The table name + * @param string $cond Join on this condition + * @param array|string $cols The columns to select from the joined table + * @param string $schema The database name to specify, if any + * + * @return $this + */ + public function join($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null) + { + $this->select->joinInner($name, $cond, $cols, $schema); + return $this; + } + + /** + * Add an INNER JOIN table and colums to the query + * + * @param array|string|Zend_Db_Expr $name The table name + * @param string $cond Join on this condition + * @param array|string $cols The columns to select from the joined table + * @param string $schema The database name to specify, if any + * + * @return $this + */ + public function joinInner($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null) + { + $this->select->joinInner($name, $cond, $cols, $schema); + return $this; + } + + /** + * Add a LEFT OUTER JOIN table and colums to the query + * + * @param array|string|Zend_Db_Expr $name The table name + * @param string $cond Join on this condition + * @param array|string $cols The columns to select from the joined table + * @param string $schema The database name to specify, if any + * + * @return $this + */ + public function joinLeft($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null) + { + $this->select->joinLeft($name, $cond, $cols, $schema); + return $this; + } + + /** + * Add a RIGHT OUTER JOIN table and colums to the query + * + * @param array|string|Zend_Db_Expr $name The table name + * @param string $cond Join on this condition + * @param array|string $cols The columns to select from the joined table + * @param string $schema The database name to specify, if any + * + * @return $this + */ + public function joinRight($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null) + { + $this->select->joinRight($name, $cond, $cols, $schema); + return $this; + } + + /** + * Add a FULL OUTER JOIN table and colums to the query + * + * @param array|string|Zend_Db_Expr $name The table name + * @param string $cond Join on this condition + * @param array|string $cols The columns to select from the joined table + * @param string $schema The database name to specify, if any + * + * @return $this + */ + public function joinFull($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null) + { + $this->select->joinFull($name, $cond, $cols, $schema); + return $this; + } + + /** + * Add a CROSS JOIN table and colums to the query + * + * @param array|string|Zend_Db_Expr $name The table name + * @param array|string $cols The columns to select from the joined table + * @param string $schema The database name to specify, if any + * + * @return $this + */ + public function joinCross($name, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null) + { + $this->select->joinCross($name, $cols, $schema); + return $this; + } + + /** + * Add a NATURAL JOIN table and colums to the query + * + * @param array|string|Zend_Db_Expr $name The table name + * @param array|string $cols The columns to select from the joined table + * @param string $schema The database name to specify, if any + * + * @return $this + */ + public function joinNatural($name, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null) + { + $this->select->joinNatural($name, $cols, $schema); + return $this; + } } diff --git a/library/Icinga/Data/Extensible.php b/library/Icinga/Data/Extensible.php new file mode 100644 index 000000000..8b9f39d42 --- /dev/null +++ b/library/Icinga/Data/Extensible.php @@ -0,0 +1,22 @@ +filters() as $filter) { @@ -117,6 +119,12 @@ abstract class FilterChain extends Filter return $this->operatorSymbol; } + public function setAllowedFilterColumns(array $columns) + { + $this->allowedColumns = $columns; + return $this; + } + public function listFilteredColumns() { $columns = array(); @@ -154,7 +162,7 @@ abstract class FilterChain extends Filter * * Useful for debugging only * - * @return string + * @return string */ public function __toString() { @@ -197,11 +205,41 @@ abstract class FilterChain extends Filter public function addFilter(Filter $filter) { + if (! empty($this->allowedColumns)) { + $this->validateFilterColumns($filter); + } + $this->filters[] = $filter; $filter->setId($this->getId() . '-' . $this->count()); return $this; } + protected function validateFilterColumns(Filter $filter) + { + if ($filter->isExpression()) { + $valid = false; + foreach ($this->allowedColumns as $column) { + if (is_callable($column)) { + if (call_user_func($column, $filter->getColumn())) { + $valid = true; + break; + } + } elseif ($filter->getColumn() === $column) { + $valid = true; + break; + } + } + + if (! $valid) { + throw new QueryException('Invalid filter column provided: %s', $filter->getColumn()); + } + } else { + foreach ($filter->filters() as $subFilter) { + $this->validateFilterColumns($subFilter); + } + } + } + public function &filters() { return $this->filters; @@ -217,5 +255,5 @@ abstract class FilterChain extends Filter foreach ($this->filters as & $filter) { $filter = clone $filter; } - } + } } diff --git a/library/Icinga/Data/Filter/FilterEqual.php b/library/Icinga/Data/Filter/FilterEqual.php index 66e4c06ad..4c090b3ce 100644 --- a/library/Icinga/Data/Filter/FilterEqual.php +++ b/library/Icinga/Data/Filter/FilterEqual.php @@ -1,6 +1,5 @@ column = $column; $this->sign = $sign; $this->expression = $expression; diff --git a/library/Icinga/Data/Filter/FilterGreaterThan.php b/library/Icinga/Data/Filter/FilterGreaterThan.php index 62ba16fca..14566bc22 100644 --- a/library/Icinga/Data/Filter/FilterGreaterThan.php +++ b/library/Icinga/Data/Filter/FilterGreaterThan.php @@ -1,6 +1,5 @@ add(). We must no require classes implementing this interface to + * implement redundant methods over and over again. This interface must be moved to the namespace Icinga\Data\Filter. + * It lacks documentation. */ interface Filterable { diff --git a/library/Icinga/Data/Identifiable.php b/library/Icinga/Data/Identifiable.php index cfa727a1d..8a5797eed 100644 --- a/library/Icinga/Data/Identifiable.php +++ b/library/Icinga/Data/Identifiable.php @@ -1,6 +1,5 @@ description = $description; + } + + /** + * Get the name of this Inspection + * + * @return mixed + */ + public function getDescription() + { + return $this->description; + } + + /** + * Append the given log entry or nested inspection + * + * @throws ProgrammingError When called after erroring + * + * @param $entry string|Inspection A log entry or nested inspection + */ + public function write($entry) + { + if (isset($this->error)) { + throw new ProgrammingError('Inspection object used after error'); + } + if ($entry instanceof Inspection) { + $this->log[$entry->description] = $entry->toArray(); + } else { + Logger::debug($entry); + $this->log[] = $entry; + } + } + + /** + * Append the given log entry and fail this inspection with the given error + * + * @param $entry string|Inspection A log entry or nested inspection + * + * @throws ProgrammingError When called multiple times + * + * @return this fluent interface + */ + public function error($entry) + { + if (isset($this->error)) { + throw new ProgrammingError('Inspection object used after error'); + } + Logger::error($entry); + $this->log[] = $entry; + $this->error = $entry; + return $this; + } + + /** + * If the inspection resulted in an error + * + * @return bool + */ + public function hasError() + { + return isset($this->error); + } + + /** + * The error that caused the inspection to fail + * + * @return Inspection|string + */ + public function getError() + { + return $this->error; + } + + /** + * Convert the inspection to an array + * + * @return array An array of strings that describe the state in a human-readable form, each array element + * represents one log entry about this object. + */ + public function toArray() + { + return $this->log; + } + + /** + * Return a text representation of the inspection log entries + */ + public function __toString() + { + return sprintf( + 'Inspection: description: "%s" error: "%s"', + $this->description, + $this->error + ); + } +} diff --git a/library/Icinga/Data/Limitable.php b/library/Icinga/Data/Limitable.php index 380eae9f9..96c5046a7 100644 --- a/library/Icinga/Data/Limitable.php +++ b/library/Icinga/Data/Limitable.php @@ -1,6 +1,5 @@ baseQuery = $query; $this->xAxisColumn = $xAxisColumn; $this->yAxisColumn = $yAxisColumn; - $this->prepareQueries()->adjustSorting(); } /** - * Prepare the queries used for the pre processing + * Set the filter to apply on the query for the x axis * - * @return self + * @param Filter $filter + * + * @return $this */ - protected function prepareQueries() + public function setXAxisFilter(Filter $filter = null) { - $this->xAxisQuery = clone $this->baseQuery; - $this->xAxisQuery->group($this->xAxisColumn); - $this->xAxisQuery->columns(array($this->xAxisColumn)); - $this->xAxisQuery->setUseSubqueryCount(); - $this->yAxisQuery = clone $this->baseQuery; - $this->yAxisQuery->group($this->yAxisColumn); - $this->yAxisQuery->columns(array($this->yAxisColumn)); - $this->yAxisQuery->setUseSubqueryCount(); - + $this->xAxisFilter = $filter; return $this; } /** - * Set a default sorting for the x- and y-axis without losing any existing rules + * Set the filter to apply on the query for the y axis * - * @return self + * @param Filter $filter + * + * @return $this */ - protected function adjustSorting() + public function setYAxisFilter(Filter $filter = null) { - if (false === $this->xAxisQuery->hasOrder($this->xAxisColumn)) { - $this->xAxisQuery->order($this->xAxisColumn, 'ASC'); - } - - if (false === $this->yAxisQuery->hasOrder($this->yAxisColumn)) { - $this->yAxisQuery->order($this->yAxisColumn, 'ASC'); - } - + $this->yAxisFilter = $filter; return $this; } @@ -109,7 +111,7 @@ class PivotTable */ protected function getPaginationParameter($axis, $param, $default = null) { - $request = Icinga::app()->getFrontController()->getRequest(); + $request = Icinga::app()->getRequest(); $value = $request->getParam($param, ''); if (strpos($value, ',') > 0) { @@ -120,6 +122,56 @@ class PivotTable return $default !== null ? $default : 0; } + /** + * Query horizontal (x) axis + * + * @return SimpleQuery + */ + protected function queryXAxis() + { + if ($this->xAxisQuery === null) { + $this->xAxisQuery = clone $this->baseQuery; + $this->xAxisQuery->group($this->xAxisColumn); + $this->xAxisQuery->columns(array($this->xAxisColumn)); + $this->xAxisQuery->setUseSubqueryCount(); + + if ($this->xAxisFilter !== null) { + $this->xAxisQuery->addFilter($this->xAxisFilter); + } + + if (! $this->xAxisQuery->hasOrder($this->xAxisColumn)) { + $this->xAxisQuery->order($this->xAxisColumn, 'asc'); + } + } + + return $this->xAxisQuery; + } + + /** + * Query vertical (y) axis + * + * @return SimpleQuery + */ + protected function queryYAxis() + { + if ($this->yAxisQuery === null) { + $this->yAxisQuery = clone $this->baseQuery; + $this->yAxisQuery->group($this->yAxisColumn); + $this->yAxisQuery->columns(array($this->yAxisColumn)); + $this->yAxisQuery->setUseSubqueryCount(); + + if ($this->yAxisFilter !== null) { + $this->yAxisQuery->addFilter($this->yAxisFilter); + } + + if (! $this->yAxisQuery->hasOrder($this->yAxisColumn)) { + $this->yAxisQuery->order($this->yAxisColumn, 'asc'); + } + } + + return $this->yAxisQuery; + } + /** * Return a pagination adapter for the x axis query * @@ -142,9 +194,10 @@ class PivotTable } } - $this->xAxisQuery->limit($limit, $page > 0 ? ($page - 1) * $limit : 0); + $query = $this->queryXAxis(); + $query->limit($limit, $page > 0 ? ($page - 1) * $limit : 0); - $paginator = new Zend_Paginator(new QueryAdapter($this->xAxisQuery)); + $paginator = new Zend_Paginator(new QueryAdapter($query)); $paginator->setItemCountPerPage($limit); $paginator->setCurrentPageNumber($page); return $paginator; @@ -172,9 +225,10 @@ class PivotTable } } - $this->yAxisQuery->limit($limit, $page > 0 ? ($page - 1) * $limit : 0); + $query = $this->queryYAxis(); + $query->limit($limit, $page > 0 ? ($page - 1) * $limit : 0); - $paginator = new Zend_Paginator(new QueryAdapter($this->yAxisQuery)); + $paginator = new Zend_Paginator(new QueryAdapter($query)); $paginator->setItemCountPerPage($limit); $paginator->setCurrentPageNumber($page); return $paginator; @@ -187,10 +241,23 @@ class PivotTable */ public function toArray() { - $pivot = array(); - $xAxis = $this->xAxisQuery->fetchColumn(); - $yAxis = $this->yAxisQuery->fetchColumn(); + if ( + ($this->xAxisFilter === null && $this->yAxisFilter === null) + || ($this->xAxisFilter !== null && $this->yAxisFilter !== null) + ) { + $xAxis = $this->queryXAxis()->fetchColumn(); + $yAxis = $this->queryYAxis()->fetchColumn(); + } else { + if ($this->xAxisFilter !== null) { + $xAxis = $this->queryXAxis()->fetchColumn(); + $yAxis = $this->queryYAxis()->where($this->xAxisColumn, $xAxis)->fetchColumn(); + } else { // $this->yAxisFilter !== null + $yAxis = $this->queryYAxis()->fetchColumn(); + $xAxis = $this->queryXAxis()->where($this->yAxisColumn, $yAxis)->fetchColumn(); + } + } + $pivot = array(); if (!empty($xAxis) && !empty($yAxis)) { $this->baseQuery->where($this->xAxisColumn, $xAxis)->where($this->yAxisColumn, $yAxis); @@ -200,7 +267,7 @@ class PivotTable } } - foreach ($this->baseQuery->fetchAll() as $row) { + foreach ($this->baseQuery as $row) { $pivot[$row->{$this->yAxisColumn}][$row->{$this->xAxisColumn}] = $row; } } diff --git a/library/Icinga/Data/QueryInterface.php b/library/Icinga/Data/QueryInterface.php index ef8c7c741..6ae6c16ca 100644 --- a/library/Icinga/Data/QueryInterface.php +++ b/library/Icinga/Data/QueryInterface.php @@ -1,9 +1,6 @@ compare *only*. + * + * @var array + */ + protected $flippedColumns; + /** * The columns you're using to sort the query result * @@ -64,6 +91,20 @@ class SimpleQuery implements QueryInterface, Queryable */ protected $limitOffset; + /** + * Whether to peek ahead for more results + * + * @var bool + */ + protected $peekAhead; + + /** + * Whether the query did not yield all available results + * + * @var bool + */ + protected $hasMore; + protected $filter; /** @@ -103,11 +144,97 @@ class SimpleQuery implements QueryInterface, Queryable } /** - * Choose a table and the colums you are interested in + * Return the current position of this query's iterator * - * Query will return all available columns if none are given here + * @return int + */ + public function getIteratorPosition() + { + return $this->iteratorPosition; + } + + /** + * Start or rewind the iteration + */ + public function rewind() + { + if ($this->iterator === null) { + $iterator = $this->ds->query($this); + if ($iterator instanceof IteratorAggregate) { + $this->iterator = $iterator->getIterator(); + } else { + $this->iterator = $iterator; + } + } + + $this->iterator->rewind(); + $this->iteratorPosition = null; + Benchmark::measure('Query result iteration started'); + } + + /** + * Fetch and return the current row of this query's result * - * @return self + * @return object + */ + public function current() + { + return $this->iterator->current(); + } + + /** + * Return whether the current row of this query's result is valid + * + * @return bool + */ + public function valid() + { + $valid = $this->iterator->valid(); + if ($valid && $this->peekAhead && $this->hasLimit() && $this->iteratorPosition + 1 === $this->getLimit()) { + $this->hasMore = true; + $valid = false; // We arrived at the last result, which is the requested extra row, so stop the iteration + } elseif (! $valid) { + $this->hasMore = false; + } + + if (! $valid) { + Benchmark::measure('Query result iteration finished'); + return false; + } elseif ($this->iteratorPosition === null) { + $this->iteratorPosition = 0; + } + + return true; + } + + /** + * Return the key for the current row of this query's result + * + * @return mixed + */ + public function key() + { + return $this->iterator->key(); + } + + /** + * Advance to the next row of this query's result + */ + public function next() + { + $this->iterator->next(); + $this->iteratorPosition += 1; + } + + /** + * Choose a table and the columns you are interested in + * + * Query will return all available columns if none are given here. + * + * @param mixed $target + * @param array $fields + * + * @return $this */ public function from($target, array $fields = null) { @@ -126,7 +253,7 @@ class SimpleQuery implements QueryInterface, Queryable * @param string $condition * @param mixed $value * - * @return self + * @return $this */ public function where($condition, $value = null) { @@ -162,6 +289,26 @@ class SimpleQuery implements QueryInterface, Queryable throw new IcingaException('This function does nothing and will be removed'); } + /** + * Split order field into its field and sort direction + * + * @param string $field + * + * @return array + */ + public function splitOrder($field) + { + $fieldAndDirection = explode(' ', $field, 2); + if (count($fieldAndDirection) === 1) { + $direction = null; + } else { + $field = $fieldAndDirection[0]; + $direction = (strtoupper(trim($fieldAndDirection[1])) === 'DESC') ? + Sortable::SORT_DESC : Sortable::SORT_ASC; + } + return array($field, $direction); + } + /** * Sort result set by the given field (and direction) * @@ -173,18 +320,14 @@ class SimpleQuery implements QueryInterface, Queryable * @param string $field * @param string $direction * - * @return self + * @return $this */ public function order($field, $direction = null) { if ($direction === null) { - $fieldAndDirection = explode(' ', $field, 2); - if (count($fieldAndDirection) === 1) { - $direction = self::SORT_ASC; - } else { - $field = $fieldAndDirection[0]; - $direction = (strtoupper(trim($fieldAndDirection[1])) === 'DESC') ? - Sortable::SORT_DESC : Sortable::SORT_ASC; + list($field, $direction) = $this->splitOrder($field); + if ($direction === null) { + $direction = Sortable::SORT_ASC; } } else { switch (($direction = strtoupper($direction))) { @@ -200,32 +343,42 @@ class SimpleQuery implements QueryInterface, Queryable return $this; } - public function compare($a, $b, $col_num = 0) + /** + * Compare $a with $b based on this query's sort rules and column aliases + * + * @param object $a + * @param object $b + * @param int $orderIndex + * + * @return int + */ + public function compare($a, $b, $orderIndex = 0) { - // Last column to sort reached, rows are considered being equal - if (! array_key_exists($col_num, $this->order)) { - return 0; - } - $col = $this->order[$col_num][0]; - $dir = $this->order[$col_num][1]; -// TODO: throw Exception if column is missing - //$res = strnatcmp(strtolower($a->$col), strtolower($b->$col)); - $res = @strcmp(strtolower($a->$col), strtolower($b->$col)); - if ($res === 0) { -// return $this->compare($a, $b, $col_num++); - - if (array_key_exists(++$col_num, $this->order)) { - return $this->compare($a, $b, $col_num); - } else { - return 0; - } - + if (! array_key_exists($orderIndex, $this->order)) { + return 0; // Last column to sort reached, rows are considered being equal } - if ($dir === self::SORT_ASC) { - return $res; + if ($this->flippedColumns === null) { + $this->flippedColumns = array_flip($this->columns); + } + + $column = $this->order[$orderIndex][0]; + if (array_key_exists($column, $this->flippedColumns)) { + $column = $this->flippedColumns[$column]; + } + + // TODO: throw Exception if column is missing + //$res = strnatcmp(strtolower($a->$column), strtolower($b->$column)); + $result = @strcmp(strtolower($a->$column), strtolower($b->$column)); + if ($result === 0) { + return $this->compare($a, $b, ++$orderIndex); + } + + $direction = $this->order[$orderIndex][1]; + if ($direction === self::SORT_ASC) { + return $result; } else { - return $res * -1; + return $result * -1; } } @@ -249,13 +402,53 @@ class SimpleQuery implements QueryInterface, Queryable return $this->order; } + /** + * Set whether this query should peek ahead for more results + * + * Enabling this causes the current query limit to be increased by one. The potential extra row being yielded will + * be removed from the result set. Note that this only applies when fetching multiple results of limited queries. + * + * @return $this + */ + public function peekAhead($state = true) + { + $this->peekAhead = (bool) $state; + return $this; + } + + /** + * Return whether this query did not yield all available results + * + * @return bool + * + * @throws ProgrammingError In case the query did not run yet + */ + public function hasMore() + { + if ($this->hasMore === null) { + throw new ProgrammingError('Query did not run. Cannot determine whether there are more results.'); + } + + return $this->hasMore; + } + + /** + * Return whether this query will or has yielded any result + * + * @return bool + */ + public function hasResult() + { + return $this->iteratorPosition !== null || $this->fetchRow() !== false; + } + /** * Set a limit count and offset to the query * * @param int $count Number of rows to return * @param int $offset Start returning after this many rows * - * @return self + * @return $this */ public function limit($count = null, $offset = null) { @@ -271,7 +464,7 @@ class SimpleQuery implements QueryInterface, Queryable */ public function hasLimit() { - return $this->limitCount !== null; + return $this->limitCount !== null && $this->limitCount > 0; } /** @@ -281,7 +474,7 @@ class SimpleQuery implements QueryInterface, Queryable */ public function getLimit() { - return $this->limitCount; + return $this->peekAhead && $this->hasLimit() ? $this->limitCount + 1 : $this->limitCount; } /** @@ -313,14 +506,22 @@ class SimpleQuery implements QueryInterface, Queryable * @param int $pageNumber Current page number * * @return Zend_Paginator + * + * @deprecated Use Icinga\Web\Controller::setupPaginationControl() and/or Icinga\Web\Widget\Paginator instead */ public function paginate($itemsPerPage = null, $pageNumber = null) { + trigger_error( + 'SimpleQuery::paginate() is deprecated. Use Icinga\Web\Controller::setupPaginationControl()' + . ' and/or Icinga\Web\Widget\Paginator instead', + E_USER_DEPRECATED + ); + if ($itemsPerPage === null || $pageNumber === null) { // Detect parameters from request - $request = Icinga::app()->getFrontController()->getRequest(); + $request = Icinga::app()->getRequest(); if ($itemsPerPage === null) { - $itemsPerPage = $request->getParam('limit', 20); + $itemsPerPage = $request->getParam('limit', 25); } if ($pageNumber === null) { $pageNumber = $request->getParam('page', 0); @@ -340,7 +541,18 @@ class SimpleQuery implements QueryInterface, Queryable */ public function fetchAll() { - return $this->ds->fetchAll($this); + Benchmark::measure('Fetching all results started'); + $results = $this->ds->fetchAll($this); + Benchmark::measure('Fetching all results finished'); + + if ($this->peekAhead && $this->hasLimit() && count($results) === $this->getLimit()) { + $this->hasMore = true; + array_pop($results); + } else { + $this->hasMore = false; + } + + return $results; } /** @@ -350,19 +562,31 @@ class SimpleQuery implements QueryInterface, Queryable */ public function fetchRow() { - return $this->ds->fetchRow($this); + Benchmark::measure('Fetching one row started'); + $row = $this->ds->fetchRow($this); + Benchmark::measure('Fetching one row finished'); + return $row; } /** - * Fetch a column of all rows of the result set as an array - * - * @param int $columnIndex Index of the column to fetch + * Fetch the first column of all rows of the result set as an array * * @return array */ - public function fetchColumn($columnIndex = 0) + public function fetchColumn() { - return $this->ds->fetchColumn($this, $columnIndex); + Benchmark::measure('Fetching one column started'); + $values = $this->ds->fetchColumn($this); + Benchmark::measure('Fetching one column finished'); + + if ($this->peekAhead && $this->hasLimit() && count($values) === $this->getLimit()) { + $this->hasMore = true; + array_pop($values); + } else { + $this->hasMore = false; + } + + return $values; } /** @@ -372,7 +596,10 @@ class SimpleQuery implements QueryInterface, Queryable */ public function fetchOne() { - return $this->ds->fetchOne($this); + Benchmark::measure('Fetching one value started'); + $value = $this->ds->fetchOne($this); + Benchmark::measure('Fetching one value finished'); + return $value; } /** @@ -384,17 +611,33 @@ class SimpleQuery implements QueryInterface, Queryable */ public function fetchPairs() { - return $this->ds->fetchPairs($this); + Benchmark::measure('Fetching pairs started'); + $pairs = $this->ds->fetchPairs($this); + Benchmark::measure('Fetching pairs finished'); + + if ($this->peekAhead && $this->hasLimit() && count($pairs) === $this->getLimit()) { + $this->hasMore = true; + array_pop($pairs); + } else { + $this->hasMore = false; + } + + return $pairs; } /** - * Count all rows of the result set + * Count all rows of the result set, ignoring limit and offset * - * @return int + * @return int */ public function count() { - return $this->ds->count($this); + $query = clone $this; + $query->limit(0, 0); + Benchmark::measure('Counting all results started'); + $count = $this->ds->count($query); + Benchmark::measure('Counting all results finished'); + return $count; } /** @@ -402,11 +645,12 @@ class SimpleQuery implements QueryInterface, Queryable * * @param array $columns * - * @return self + * @return $this */ public function columns(array $columns) { $this->columns = $columns; + $this->flippedColumns = null; // Reset, due to updated columns return $this; } @@ -414,4 +658,12 @@ class SimpleQuery implements QueryInterface, Queryable { return $this->columns; } + + /** + * Deep clone self::$filter + */ + public function __clone() + { + $this->filter = clone $this->filter; + } } diff --git a/library/Icinga/Data/SortRules.php b/library/Icinga/Data/SortRules.php new file mode 100644 index 000000000..cba77fe0b --- /dev/null +++ b/library/Icinga/Data/SortRules.php @@ -0,0 +1,14 @@ +value = $value; - } - - /** - * Get the node's value - * - * @return mixed - */ - public function getValue() - { - return $this->value; - } - - /** - * Create a new node from the given value and insert the node as the last child of this node - * - * @param mixed $value The node's value - * - * @return NodeInterface The appended node - */ - public function appendChild($value) - { - $child = new static($value); - $this->push($child); - return $child; - } - - /** - * Whether this node has child nodes - * - * @return bool - */ - public function hasChildren() - { - $current = $this->current(); - if ($current === null) { - $current = $this; - } - return ! $current->isEmpty(); - } - - /** - * Get the node's child nodes - * - * @return NodeInterface - */ - public function getChildren() - { - $current = $this->current(); - if ($current === null) { - $current = $this; - } - return $current; - } -} diff --git a/library/Icinga/Data/Tree/NodeInterface.php b/library/Icinga/Data/Tree/NodeInterface.php deleted file mode 100644 index 6953214dc..000000000 --- a/library/Icinga/Data/Tree/NodeInterface.php +++ /dev/null @@ -1,26 +0,0 @@ -sentinel = new TreeNode(); + } + + /** + * Add a child node + * + * @param TreeNode $child + * @param TreeNode $parent + * + * @return $this + */ + public function addChild(TreeNode $child, TreeNode $parent = null) + { + if ($parent === null) { + $parent = $this->sentinel; + } elseif (! isset($this->nodes[$parent->getId()])) { + throw new LogicException(sprintf( + 'Can\'t append child node %s to parent node %s: Parent node does not exist', + $child->getId(), + $parent->getId() + )); + } + if (isset($this->nodes[$child->getId()])) { + throw new LogicException(sprintf( + 'Can\'t append child node %s to parent node %s: Child node does already exist', + $child->getId(), + $parent->getId() + )); + } + $this->nodes[$child->getId()] = $child; + $parent->appendChild($child); + return $this; + } + + /** + * Get a node by its ID + * + * @param mixed $id + * + * @return TreeNode|null + */ + public function getNode($id) + { + if (! isset($this->nodes[$id])) { + return null; + } + return $this->nodes[$id]; + } + + /** + * {@inheritdoc} + * @return TreeNodeIterator + */ + public function getIterator() + { + return new TreeNodeIterator($this->sentinel); + } +} diff --git a/library/Icinga/Data/Tree/TreeNode.php b/library/Icinga/Data/Tree/TreeNode.php new file mode 100644 index 000000000..975a33e76 --- /dev/null +++ b/library/Icinga/Data/Tree/TreeNode.php @@ -0,0 +1,109 @@ +id = $id; + return $this; + } + + /** + * (non-PHPDoc) + * @see Identifiable::getId() For the method documentation. + */ + public function getId() + { + return $this->id; + } + + /** + * Set the node's value + * + * @param mixed $value + * + * @return $this + */ + public function setValue($value) + { + $this->value = $value; + return $this; + } + + /** + * Get the node's value + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Append a child node as the last child of this node + * + * @param TreeNode $child The child to append + * + * @return $this + */ + public function appendChild(TreeNode $child) + { + $this->children[] = $child; + return $this; + } + + + /** + * Get whether the node has children + * + * @return bool + */ + public function hasChildren() + { + return ! empty($this->children); + } + + /** + * Get the node's children + * + * @return array + */ + public function getChildren() + { + return $this->children; + } +} diff --git a/library/Icinga/Data/Tree/TreeNodeIterator.php b/library/Icinga/Data/Tree/TreeNodeIterator.php new file mode 100644 index 000000000..76590398b --- /dev/null +++ b/library/Icinga/Data/Tree/TreeNodeIterator.php @@ -0,0 +1,87 @@ +children = new ArrayIterator($node->getChildren()); + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->children->current(); + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->children->key(); + } + + /** + * {@inheritdoc} + */ + public function next() + { + $this->children->next(); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->children->rewind(); + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->children->valid(); + } + + /** + * {@inheritdoc} + */ + public function hasChildren() + { + return $this->current()->hasChildren(); + } + + /** + * {@inheritdoc} + * @return TreeNodeIterator + */ + public function getChildren() + { + return new static($this->current()); + } +} diff --git a/library/Icinga/Data/Updatable.php b/library/Icinga/Data/Updatable.php new file mode 100644 index 000000000..ec07537cb --- /dev/null +++ b/library/Icinga/Data/Updatable.php @@ -0,0 +1,24 @@ + 3600 * 24 * 3) { + $type = static::DATE; + if (date('Y') === date('Y', $time)) { + $formatted = date('M j', $time); + } else { + $formatted = date('Y-m', $time); + } + } else { + $minutes = floor($diff / 60); + if ($minutes < 60) { + $type = static::RELATIVE; + $formatted = sprintf('%dm %ds', $minutes, $diff % 60); + } else { + $hours = floor($minutes / 60); + if ($hours < 24) { + if (date('d') === date('d', $time)) { + $type = static::TIME; + $formatted = date('H:i', $time); + } else { + $type = static::DATE; + $formatted = date('M j H:i', $time); + } + } else { + $type = static::RELATIVE; + $formatted = sprintf('%dd %dh', floor($hours / 24), $hours % 24); + } + } + } + return array($type, $formatted, $invert); + } + + /** + * Format date + * + * @param int|float $date + * + * @return string + */ + public static function formatDate($date) + { + return date('Y-m-d', (float) $date); + } + + /** + * Format date and time + * + * @param int|float $dateTime + * + * @return string + */ + public static function formatDateTime($dateTime) + { + return date('Y-m-d H:i:s', (float) $dateTime); + } + + /** + * Format a duration + * + * @param int|float $seconds Duration in seconds + * + * @return string + */ + public static function formatDuration($seconds) + { + $minutes = floor((float) $seconds / 60); + if ($minutes < 60) { + $formatted = sprintf('%dm %ds', $minutes, $seconds % 60); + } else { + $hours = floor($minutes / 60); + if ($hours < 24) { + $formatted = sprintf('%dh %dm', $hours, $minutes % 60); + } else { + $formatted = sprintf('%dd %dh', floor($hours / 24), $hours % 24); + } + } + return $formatted; + } + + /** + * Format time + * + * @param int|float $time + * + * @return string + */ + public static function formatTime($time) + { + return date('H:i:s', (float) $time); + } + + /** + * Format time as time ago + * + * @param int|float $time + * @param bool $timeOnly + * + * @return string + */ + public static function timeAgo($time, $timeOnly = false) + { + list($type, $ago, $invert) = static::diff($time); + if ($timeOnly) { + return $ago; + } + switch ($type) { + case static::DATE: + // Move to next case + case static::DATETIME: + $formatted = sprintf( + t('on %s', 'An event happened on the given date or date and time'), + $ago + ); + break; + case static::RELATIVE: + $formatted = sprintf( + t('%s ago', 'An event that happened the given time interval ago'), + $ago + ); + break; + case static::TIME: + $formatted = sprintf(t('at %s', 'An event happened at the given time'), $ago); + break; + } + return $formatted; + } + + /** + * Format time as time since + * + * @param int|float $time + * @param bool $timeOnly + * + * @return string + */ + public static function timeSince($time, $timeOnly = false) + { + list($type, $since, $invert) = static::diff($time); + if ($timeOnly) { + return $since; + } + switch ($type) { + case static::RELATIVE: + $formatted = sprintf( + t('for %s', 'A status is lasting for the given time interval'), + $since + ); + break; + case static::DATE: + // Move to next case + case static::DATETIME: + // Move to next case + case static::TIME: + $formatted = sprintf( + t('since %s', 'A status is lasting since the given time, date or date and time'), + $since + ); + break; + } + return $formatted; + } + + /** + * Format time as time until + * + * @param int|float $time + * @param bool $timeOnly + * + * @return string + */ + public static function timeUntil($time, $timeOnly = false) + { + list($type, $until, $invert) = static::diff($time); + if ($invert && $type === static::RELATIVE) { + $until = '-' . $until; + } + if ($timeOnly) { + return $until; + } + switch ($type) { + case static::DATE: + // Move to next case + case static::DATETIME: + $formatted = sprintf( + t('on %s', 'An event will happen on the given date or date and time'), + $until + ); + break; + case static::RELATIVE: + $formatted = sprintf( + t('in %s', 'An event will happen after the given time interval has elapsed'), + $until + ); + break; + case static::TIME: + $formatted = sprintf(t('at %s', 'An event will happen at the given time'), $until); + break; + } + return $formatted; + } +} diff --git a/library/Icinga/Exception/AuthenticationException.php b/library/Icinga/Exception/AuthenticationException.php index ebed296e4..5cce7a113 100644 --- a/library/Icinga/Exception/AuthenticationException.php +++ b/library/Icinga/Exception/AuthenticationException.php @@ -1,6 +1,5 @@ allowedMethods; + } + + /** + * Set the allowed HTTP methods + * + * @param string $allowedMethods + * + * @return $this + */ + public function setAllowedMethods($allowedMethods) + { + $this->allowedMethods = (string) $allowedMethods; + return $this; + } +} diff --git a/library/Icinga/Exception/Http/HttpNotFoundException.php b/library/Icinga/Exception/Http/HttpNotFoundException.php new file mode 100644 index 000000000..8ec6b7fcb --- /dev/null +++ b/library/Icinga/Exception/Http/HttpNotFoundException.php @@ -0,0 +1,11 @@ +newInstanceArgs($args); + } + + /** + * Return the given exception formatted as one-liner + * + * The format used is: %class% in %path%:%line% with message: %message% + * + * @param Exception $exception + * + * @return string + */ + public static function describe(Exception $exception) + { + return sprintf( + '%s in %s:%d with message: %s', + get_class($exception), + $exception->getFile(), + $exception->getLine(), + $exception->getMessage() + ); + } } diff --git a/library/Icinga/Exception/InvalidPropertyException.php b/library/Icinga/Exception/InvalidPropertyException.php index f79055f0b..59179183e 100644 --- a/library/Icinga/Exception/InvalidPropertyException.php +++ b/library/Icinga/Exception/InvalidPropertyException.php @@ -1,6 +1,5 @@ parameter; + } + + /** + * Set the name of the missing parameter + * + * @param string $name + * + * @return $this + */ + public function setParameter($name) + { + $this->parameter = (string) $name; + return $this; + } } diff --git a/library/Icinga/Exception/NotFoundError.php b/library/Icinga/Exception/NotFoundError.php index c90b0322b..69e884c3f 100644 --- a/library/Icinga/Exception/NotFoundError.php +++ b/library/Icinga/Exception/NotFoundError.php @@ -1,6 +1,5 @@ query = $query; diff --git a/library/Icinga/File/FileExtensionFilterIterator.php b/library/Icinga/File/FileExtensionFilterIterator.php new file mode 100644 index 000000000..41efce0ad --- /dev/null +++ b/library/Icinga/File/FileExtensionFilterIterator.php @@ -0,0 +1,70 @@ + + * + */ +class FileExtensionFilterIterator extends FilterIterator +{ + /** + * The extension to filter for + * + * @var string + */ + protected $extension; + + /** + * Create a new FileExtensionFilterIterator + * + * @param Iterator $iterator Apply filter to this iterator + * @param string $extension The file extension to filter for. The file extension may not contain the leading dot + */ + public function __construct(Iterator $iterator, $extension) + { + $this->extension = '.' . ltrim(strtolower((string) $extension), '.'); + parent::__construct($iterator); + } + + /** + * Accept files which match the file extension to filter for + * + * @return bool Whether the current element of the iterator is acceptable + * through this filter + */ + public function accept() + { + $current = $this->current(); + /** @var $current \SplFileInfo */ + if (! $current->isFile()) { + return false; + } + // SplFileInfo::getExtension() is only available since PHP 5 >= 5.3.6 + $filename = $current->getFilename(); + $sfx = substr($filename, -strlen($this->extension)); + return $sfx === false ? false : strtolower($sfx) === $this->extension; + } +} diff --git a/library/Icinga/File/Ini/Dom/Comment.php b/library/Icinga/File/Ini/Dom/Comment.php new file mode 100644 index 000000000..a1f6c04d2 --- /dev/null +++ b/library/Icinga/File/Ini/Dom/Comment.php @@ -0,0 +1,37 @@ +content = $content; + } + + /** + * Render this comment into INI markup + * + * @return string + */ + public function render() + { + return ';' . $this->content; + } +} diff --git a/library/Icinga/File/Ini/Dom/Directive.php b/library/Icinga/File/Ini/Dom/Directive.php new file mode 100644 index 000000000..0014d25bb --- /dev/null +++ b/library/Icinga/File/Ini/Dom/Directive.php @@ -0,0 +1,167 @@ +key = trim($key); + if (strlen($this->key) < 1) { + throw new ConfigurationError(sprintf('Ini error: empty directive key.')); + } + } + + /** + * Return the name of this directive + * + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * Return the value of this configuration directive + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the value of this configuration directive + * + * @param string $value + */ + public function setValue($value) + { + $this->value = trim($value); + } + + /** + * Set the comments to be rendered on the line before this directive + * + * @param Comment[] $comments + */ + public function setCommentsPre(array $comments) + { + $this->commentsPre = $comments; + } + + /** + * Return the comments to be rendered on the line before this directive + * + * @return Comment[] + */ + public function getCommentsPre() + { + return $this->commentsPre; + } + + /** + * Set the comment rendered on the same line of this directive + * + * @param Comment $comment + */ + public function setCommentPost(Comment $comment) + { + $this->commentPost = $comment; + } + + /** + * Render this configuration directive into INI markup + * + * @return string + */ + public function render() + { + $str = ''; + if (! empty ($this->commentsPre)) { + $comments = array(); + foreach ($this->commentsPre as $comment) { + $comments[] = $comment->render(); + } + $str = implode(PHP_EOL, $comments) . PHP_EOL; + } + $str .= sprintf('%s = "%s"', $this->sanitizeKey($this->key), $this->sanitizeValue($this->value)); + if (isset ($this->commentPost)) { + $str .= ' ' . $this->commentPost->render(); + } + return $str; + } + + /** + * Assure that the given identifier contains no newlines and pending or trailing whitespaces + * + * @param $str The string to sanitize + * + * @return string + */ + protected function sanitizeKey($str) + { + return trim(str_replace(PHP_EOL, ' ', $str)); + } + + /** + * Escape the significant characters in directive values, normalize line breaks and assure that + * the character contains no linebreaks + * + * @param $str The string to sanitize + * + * @return mixed|string + */ + protected function sanitizeValue($str) + { + $str = trim($str); + $str = str_replace('\\', '\\\\', $str); + $str = str_replace('"', '\\"', $str); + + // line breaks in the value should always match the current system EOL sequence + // to assure editable configuration files + $str = preg_replace("/(\r\n)|(\n)/", PHP_EOL, $str); + return $str; + } +} diff --git a/library/Icinga/File/Ini/Dom/Document.php b/library/Icinga/File/Ini/Dom/Document.php new file mode 100644 index 000000000..69b628916 --- /dev/null +++ b/library/Icinga/File/Ini/Dom/Document.php @@ -0,0 +1,118 @@ +sections[$section->getName()] = $section; + } + + /** + * Return whether this INI file has the section with the given key + * + * @param string $name + * + * @return bool + */ + public function hasSection($name) + { + return isset($this->sections[trim($name)]); + } + + /** + * Return the section with the given name + * + * @param string $name + * + * @return Section + */ + public function getSection($name) + { + return $this->sections[trim($name)]; + } + + /** + * Set the section with the given name + * + * @param string $name + * @param Section $section + * + * @return Section + */ + public function setSection($name, Section $section) + { + return $this->sections[trim($name)] = $section; + } + + /** + * Remove the section with the given name + * + * @param string $name + */ + public function removeSection($name) + { + unset ($this->sections[trim($name)]); + } + + /** + * Set the dangling comments at file end that belong to no particular directive + * + * @param Comment[] $comments + */ + public function setCommentsDangling(array $comments) + { + $this->commentsDangling = $comments; + } + + /** + * Get the dangling comments at file end that belong to no particular directive + * + * @return array + */ + public function getCommentsDangling() + { + return $this->commentsDangling; + } + + /** + * Render this document into the corresponding INI markup + * + * @return string + */ + public function render() + { + $sections = array(); + foreach ($this->sections as $section) { + $sections []= $section->render(); + } + $str = implode(PHP_EOL, $sections); + if (! empty($this->commentsDangling)) { + foreach ($this->commentsDangling as $comment) { + $str .= PHP_EOL . $comment->render(); + } + } + return $str; + } +} diff --git a/library/Icinga/File/Ini/Dom/Section.php b/library/Icinga/File/Ini/Dom/Section.php new file mode 100644 index 000000000..dbc9188cb --- /dev/null +++ b/library/Icinga/File/Ini/Dom/Section.php @@ -0,0 +1,172 @@ +name = trim($name); + if (strlen($this->name) < 1) { + throw new ConfigurationError(sprintf('Ini file error: empty section identifier')); + } + } + + /** + * Append a directive to the end of this section + * + * @param Directive $directive The directive to append + */ + public function addDirective(Directive $directive) + { + $this->directives[$directive->getKey()] = $directive; + } + + /** + * Remove the directive with the given name + * + * @param string $key They name of the directive to remove + */ + public function removeDirective($key) + { + unset ($this->directives[$key]); + } + + /** + * Return whether this section has a directive with the given key + * + * @param string $key The name of the directive + * + * @return bool + */ + public function hasDirective($key) + { + return isset($this->directives[$key]); + } + + /** + * Get the directive with the given key + * + * @param $key string + * + * @return Directive + */ + public function getDirective($key) + { + return $this->directives[$key]; + } + + /** + * Return the name of this section + * + * @return string The name + */ + public function getName() + { + return $this->name; + } + + /** + * Set the comments to be rendered on the line before this section + * + * @param Comment[] $comments + */ + public function setCommentsPre(array $comments) + { + $this->commentsPre = $comments; + } + + /** + * Set the comment rendered on the same line of this section + * + * @param Comment $comment + */ + public function setCommentPost(Comment $comment) + { + $this->commentPost = $comment; + } + + /** + * Render this section into INI markup + * + * @return string + */ + public function render() + { + $dirs = ''; + $i = 0; + foreach ($this->directives as $directive) { + $comments = $directive->getCommentsPre(); + $dirs .= (($i++ > 0 && ! empty($comments)) ? PHP_EOL : '') + . $directive->render() . PHP_EOL; + } + $cms = ''; + if (! empty($this->commentsPre)) { + foreach ($this->commentsPre as $comment) { + $comments[] = $comment->render(); + } + $cms = implode(PHP_EOL, $comments) . PHP_EOL; + } + $post = ''; + if (isset($this->commentPost)) { + $post = ' ' . $this->commentPost->render(); + } + return $cms . sprintf('[%s]', $this->sanitize($this->name)) . $post . PHP_EOL . $dirs; + } + + /** + * Escape the significant characters in sections and normalize line breaks + * + * @param $str The string to sanitize + * + * @return mixed + */ + protected function sanitize($str) + { + $str = trim($str); + $str = str_replace('\\', '\\\\', $str); + $str = str_replace('"', '\\"', $str); + $str = str_replace(']', '\\]', $str); + $str = str_replace(';', '\\;', $str); + return str_replace(PHP_EOL, ' ', $str); + } +} diff --git a/library/Icinga/File/Ini/IniEditor.php b/library/Icinga/File/Ini/IniEditor.php deleted file mode 100644 index 85d71b697..000000000 --- a/library/Icinga/File/Ini/IniEditor.php +++ /dev/null @@ -1,627 +0,0 @@ -text = explode(PHP_EOL, $content); - $this->valueIndentation = array_key_exists('valueIndentation', $options) - ? $options['valueIndentation'] : 19; - $this->commentIndentation = array_key_exists('commentIndentation', $options) - ? $options['commentIndentation'] : 43; - $this->sectionSeparators = array_key_exists('sectionSeparators', $options) - ? $options['sectionSeparators'] : 2; - } - - /** - * Set the value of the given key. - * - * @param array $key The key to set - * @param string $value The value to set - * @param array $section The section to insert to. - */ - public function set(array $key, $value, $section = null) - { - $line = $this->getKeyLine($key, $section); - if ($line === -1) { - $this->insert($key, $value, $section); - } else { - $content = $this->formatKeyValuePair($key, $value); - $this->updateLine($line, $content); - } - } - - /** - * Reset the value of the given array element - * - * @param array $key The key of the array value - * @param array $section The section of the array. - */ - public function resetArrayElement(array $key, $section = null) - { - $line = $this->getArrayElement($key, $section); - if ($line !== -1) { - $this->deleteLine($line); - } - } - - /** - * Set the value for an array element - * - * @param array $key The key of the property - * @param string $value The value of the property - * @param array $section The section to use - */ - public function setArrayElement(array $key, $value, $section = null) - { - $line = $this->getArrayElement($key, $section); - if ($line !== -1) { - if (isset($section)) { - $this->updateLine($line, $this->formatKeyValuePair($key, $value)); - } else { - /* - * Move into new section to avoid ambiguous configurations - */ - $section = $key[0]; - unset($key[0]); - $this->deleteLine($line); - $this->setSection($section); - $this->insert($key, $value, $section); - } - } else { - $this->insert($key, $value, $section); - } - } - - /** - * Get the line of an array element - * - * @param array $key The key of the property. - * @param mixed $section The section to use - * - * @return int The line of the array element. - */ - private function getArrayElement(array $key, $section = null) - { - $line = isset($section) ? $this->getSectionLine($section) + 1 : 0; - $index = array_pop($key); - $formatted = $this->formatKey($key); - for (; $line < count($this->text); $line++) { - $l = $this->text[$line]; - if ($this->isSectionDeclaration($l)) { - return -1; - } - if (preg_match('/^\s*' . $formatted . '\[\]\s*=/', $l) === 1) { - return $line; - } - if ($this->isPropertyDeclaration($l, array_merge($key, array($index)))) { - return $line; - } - } - return -1; - } - - /** - * When it exists, set the key back to null - * - * @param array $key The key to reset - * @param array $section The section of the key - */ - public function reset(array $key, $section = null) - { - $line = $this->getKeyLine($key, $section); - if ($line === -1) { - return; - } - $this->deleteLine($line); - } - - /** - * Create the section if it does not exist and set the properties - * - * @param string $section The section name - * @param array $extend The section that should be extended by this section - */ - public function setSection($section, $extend = null) - { - if (isset($extend)) { - $decl = '[' . $section . ' : ' . $extend . ']'; - } else { - $decl = '[' . $section . ']'; - } - $line = $this->getSectionLine($section); - if ($line !== -1) { - $this->deleteLine($line); - $this->insertAtLine($line, $decl); - } else { - $line = $this->getLastLine(); - $this->insertAtLine($line, $decl); - } - } - - /** - * Refresh the section order of the ini file - * - * @param array $order An array containing the section names in the new order - * Example: array(0 => 'FirstSection', 1 => 'SecondSection') - */ - public function refreshSectionOrder(array $order) - { - $sections = $this->createSectionMap($this->text); - /* - * Move section-less properties to the start of the ordered text - */ - $orderedText = array(); - foreach ($sections['[section-less]'] as $line) { - array_push($orderedText, $line); - } - /* - * Reorder the sections - */ - $len = count($order); - for ($i = 0; $i < $len; $i++) { - if (array_key_exists($i, $order)) { - /* - * Append the lines of the section to the end of the - * ordered text - */ - foreach ($sections[$order[$i]] as $line) { - array_push($orderedText, $line); - } - } - } - $this->text = $orderedText; - } - - /** - * Create a map of sections to lines of a given ini file - * - * @param array $text The text split up in lines - * - * @return array $sectionMap A map containing all sections as arrays of lines. The array of section-less - * lines will be available using they key '[section-less]' which is no valid - * section declaration because it contains brackets. - */ - private function createSectionMap($text) - { - $sections = array('[section-less]' => array()); - $section = '[section-less]'; - $len = count($text); - for ($i = 0; $i < $len; $i++) { - if ($this->isSectionDeclaration($text[$i])) { - $newSection = $this->getSectionFromDeclaration($this->text[$i]); - $sections[$newSection] = array(); - - /* - * Remove comments 'glued' to the new section from the old - * section array and put them into the new section. - */ - $j = $i - 1; - $comments = array(); - while ($j >= 0 && $this->isComment($this->text[$j])) { - array_push($comments, array_pop($sections[$section])); - $j--; - } - $comments = array_reverse($comments); - foreach ($comments as $comment) { - array_push($sections[$newSection], $comment); - } - - $section = $newSection; - } - array_push($sections[$section], $this->text[$i]); - } - return $sections; - } - - /** - * Extract the section name from a section declaration - * - * @param String $declaration The section declaration - * - * @return string The section name - */ - private function getSectionFromDeclaration($declaration) - { - $tmp = preg_split('/(\[|\]|:)/', $declaration); - return trim($tmp[1]); - } - - /** - * Remove a section declaration - * - * @param string $section The section name - */ - public function removeSection($section) - { - $line = $this->getSectionLine($section); - if ($line !== -1) { - $this->deleteLine($line); - } - } - - /** - * Insert the key at the end of the corresponding section - * - * @param array $key The key to insert - * @param mixed $value The value to insert - * @param array $section The key to insert - */ - private function insert(array $key, $value, $section = null) - { - $line = $this->getSectionEnd($section); - $content = $this->formatKeyValuePair($key, $value); - $this->insertAtLine($line, $content); - } - - /** - * Get the edited text - * - * @return string The edited text - */ - public function getText() - { - $this->cleanUpWhitespaces(); - return implode(PHP_EOL, $this->text); - } - - /** - * Remove all unneeded line breaks between sections - */ - private function cleanUpWhitespaces() - { - $i = count($this->text) - 1; - for (; $i > 0; $i--) { - $line = $this->text[$i]; - if ($this->isSectionDeclaration($line) && $i > 0) { - $i--; - $line = $this->text[$i]; - /* - * Ignore comments that are glued to the section declaration - */ - while ($i > 0 && $this->isComment($line)) { - $i--; - $line = $this->text[$i]; - } - /* - * Remove whitespaces between the sections - */ - while ($i > 0 && preg_match('/^\s*$/', $line) === 1) { - $this->deleteLine($i); - $i--; - $line = $this->text[$i]; - } - /* - * Refresh section separators - */ - if ($i !== 0 && $this->sectionSeparators > 0) { - $this->insertAtLine($i + 1, str_repeat(PHP_EOL, $this->sectionSeparators - 1)); - } - } - } - } - - /** - * Insert the text at line $lineNr - * - * @param $lineNr The line nr the inserted line should have - * @param $toInsert The text that will be inserted - */ - private function insertAtLine($lineNr, $toInsert) - { - $this->text = IniEditor::insertIntoArray($this->text, $lineNr, $toInsert); - } - - /** - * Update the line $lineNr - * - * @param int $lineNr The line number of the target line - * @param string $content The new line content - */ - private function updateLine($lineNr, $content) - { - $comment = $this->getComment($this->text[$lineNr]); - $comment = trim($comment); - if (strlen($comment) > 0) { - $comment = ' ; ' . $comment; - $content = str_pad($content, $this->commentIndentation) . $comment; - } - $this->text[$lineNr] = $content; - } - - /** - * Get the comment from the given line - * - * @param $lineContent The content of the line - * - * @return string The extracted comment - */ - private function getComment($lineContent) - { - /* - * Remove all content in double quotes that is not behind a semicolon, recognizing - * escaped double quotes inside the string - */ - $cleaned = preg_replace('/^[^;"]*"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"/s', '', $lineContent); - - $matches = explode(';', $cleaned, 2); - return array_key_exists(1, $matches) ? $matches[1] : ''; - } - - /** - * Delete the line $lineNr - * - * @param $lineNr The lineNr starting at 0 - */ - private function deleteLine($lineNr) - { - $this->text = $this->removeFromArray($this->text, $lineNr); - } - - /** - * Format a key-value pair to an INI file-entry - * - * @param array $key The key to format - * @param string $value The value to format - * - * @return string The formatted key-value pair - */ - private function formatKeyValuePair(array $key, $value) - { - return str_pad($this->formatKey($key), $this->valueIndentation) . ' = ' . $this->formatValue($value); - } - - /** - * Format a key to an INI key - * - * @param array $key the key array to format - * - * @return string - */ - private function formatKey(array $key) - { - foreach ($key as $i => $separator) { - $key[$i] = $this->sanitize($separator); - } - return implode($this->nestSeparator, $key); - } - - /** - * Get the first line after the given $section - * - * @param $section The name of the section - * - * @return int The line number of the section - */ - private function getSectionEnd($section = null) - { - $i = 0; - $started = isset($section) ? false : true; - foreach ($this->text as $line) { - if ($started && $this->isSectionDeclaration($line)) { - if ($i === 0) { - return $i; - } - /* - * ignore all comments 'glued' to the next section, to allow section - * comments in front of sections - */ - while ($i > 0 && $this->isComment($this->text[$i - 1])) { - $i--; - } - return $i; - } elseif ($this->isSectionDeclaration($line, $section)) { - $started = true; - } - $i++; - } - if (!$started) { - return -1; - } - return $i; - } - - /** - * Check if the given line contains only a comment - */ - private function isComment($line) - { - return preg_match('/^\s*;/', $line) === 1; - } - - /** - * Check if the line contains the property declaration for a key - * - * @param string $lineContent The content of the line - * @param array $key The key this declaration is supposed to have - * - * @return boolean True, when the lineContent is a property declaration - */ - private function isPropertyDeclaration($lineContent, array $key) - { - return preg_match( - '/^\s*' . preg_quote($this->formatKey($key), '/') . '\s*=\s*/', - $lineContent - ) === 1; - } - - /** - * Check if the given line contains a section declaration - * - * @param $lineContent The content of the line - * @param string $section The optional section name that will be assumed - * - * @return bool True, when the lineContent is a section declaration - */ - private function isSectionDeclaration($lineContent, $section = null) - { - if (isset($section)) { - return preg_match('/^\s*\[\s*' . $section . '\s*[\]:]/', $lineContent) === 1; - } else { - return preg_match('/^\s*\[/', $lineContent) === 1; - } - } - - /** - * Get the line where the section begins - * - * @param $section The section - * - * @return int The line number - */ - private function getSectionLine($section) - { - $i = 0; - foreach ($this->text as $line) { - if ($this->isSectionDeclaration($line, $section)) { - return $i; - } - $i++; - } - return -1; - } - - /** - * Get the line number where the given key occurs - * - * @param array $keys The key and its parents - * @param $section The section of the key - * - * @return int The line number - */ - private function getKeyLine(array $keys, $section = null) - { - $key = implode($this->nestSeparator, $keys); - $inSection = isset($section) ? false : true; - $i = 0; - foreach ($this->text as $line) { - if ($inSection && $this->isSectionDeclaration($line)) { - return -1; - } - if ($inSection && $this->isPropertyDeclaration($line, $keys)) { - return $i; - } - if (!$inSection && $this->isSectionDeclaration($line, $section)) { - $inSection = true; - } - $i++; - } - return -1; - } - - /** - * Get the last line number occurring in the text - * - * @return The line number of the last line - */ - private function getLastLine() - { - return count($this->text); - } - - /** - * Insert a new element into a specific position of an array - * - * @param $array The array to use - * @param $pos The target position - * @param $element The element to insert - * - * @return array The changed array - */ - private static function insertIntoArray($array, $pos, $element) - { - array_splice($array, $pos, 0, $element); - return $array; - } - - /** - * Remove an element from an array - * - * @param $array The array to use - * @param $pos The position to remove - * - * @return array The altered array - */ - private function removeFromArray($array, $pos) - { - unset($array[$pos]); - return array_values($array); - } - - /** - * Prepare a value for INI - * - * @param $value The value of the string - * - * @return string The formatted value - * - * @throws Zend_Config_Exception - */ - private function formatValue($value) - { - if (is_integer($value) || is_float($value)) { - return $value; - } elseif (is_bool($value)) { - return ($value ? 'true' : 'false'); - } - return '"' . str_replace('"', '\"', $this->sanitize($value)) . '"'; - } - - private function sanitize($value) - { - return str_replace('\n', '', $value); - } -} diff --git a/library/Icinga/File/Ini/IniParser.php b/library/Icinga/File/Ini/IniParser.php new file mode 100644 index 000000000..43c96836d --- /dev/null +++ b/library/Icinga/File/Ini/IniParser.php @@ -0,0 +1,242 @@ +setCommentsPre($coms); + $doc->addSection($sec); + $dir = null; + $coms = array(); + + $state = self::LINE_END; + $token = ''; + } + break; + + case self::DIRECTIVE_KEY: + if ($s !== '=') { + $token .= $s; + } else { + $dir = new Directive($token); + $dir->setCommentsPre($coms); + if (isset($sec)) { + $sec->addDirective($dir); + } else { + Logger::warning(sprintf( + 'Ini parser warning: section-less directive "%s" ignored. (l. %d)', + $token, + $line + )); + } + + $coms = array(); + $state = self::DIRECTIVE_VALUE_START; + $token = ''; + } + break; + + case self::DIRECTIVE_VALUE_START: + if (ctype_space($s)) { + continue; + } elseif ($s === '"') { + $state = self::DIRECTIVE_VALUE_QUOTED; + } else { + $state = self::DIRECTIVE_VALUE; + $token = $s; + } + break; + + case self::DIRECTIVE_VALUE: + /* + Escaping non-quoted values is not supported by php_parse_ini, it might + be reasonable to include in case we are switching completely our own + parser implementation + */ + if ($s === "\n" || $s === ";") { + $dir->setValue($token); + $token = ''; + + if ($s === "\n") { + $state = self::LINE_START; + $line ++; + } elseif ($s === ';') { + $state = self::COMMENT; + } + } else { + $token .= $s; + } + break; + + case self::DIRECTIVE_VALUE_QUOTED: + if ($s === '\\') { + $state = self::ESCAPE; + $escaping = self::DIRECTIVE_VALUE_QUOTED; + } elseif ($s !== '"') { + $token .= $s; + } else { + $dir->setValue($token); + $token = ''; + $state = self::LINE_END; + } + break; + + case self::COMMENT: + case self::COMMENT_END: + if ($s !== "\n") { + $token .= $s; + } else { + $com = new Comment(); + $com->setContent($token); + $token = ''; + + // Comments at the line end belong to the current line's directive or section. Comments + // on empty lines belong to the next directive that shows up. + if ($state === self::COMMENT_END) { + if (isset($dir)) { + $dir->setCommentPost($com); + } else { + $sec->setCommentPost($com); + } + } else { + $coms[] = $com; + } + $state = self::LINE_START; + $line ++; + } + break; + + case self::LINE_END: + if ($s === "\n") { + $state = self::LINE_START; + $line ++; + } elseif ($s === ';') { + $state = self::COMMENT_END; + } + break; + } + } + + // process the last token + switch ($state) { + case self::COMMENT: + case self::COMMENT_END: + $com = new Comment(); + $com->setContent($token); + if ($state === self::COMMENT_END) { + if (isset($dir)) { + $dir->setCommentPost($com); + } else { + $sec->setCommentPost($com); + } + } else { + $coms[] = $com; + } + break; + + case self::DIRECTIVE_VALUE: + $dir->setValue($token); + $sec->addDirective($dir); + break; + + case self::ESCAPE: + case self::DIRECTIVE_VALUE_QUOTED: + case self::DIRECTIVE_KEY: + case self::SECTION: + self::throwParseError('File ended in unterminated state ' . $state, $line); + } + if (! empty($coms)) { + $doc->setCommentsDangling($coms); + } + return $doc; + } +} diff --git a/library/Icinga/File/Ini/IniWriter.php b/library/Icinga/File/Ini/IniWriter.php index 94a20faa5..8bf8241aa 100644 --- a/library/Icinga/File/Ini/IniWriter.php +++ b/library/Icinga/File/Ini/IniWriter.php @@ -1,19 +1,21 @@ toArray(), true); - } - + $this->config = $config; + $this->filename = $filename; + $this->fileMode = $filemode; $this->options = $options; - parent::__construct($options); } /** - * Render the Zend_Config into a config file string + * Render the Zend_Config into a config filestring * * @return string */ public function render() { - if (file_exists($this->_filename)) { - $oldconfig = new Zend_Config_Ini($this->_filename); - $content = file_get_contents($this->_filename); + if (file_exists($this->filename)) { + $oldconfig = Config::fromIni($this->filename); + $content = trim(file_get_contents($this->filename)); } else { - $oldconfig = new Zend_Config(array()); + $oldconfig = Config::fromArray(array()); $content = ''; } - - $newconfig = $this->_config; - $editor = new IniEditor($content, $this->options); - $this->diffConfigs($oldconfig, $newconfig, $editor); - $this->updateSectionOrder($newconfig, $editor); - return $editor->getText(); + $doc = IniParser::parseIni($content); + $this->diffPropertyUpdates($this->config, $doc); + $this->diffPropertyDeletions($oldconfig, $this->config, $doc); + $doc = $this->updateSectionOrder($this->config, $doc); + return $doc->render(); } /** * Write configuration to file and set file mode in case it does not exist yet * * @param string $filename - * @param Zend_Config $config * @param bool $exclusiveLock + * + * @throws Zend_Config_Exception */ - public function write($filename = null, Zend_Config $config = null, $exclusiveLock = null) + public function write($filename = null, $exclusiveLock = false) { - $filePath = $filename !== null ? $filename : $this->_filename; + $filePath = isset($filename) ? $filename : $this->filename; $setMode = false === file_exists($filePath); - parent::write($filename, $config, $exclusiveLock); + if (file_put_contents($filePath, $this->render(), $exclusiveLock ? LOCK_EX : 0) === false) { + throw new Zend_Config_Exception('Could not write to file "' . $filePath . '"'); + } if ($setMode) { - $mode = isset($this->options['filemode']) ? $this->options['filemode'] : static::$fileMode; - $old = umask(0); // Make sure that the mode we're going to set doesn't get mangled - if (is_int($mode) && false === @chmod($filePath, $mode)) { + // file was newly created + $mode = $this->fileMode; + if (is_int($this->fileMode) && false === @chmod($filePath, $this->fileMode)) { throw new Zend_Config_Exception(sprintf('Failed to set file mode "%o" on file "%s"', $mode, $filePath)); } - umask($old); } } - /** - * Create a property diff and apply the changes to the editor - * - * @param Zend_Config $oldconfig The config representing the state before the change - * @param Zend_Config $newconfig The config representing the state after the change - * @param IniEditor $editor The editor that should be used to edit the old config file - * @param array $parents The parent keys that should be respected when editing the config - */ - protected function diffConfigs( - Zend_Config $oldconfig, - Zend_Config $newconfig, - IniEditor $editor, - array $parents = array() - ) { - $this->diffPropertyUpdates($oldconfig, $newconfig, $editor, $parents); - $this->diffPropertyDeletions($oldconfig, $newconfig, $editor, $parents); - } - /** * Update the order of the sections in the ini file to match the order of the new config + * + * @return Document A new document with the changed section order applied */ - protected function updateSectionOrder(Zend_Config $newconfig, IniEditor $editor) + protected function updateSectionOrder(Config $newconfig, Document $oldDoc) { - $order = array(); - foreach ($newconfig as $key => $value) { - if ($value instanceof Zend_Config) { - array_push($order, $key); - } + $doc = new Document(); + $dangling = $oldDoc->getCommentsDangling(); + if (isset($dangling)) { + $doc->setCommentsDangling($dangling); } - $editor->refreshSectionOrder($order); + foreach ($newconfig->toArray() as $section => $directives) { + $doc->addSection($oldDoc->getSection($section)); + } + return $doc; } /** * Search for created and updated properties and use the editor to create or update these entries * - * @param Zend_Config $oldconfig The config representing the state before the change - * @param Zend_Config $newconfig The config representing the state after the change - * @param IniEditor $editor The editor that should be used to edit the old config file - * @param array $parents The parent keys that should be respected when editing the config + * @param Config $newconfig The config representing the state after the change + * @param Document $doc + * + * @throws ProgrammingError */ - protected function diffPropertyUpdates( - Zend_Config $oldconfig, - Zend_Config $newconfig, - IniEditor $editor, - array $parents = array() - ) { - // The current section. This value is null when processing the section-less root element - $section = empty($parents) ? null : $parents[0]; - // Iterate over all properties in the new configuration file and search for changes - foreach ($newconfig as $key => $value) { - $oldvalue = $oldconfig->get($key); - $nextParents = array_merge($parents, array($key)); - $keyIdentifier = empty($parents) ? array($key) : array_slice($nextParents, 1, null, true); - if ($value instanceof Zend_Config) { - // The value is a nested Zend_Config, handle it recursively - if ($section === null) { - // Update the section declaration - $extends = $newconfig->getExtends(); - $extend = array_key_exists($key, $extends) ? $extends[$key] : null; - $editor->setSection($key, $extend); - } - if ($oldvalue === null) { - $oldvalue = new Zend_Config(array()); - } - $this->diffConfigs($oldvalue, $value, $editor, $nextParents); + protected function diffPropertyUpdates(Config $newconfig, Document $doc) + { + foreach ($newconfig->toArray() as $section => $directives) { + if (! is_array($directives)) { + Logger::warning('Section-less property ' . (string)$directives . ' was ignored.'); + continue; + } + if (!$doc->hasSection($section)) { + $domSection = new Section($section); + $doc->addSection($domSection); } else { - // The value is a plain value, use the editor to set it - if (is_numeric($key)) { - $editor->setArrayElement($keyIdentifier, $value, $section); + $domSection = $doc->getSection($section); + } + foreach ($directives as $key => $value) { + if ($value instanceof ConfigObject) { + throw new ProgrammingError('Cannot diff recursive configs'); + } + if ($domSection->hasDirective($key)) { + $domSection->getDirective($key)->setValue($value); } else { - $editor->set($keyIdentifier, $value, $section); + $dir = new Directive($key); + $dir->setValue($value); + $domSection->addDirective($dir); } } } @@ -177,68 +166,36 @@ class IniWriter extends Zend_Config_Writer_FileAbstract /** * Search for deleted properties and use the editor to delete these entries * - * @param Zend_Config $oldconfig The config representing the state before the change - * @param Zend_Config $newconfig The config representing the state after the change - * @param IniEditor $editor The editor that should be used to edit the old config file - * @param array $parents The parent keys that should be respected when editing the config + * @param Config $oldconfig The config representing the state before the change + * @param Config $newconfig The config representing the state after the change + * @param Document $doc + * + * @throws ProgrammingError */ - protected function diffPropertyDeletions( - Zend_Config $oldconfig, - Zend_Config $newconfig, - IniEditor $editor, - array $parents = array() - ) { - // The current section. This value is null when processing the section-less root element - $section = empty($parents) ? null : $parents[0]; + protected function diffPropertyDeletions(Config $oldconfig, Config $newconfig, Document $doc) + { + // Iterate over all properties in the old configuration file and remove those that don't + // exist in the new config + foreach ($oldconfig->toArray() as $section => $directives) { + if (! is_array($directives)) { + Logger::warning('Section-less property ' . (string)$directives . ' was ignored.'); + continue; + } - // Iterate over all properties in the old configuration file and search for deleted properties - foreach ($oldconfig as $key => $value) { - if ($newconfig->get($key) === null) { - $nextParents = array_merge($parents, array($key)); - $keyIdentifier = empty($parents) ? array($key) : array_slice($nextParents, 1, null, true); - foreach ($this->getPropertyIdentifiers($value, $keyIdentifier) as $propertyIdentifier) { - $editor->reset($propertyIdentifier, $section); + if ($newconfig->hasSection($section)) { + $newSection = $newconfig->getSection($section); + $oldDomSection = $doc->getSection($section); + foreach ($directives as $key => $value) { + if ($value instanceof ConfigObject) { + throw new ProgrammingError('Cannot diff recursive configs'); + } + if (null === $newSection->get($key) && $oldDomSection->hasDirective($key)) { + $oldDomSection->removeDirective($key); + } } + } else { + $doc->removeSection($section); } } } - - /** - * Return all possible combinations of property identifiers for the given value - * - * @param mixed $value The value to return all combinations for - * @param array $key The root property identifier, if any - * - * @return array All property combinations that are possible - * - * @todo Cannot handle array properties yet (e.g. a.b[]='c') - */ - protected function getPropertyIdentifiers($value, array $key = null) - { - $combinations = array(); - $rootProperty = $key !== null ? $key : array(); - - if ($value instanceof Zend_Config) { - foreach ($value as $subProperty => $subValue) { - $combinations = array_merge( - $combinations, - $this->getPropertyIdentifiers($subValue, array_merge($rootProperty, array($subProperty))) - ); - } - } elseif (is_string($value)) { - $combinations[] = $rootProperty; - } - - return $combinations; - } - - /** - * Getter for filename - * - * @return string - */ - public function getFilename() - { - return $this->_filename; - } } diff --git a/library/Icinga/File/NonEmptyFileIterator.php b/library/Icinga/File/NonEmptyFileIterator.php new file mode 100644 index 000000000..03756ccd2 --- /dev/null +++ b/library/Icinga/File/NonEmptyFileIterator.php @@ -0,0 +1,48 @@ + + * + */ +class NonEmptyFileIterator extends FilterIterator +{ + /** + * Accept non-empty files + * + * @return bool Whether the current element of the iterator is acceptable + * through this filter + */ + public function accept() + { + $current = $this->current(); + /** @var $current \SplFileInfo */ + if (! $current->isFile() + || $current->getSize() === 0 + ) { + return false; + } + return true; + } +} diff --git a/library/Icinga/File/Pdf.php b/library/Icinga/File/Pdf.php index 44d1a107a..b71634f68 100644 --- a/library/Icinga/File/Pdf.php +++ b/library/Icinga/File/Pdf.php @@ -1,6 +1,5 @@ assertNoHeadersSent(); - ini_set('memory_limit', '384M'); ini_set('max_execution_time', 300); - - $request = $controller->getRequest(); + $viewRenderer = $controller->getHelper('viewRenderer'); + $controller->render( + $viewRenderer->getScriptAction(), + $viewRenderer->getResponseSegment(), + $viewRenderer->getNoController() + ); $layout = $controller->getHelper('layout')->setLayout('pdf'); - $controller->render(); $layout->content = $controller->getResponse(); $html = $layout->render(); $imgDir = Url::fromPath('img'); $html = preg_replace('~src="' . $imgDir . '/~', 'src="' . Icinga::app()->getBootstrapDirectory() . '/img/', $html); - $html = preg_replace('~src="/svg/chart.php(.*)"~', 'class="icon" src="http://master1.com/png/chart.php$1"', $html); $this->load_html($html); $this->render(); + $request = $controller->getRequest(); $this->stream( sprintf( '%s-%s-%d.pdf', diff --git a/library/Icinga/Protocol/Dns.php b/library/Icinga/Protocol/Dns.php index 950c22f44..70fe575e8 100644 --- a/library/Icinga/Protocol/Dns.php +++ b/library/Icinga/Protocol/Dns.php @@ -1,6 +1,5 @@ filename, $this->fields); + return new LogFileIterator($this->filename, $this->fields); } /** @@ -65,6 +72,18 @@ class FileReader implements Selectable, Countable return new FileQuery($this); } + /** + * Fetch and return all rows of the given query's result set using an iterator + * + * @param FileQuery $query + * + * @return ArrayIterator + */ + public function query(FileQuery $query) + { + return new ArrayIterator($this->fetchAll($query)); + } + /** * Return the number of available valid lines. * @@ -72,7 +91,10 @@ class FileReader implements Selectable, Countable */ public function count() { - return iterator_count($this->iterate()); + if ($this->count === null) { + $this->count = iterator_count($this->iterate()); + } + return $this->count; } /** diff --git a/library/Icinga/Protocol/File/LogFileIterator.php b/library/Icinga/Protocol/File/LogFileIterator.php new file mode 100644 index 000000000..0815a5376 --- /dev/null +++ b/library/Icinga/Protocol/File/LogFileIterator.php @@ -0,0 +1,155 @@ +file = new SplFileObject($filename); + $this->file->setFlags( + SplFileObject::DROP_NEW_LINE | + SplFileObject::READ_AHEAD + ); + $this->fields = $fields; + } + + public function rewind() + { + $this->file->rewind(); + $this->index = 0; + $this->nextMessage(); + } + + public function next() + { + $this->file->next(); + ++$this->index; + $this->nextMessage(); + } + + /** + * @return string + */ + public function current() + { + return $this->current; + } + + /** + * @return int + */ + public function key() + { + return $this->index; + } + + /** + * @return boolean + */ + public function valid() + { + return $this->valid; + } + + protected function nextMessage() + { + $message = $this->next === null ? array() : array($this->next); + $this->valid = null; + while ($this->file->valid()) { + if (false === ($res = preg_match( + $this->fields, $current = $this->file->current() + ))) { + throw new IcingaException('Failed at preg_match()'); + } + if (empty($message)) { + if ($res === 1) { + $message[] = $current; + } + } else if ($res === 1) { + $this->next = $current; + $this->valid = true; + break; + } else { + $message[] = $current; + } + + $this->file->next(); + } + if ($this->valid === null) { + $this->next = null; + $this->valid = ! empty($message); + } + + if ($this->valid) { + while (! empty($message)) { + $matches = array(); + if (false === ($res = preg_match( + $this->fields, implode(PHP_EOL, $message), $matches + ))) { + throw new IcingaException('Failed at preg_match()'); + } + if ($res === 1) { + $this->current = $matches; + return; + } + array_pop($message); + } + $this->valid = false; + } + } +} diff --git a/library/Icinga/Protocol/Ldap/Connection.php b/library/Icinga/Protocol/Ldap/Connection.php deleted file mode 100644 index 2f8737c8b..000000000 --- a/library/Icinga/Protocol/Ldap/Connection.php +++ /dev/null @@ -1,790 +0,0 @@ - - * $lconf = new Connection((object) array( - * 'hostname' => 'localhost', - * 'root_dn' => 'dc=monitoring,dc=...', - * 'bind_dn' => 'cn=Mangager,dc=monitoring,dc=...', - * 'bind_pw' => '***' - * )); - * - * - * @copyright Copyright (c) 2013 Icinga-Web Team - * @author Icinga-Web Team - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License - */ -class Connection -{ - const LDAP_NO_SUCH_OBJECT = 0x20; - - protected $ds; - protected $hostname; - protected $port = 389; - protected $bind_dn; - protected $bind_pw; - protected $root_dn; - protected $count; - - protected $ldap_extension = array( - '1.3.6.1.4.1.1466.20037' => 'STARTTLS', - // '1.3.6.1.4.1.4203.1.11.1' => '11.1', // PASSWORD_MODIFY - // '1.3.6.1.4.1.4203.1.11.3' => '11.3', // Whoami - // '1.3.6.1.1.8' => '8', // Cancel Extended Request - ); - - protected $ms_capability = array( - // Prefix LDAP_CAP_ - // Source: http://msdn.microsoft.com/en-us/library/cc223359.aspx - - // Running Active Directory as AD DS: - '1.2.840.113556.1.4.800' => 'ACTIVE_DIRECTORY_OID', - - // Capable of signing and sealing on an NTLM authenticated connection - // and of performing subsequent binds on a signed or sealed connection. - '1.2.840.113556.1.4.1791' => 'ACTIVE_DIRECTORY_LDAP_INTEG_OID', - - // If AD DS: running at least W2K3, if AD LDS running at least W2K8 - '1.2.840.113556.1.4.1670' => 'ACTIVE_DIRECTORY_V51_OID', - - // If AD LDS: accepts DIGEST-MD5 binds for AD LDSsecurity principals - '1.2.840.113556.1.4.1880' => 'ACTIVE_DIRECTORY_ADAM_DIGEST', - - // Running Active Directory as AD LDS - '1.2.840.113556.1.4.1851' => 'ACTIVE_DIRECTORY_ADAM_OID', - - // If AD DS: it's a Read Only DC (RODC) - '1.2.840.113556.1.4.1920' => 'ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID', - - // Running at least W2K8 - '1.2.840.113556.1.4.1935' => 'ACTIVE_DIRECTORY_V60_OID', - - // Running at least W2K8r2 - '1.2.840.113556.1.4.2080' => 'ACTIVE_DIRECTORY_V61_R2_OID', - - // Running at least W2K12 - '1.2.840.113556.1.4.2237' => 'ACTIVE_DIRECTORY_W8_OID', - - ); - - /** - * Whether the bind on this connection was already performed - * - * @var bool - */ - protected $bound = false; - - protected $root; - - protected $supports_v3 = false; - protected $supports_tls = false; - - protected $capabilities; - protected $namingContexts; - protected $discoverySuccess = false; - - /** - * Constructor - * - * TODO: Allow to pass port and SSL options - * - * @param ConfigObject $config - */ - public function __construct(ConfigObject $config) - { - $this->hostname = $config->hostname; - $this->bind_dn = $config->bind_dn; - $this->bind_pw = $config->bind_pw; - $this->root_dn = $config->root_dn; - $this->port = $config->get('port', $this->port); - } - - public function getHostname() - { - return $this->hostname; - } - - public function getPort() - { - return $this->port; - } - - public function getDN() - { - return $this->root_dn; - } - - public function root() - { - if ($this->root === null) { - $this->root = Root::forConnection($this); - } - return $this->root; - } - - public function select() - { - return new Query($this); - } - - public function fetchOne($query, $fields = array()) - { - $row = (array) $this->fetchRow($query, $fields); - return array_shift($row); - } - - public function hasDN($dn) - { - $this->connect(); - $this->bind(); - - $result = ldap_read($this->ds, $dn, '(objectClass=*)', array('objectClass')); - return ldap_count_entries($this->ds, $result) > 0; - } - - public function deleteRecursively($dn) - { - $this->connect(); - $this->bind(); - - $result = @ldap_list($this->ds, $dn, '(objectClass=*)', array('objectClass')); - if ($result === false) { - if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { - return false; - } - throw new LdapException( - sprintf( - 'LDAP list for "%s" failed: %s', - $dn, - ldap_error($this->ds) - ) - ); - } - $children = ldap_get_entries($this->ds, $result); - for ($i = 0; $i < $children['count']; $i++) { - $result = $this->deleteRecursively($children[$i]['dn']); - if (!$result) { - //return result code, if delete fails - throw new LdapException(sprintf('Recursively deleting "%s" failed', $dn)); - } - } - return $this->deleteDN($dn); - } - - public function deleteDN($dn) - { - $this->connect(); - $this->bind(); - - $result = @ldap_delete($this->ds, $dn); - if ($result === false) { - if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { - return false; - } - throw new LdapException( - sprintf( - 'LDAP delete for "%s" failed: %s', - $dn, - ldap_error($this->ds) - ) - ); - } - - return true; - } - - /** - * Fetch the distinguished name of the first result of the given query - * - * @param $query The query returning the result set - * @param array $fields The fields to fetch - * - * @return string Returns the distinguished name, or false when the given query yields no results - * @throws LdapException When the query result is empty and contains no DN to fetch - */ - public function fetchDN($query, $fields = array()) - { - $rows = $this->fetchAll($query, $fields); - if (count($rows) !== 1) { - throw new LdapException( - sprintf( - 'Cannot fetch single DN for %s', - $query - ) - ); - } - return key($rows); - } - - /** - * @param $query - * @param array $fields - * - * @return mixed - */ - public function fetchRow($query, $fields = array()) - { - // TODO: This is ugly, make it better! - $results = $this->fetchAll($query, $fields); - return array_shift($results); - } - - /** - * @param Query $query - * - * @return int - */ - public function count(Query $query) - { - $results = $this->runQuery($query, '+'); - if (! $results) { - return 0; - } - return ldap_count_entries($this->ds, $results); - } - - public function fetchAll($query, $fields = array()) - { - $offset = null; - $limit = null; - if ($query->hasLimit()) { - $offset = $query->getOffset(); - $limit = $query->getLimit(); - } - $entries = array(); - $results = $this->runQuery($query, $fields); - if (! $results) { - return array(); - } - $entry = ldap_first_entry($this->ds, $results); - $count = 0; - while ($entry) { - if (($offset === null || $offset <= $count) - && ($limit === null || ($offset + $limit) >= $count) - ) { - $attrs = ldap_get_attributes($this->ds, $entry); - $entries[ldap_get_dn($this->ds, $entry)] - = $this->cleanupAttributes($attrs); - } - $count++; - $entry = ldap_next_entry($this->ds, $entry); - } - ldap_free_result($results); - return $entries; - } - - public function cleanupAttributes(& $attrs) - { - $clean = (object) array(); - for ($i = 0; $i < $attrs['count']; $i++) { - $attr_name = $attrs[$i]; - if ($attrs[$attr_name]['count'] === 1) { - $clean->$attr_name = $attrs[$attr_name][0]; - } else { - for ($j = 0; $j < $attrs[$attr_name]['count']; $j++) { - $clean->{$attr_name}[] = $attrs[$attr_name][$j]; - } - } - } - return $clean; - } - - protected function runQuery($query, $fields) - { - $this->connect(); - $this->bind(); - if ($query instanceof Query) { - $fields = $query->listFields(); - } - // WARNING: - // We do not support pagination right now, and there is no chance to - // do so for PHP < 5.4. Warnings about "Sizelimit exceeded" will - // therefore not be hidden right now. - $base = $query->hasBase() ? $query->getBase() : $this->root_dn; - $results = @ldap_search( - $this->ds, - $base, - $query->create(), - $fields, - 0, // Attributes and values - 0 // No limit - at least where possible - ); - if ($results === false) { - if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { - return false; - } - throw new LdapException( - sprintf( - 'LDAP query "%s" (root %s) failed: %s', - $query, - $this->root_dn, - ldap_error($this->ds) - ) - ); - } - $list = array(); - if ($query instanceof Query) { - foreach ($query->getSortColumns() as $col) { - ldap_sort($this->ds, $results, $col[0]); - } - } - return $results; - } - - public function testCredentials($username, $password) - { - $this->connect(); - - $r = @ldap_bind($this->ds, $username, $password); - if ($r) { - Logger::debug( - 'Successfully tested LDAP credentials (%s / %s)', - $username, - '***' - ); - return true; - } else { - Logger::debug( - 'Testing LDAP credentials (%s / %s) failed: %s', - $username, - '***', - ldap_error($this->ds) - ); - return false; - } - } - - /** - * @param null $sub - * - * @return string - */ - protected function getConfigDir($sub = null) - { - $dir = Config::$configDir . '/ldap'; - if ($sub !== null) { - $dir .= '/' . $sub; - } - return $dir; - } - - /** - * Connect to the given ldap server and apply settings depending on the discovered capabilities - * - * @return resource A positive LDAP link identifier - * @throws LdapException When the connection is not possible - */ - protected function prepareNewConnection() - { - $use_tls = false; - $force_tls = true; - $force_tls = false; - - if ($use_tls) { - $this->prepareTlsEnvironment(); - } - - $ds = ldap_connect($this->hostname, $this->port); - try { - $capabilities = $this->discoverCapabilities($ds); - list($cap, $namingContexts) = $capabilities; - $this->discoverySuccess = true; - } catch (LdapException $e) { - - // discovery failed, guess defaults - $cap = (object) array( - 'supports_ldapv3' => true, - 'supports_starttls' => false, - 'msCapabilities' => array() - ); - $namingContexts = null; - } - $this->capabilities = $cap; - $this->namingContexts = $namingContexts; - - if ($use_tls) { - if ($cap->supports_starttls) { - if (@ldap_start_tls($ds)) { - Logger::debug('LDAP STARTTLS succeeded'); - } else { - Logger::debug('LDAP STARTTLS failed: %s', ldap_error($ds)); - throw new LdapException( - sprintf( - 'LDAP STARTTLS failed: %s', - ldap_error($ds) - ) - ); - } - } elseif ($force_tls) { - throw new LdapException( - sprintf( - 'TLS is required but not announced by %s', - $this->host_name - ) - ); - } else { - // TODO: Log noticy -> TLS enabled but not announced - } - } - // ldap_rename requires LDAPv3: - if ($cap->supports_ldapv3) { - if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) { - throw new LdapException('LDAPv3 is required'); - } - } else { - - // TODO: remove this -> FORCING v3 for now - ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); - Logger::warning('No LDAPv3 support detected'); - } - - // Not setting this results in "Operations error" on AD when using the - // whole domain as search base: - ldap_set_option($ds, LDAP_OPT_REFERRALS, 0); - // ldap_set_option($ds, LDAP_OPT_DEREF, LDAP_DEREF_NEVER); - return $ds; - } - - protected function prepareTlsEnvironment() - { - $strict_tls = true; - // TODO: allow variable known CA location (system VS Icinga) - if (Platform::isWindows()) { - // putenv('LDAP...') - } else { - if ($strict_tls) { - $ldap_conf = $this->getConfigDir('ldap_ca.conf'); - } else { - $ldap_conf = $this->getConfigDir('ldap_nocert.conf'); - } - putenv('LDAPRC=' . $ldap_conf); - if (getenv('LDAPRC') !== $ldap_conf) { - throw new LdapException('putenv failed'); - } - } - } - - /** - * Return if the capability object contains support for StartTLS - * - * @param $cap The object containing the capabilities - * - * @return bool Whether StartTLS is supported - */ - protected function hasCapabilityStartTLS($cap) - { - $cap = $this->getExtensionCapabilities($cap); - return isset($cap['1.3.6.1.4.1.1466.20037']); - } - - /** - * Return if the capability objects contains support for LdapV3 - * - * @param $cap - * - * @return bool - */ - protected function hasCapabilityLdapV3($cap) - { - if ((is_string($cap->supportedLDAPVersion) - && (int) $cap->supportedLDAPVersion === 3) - || (is_array($cap->supportedLDAPVersion) - && in_array(3, $cap->supportedLDAPVersion) - )) { - return true; - } - return false; - } - - /** - * Extract an array of all extension capabilities from the given ldap response - * - * @param $cap object The response returned by a ldap_search discovery query - * - * @return object The extracted capabilities. - */ - protected function getExtensionCapabilities($cap) - { - $extensions = array(); - if (isset($cap->supportedExtension)) { - foreach ($cap->supportedExtension as $oid) { - if (array_key_exists($oid, $this->ldap_extension)) { - if ($this->ldap_extension[$oid] === 'STARTTLS') { - $extensions['1.3.6.1.4.1.1466.20037'] = $this->ldap_extension['1.3.6.1.4.1.1466.20037']; - } - } - } - } - return $extensions; - } - - /** - * Extract an array of all MSAD capabilities from the given ldap response - * - * @param $cap object The response returned by a ldap_search discovery query - * - * @return object The extracted capabilities. - */ - protected function getMsCapabilities($cap) - { - $ms = array(); - foreach ($this->ms_capability as $name) { - $ms[$this->convName($name)] = false; - } - - if (isset($cap->supportedCapabilities)) { - foreach ($cap->supportedCapabilities as $oid) { - if (array_key_exists($oid, $this->ms_capability)) { - $ms[$this->convName($this->ms_capability[$oid])] = true; - } - } - } - return (object)$ms; - } - - /** - * Convert a single capability name entry into camel-case - * - * @param $name string The name to convert - * - * @return string The name in camel-case - */ - private function convName($name) - { - $parts = explode('_', $name); - foreach ($parts as $i => $part) { - $parts[$i] = ucfirst(strtolower($part)); - } - return implode('', $parts); - } - - /** - * Get the capabilities of this ldap server - * - * @return stdClass An object, providing the flags 'ldapv3' and 'starttls' to indicate LdapV3 and StartTLS - * support and an additional property 'msCapabilities', containing all supported active directory capabilities. - */ - public function getCapabilities() - { - return $this->capabilities; - } - - /** - * Get the default naming context of this ldap connection - * - * @return string|null the default naming context, or null when no contexts are available - */ - public function getDefaultNamingContext() - { - $cap = $this->capabilities; - if (isset($cap->defaultNamingContext)) { - return $cap->defaultNamingContext; - } - $namingContexts = $this->namingContexts($cap); - return empty($namingContexts) ? null : $namingContexts[0]; - } - - /** - * Fetch the namingContexts for this Ldap-Connection - * - * @return array the available naming contexts - */ - public function namingContexts() - { - if (!isset($this->namingContexts)) { - return array(); - } - if (!is_array($this->namingContexts)) { - return array($this->namingContexts); - } - return $this->namingContexts; - } - - /** - * Whether service discovery was successful - * - * @return boolean True when ldap settings were discovered, false when - * settings were guessed - */ - public function discoverySuccessful() - { - return $this->discoverySuccess; - } - - /** - * Discover the capabilities of the given ldap-server - * - * @param resource $ds The link identifier of the current ldap connection - * - * @return array The capabilities and naming-contexts - * @throws LdapException When the capability query fails - */ - protected function discoverCapabilities($ds) - { - $query = $this->select()->from( - '*', - array( - 'defaultNamingContext', - 'namingContexts', - 'vendorName', - 'vendorVersion', - 'supportedSaslMechanisms', - 'dnsHostName', - 'schemaNamingContext', - 'supportedLDAPVersion', // => array(3, 2) - 'supportedCapabilities', - 'supportedExtension', - '+' - ) - ); - $result = @ldap_read( - $ds, - '', - $query->create(), - $query->listFields() - ); - - if (! $result) { - throw new LdapException( - sprintf( - 'Capability query failed (%s:%d): %s. Check if hostname and port of the ldap resource are correct ' - . ' and if anonymous access is permitted.', - $this->hostname, - $this->port, - ldap_error($ds) - ) - ); - } - $entry = ldap_first_entry($ds, $result); - if ($entry === false) { - throw new LdapException( - sprintf( - 'Capabilities not available (%s:%d): %s. Discovery of root DSE probably not permitted.', - $this->hostname, - $this->port, - ldap_error($ds) - ) - ); - } - - $cap = (object) array( - 'supports_ldapv3' => false, - 'supports_starttls' => false, - 'msCapabilities' => array() - ); - - $ldapAttributes = ldap_get_attributes($ds, $entry); - $result = $this->cleanupAttributes($ldapAttributes); - $cap->supports_ldapv3 = $this->hasCapabilityLdapV3($result); - $cap->supports_starttls = $this->hasCapabilityStartTLS($result); - $cap->msCapabilities = $this->getMsCapabilities($result); - - return array($cap, $result->namingContexts); - } - - /** - * Try to connect to the given ldap server - * - * @throws LdapException When connecting is not possible - */ - public function connect() - { - if ($this->ds !== null) { - return; - } - $this->ds = $this->prepareNewConnection(); - } - - /** - * Try to bind to the current ldap domain using the provided bind_dn and bind_pw - * - * @throws LdapException When binding is not possible - */ - public function bind() - { - if ($this->bound) { - return; - } - - $r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw); - if (! $r) { - throw new LdapException( - sprintf( - 'LDAP connection to %s:%s (%s / %s) failed: %s', - $this->hostname, - $this->port, - $this->bind_dn, - '***' /* $this->bind_pw */, - ldap_error($this->ds) - ) - ); - } - $this->bound = true; - } - - /** - * Create an ldap entry - * - * @param string $dn DN to add - * @param array $entry Entry description - * - * @return bool True on success - */ - public function addEntry($dn, array $entry) - { - return ldap_add($this->ds, $dn, $entry); - } - - /** - * Modify a ldap entry - * - * @param string $dn DN of the entry to change - * @param array $entry Change values - * - * @return bool True on success - */ - public function modifyEntry($dn, array $entry) - { - return ldap_modify($this->ds, $dn, $entry); - } - - /** - * Move entry to a new DN - * - * @param string $dn DN of the object - * @param string $newRdn Relative DN identifier - * @param string $newParentDn Parent or superior entry - * @throws LdapException Thrown then rename failed - * - * @return bool True on success - */ - public function moveEntry($dn, $newRdn, $newParentDn) - { - $returnValue = ldap_rename($this->ds, $dn, $newRdn, $newParentDn, false); - - if ($returnValue === false) { - throw new LdapException('Could not move entry: ' . ldap_error($this->ds)); - } - - return $returnValue; - } - - public function __destruct() - { - putenv('LDAPRC'); - } -} diff --git a/library/Icinga/Protocol/Ldap/Discovery.php b/library/Icinga/Protocol/Ldap/Discovery.php index b876a5d6f..0d3873d96 100644 --- a/library/Icinga/Protocol/Ldap/Discovery.php +++ b/library/Icinga/Protocol/Ldap/Discovery.php @@ -1,6 +1,5 @@ connection = $conn; } - /** - * Execute the discovery on the underlying connection - */ - private function execDiscovery() - { - if (! $this->discovered) { - $this->connection->connect(); - $this->discovered = true; - } - } - /** * Suggests a resource configuration of hostname, port and root_dn * based on the discovery @@ -48,14 +29,10 @@ class Discovery { */ public function suggestResourceSettings() { - if (! $this->discovered) { - $this->execDiscovery(); - } - return array( 'hostname' => $this->connection->getHostname(), 'port' => $this->connection->getPort(), - 'root_dn' => $this->connection->getDefaultNamingContext() + 'root_dn' => $this->connection->getCapabilities()->getDefaultNamingContext() ); } @@ -67,17 +44,16 @@ class Discovery { */ public function suggestBackendSettings() { - $this->execDiscovery(); if ($this->isAd()) { return array( - 'base_dn' => $this->connection->getDefaultNamingContext(), + 'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(), 'user_class' => 'user', 'user_name_attribute' => 'sAMAccountName' ); } else { return array( - 'base_dn' => $this->connection->getDefaultNamingContext(), - 'user_class' => 'getDefaultNamingContext', + 'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(), + 'user_class' => 'inetOrgPerson', 'user_name_attribute' => 'uid' ); } @@ -90,9 +66,7 @@ class Discovery { */ public function isAd() { - $this->execDiscovery(); - $caps = $this->connection->getCapabilities(); - return isset($caps->msCapabilities->ActiveDirectoryOid) && $caps->msCapabilities->ActiveDirectoryOid; + return $this->connection->getCapabilities()->isActiveDirectory(); } /** @@ -102,7 +76,6 @@ class Discovery { */ public function isSuccess() { - $this->execDiscovery(); return $this->connection->discoverySuccessful(); } @@ -149,10 +122,10 @@ class Discovery { */ public static function discover($host, $port) { - $conn = new Connection(new ConfigObject(array( + $conn = new LdapConnection(new ConfigObject(array( 'hostname' => $host, 'port' => $port ))); return new Discovery($conn); } -} \ No newline at end of file +} diff --git a/library/Icinga/Protocol/Ldap/Exception.php b/library/Icinga/Protocol/Ldap/Exception.php deleted file mode 100644 index 41a5b782b..000000000 --- a/library/Icinga/Protocol/Ldap/Exception.php +++ /dev/null @@ -1,13 +0,0 @@ -value = $value; + } + + public function setValue($value) + { + $this->value = $value; + return $this; + } + + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->getValue(); + } +} diff --git a/library/Icinga/Protocol/Ldap/LdapCapabilities.php b/library/Icinga/Protocol/Ldap/LdapCapabilities.php new file mode 100644 index 000000000..1ee64eaba --- /dev/null +++ b/library/Icinga/Protocol/Ldap/LdapCapabilities.php @@ -0,0 +1,338 @@ +attributes = $attributes; + + $keys = array('supportedControl', 'supportedExtension', 'supportedFeatures', 'supportedCapabilities'); + foreach ($keys as $key) { + if (isset($attributes->$key)) { + if (is_array($attributes->$key)) { + foreach ($attributes->$key as $oid) { + $this->oids[$oid] = true; + } + } else { + $this->oids[$attributes->$key] = true; + } + } + } + } + + /** + * Return if the capability object contains support for StartTLS + * + * @return bool Whether StartTLS is supported + */ + public function hasStartTls() + { + return isset($this->oids[self::LDAP_SERVER_START_TLS_OID]); + } + + /** + * Return if the capability object contains support for paged results + * + * @return bool Whether StartTLS is supported + */ + public function hasPagedResult() + { + return isset($this->oids[self::LDAP_PAGED_RESULT_OID_STRING]); + } + + /** + * Whether the ldap server is an ActiveDirectory server + * + * @return boolean + */ + public function isActiveDirectory() + { + return isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_OID]); + } + + /** + * Whether the ldap server is an OpenLDAP server + * + * @return bool + */ + public function isOpenLdap() + { + return isset($this->attributes->structuralObjectClass) && + $this->attributes->structuralObjectClass === 'OpenLDAProotDSE'; + } + + /** + * Return if the capability objects contains support for LdapV3, defaults to true if discovery failed + * + * @return bool + */ + public function hasLdapV3() + { + if (! isset($this->attributes) || ! isset($this->attributes->supportedLDAPVersion)) { + // Default to true, if unknown + return true; + } + + return (is_string($this->attributes->supportedLDAPVersion) + && (int) $this->attributes->supportedLDAPVersion === 3) + || (is_array($this->attributes->supportedLDAPVersion) + && in_array(3, $this->attributes->supportedLDAPVersion)); + } + + /** + * Whether the capability with the given OID is supported + * + * @param $oid string The OID of the capability + * + * @return bool + */ + public function hasOid($oid) + { + return isset($this->oids[$oid]); + } + + /** + * Get the default naming context + * + * @return string|null the default naming context, or null when no contexts are available + */ + public function getDefaultNamingContext() + { + // defaultNamingContext entry has higher priority + if (isset($this->attributes->defaultNamingContext)) { + return $this->attributes->defaultNamingContext; + } + + // if its missing use namingContext + $namingContexts = $this->namingContexts(); + return empty($namingContexts) ? null : $namingContexts[0]; + } + + /** + * Fetch the namingContexts + * + * @return array the available naming contexts + */ + public function namingContexts() + { + if (!isset($this->attributes->namingContexts)) { + return array(); + } + if (!is_array($this->attributes->namingContexts)) { + return array($this->attributes->namingContexts); + } + return$this->attributes->namingContexts; + } + + public function getVendor() + { + /* + rfc #3045 specifies that the name of the server MAY be included in the attribute 'verndorName', + AD and OpenLDAP don't do this, but for all all other vendors we follow the standard and + just hope for the best. + */ + + if ($this->isActiveDirectory()) { + return 'Microsoft Active Directory'; + } + + if ($this->isOpenLdap()) { + return 'OpenLDAP'; + } + + if (! isset($this->attributes->vendorName)) { + return null; + } + return $this->attributes->vendorName; + } + + public function getVersion() + { + /* + rfc #3045 specifies that the version of the server MAY be included in the attribute 'vendorVersion', + but AD and OpenLDAP don't do this. For OpenLDAP there is no way to query the server versions, but for all + all other vendors we follow the standard and just hope for the best. + */ + + if ($this->isActiveDirectory()) { + return $this->getAdObjectVersionName(); + } + + if (! isset($this->attributes->vendorVersion)) { + return null; + } + return $this->attributes->vendorVersion; + } + + /** + * Discover the capabilities of the given LDAP server + * + * @param LdapConnection $connection The ldap connection to use + * + * @return LdapCapabilities + * + * @throws LdapException In case the capability query has failed + */ + public static function discoverCapabilities(LdapConnection $connection) + { + $ds = $connection->getConnection(); + + $fields = array( + 'defaultNamingContext', + 'namingContexts', + 'vendorName', + 'vendorVersion', + 'supportedSaslMechanisms', + 'dnsHostName', + 'schemaNamingContext', + 'supportedLDAPVersion', // => array(3, 2) + 'supportedCapabilities', + 'supportedControl', + 'supportedExtension', + 'objectVersion', + '+' + ); + + $result = @ldap_read($ds, '', (string) $connection->select()->from('*', $fields), $fields); + if (! $result) { + throw new LdapException( + 'Capability query failed (%s:%d): %s. Check if hostname and port of the' + . ' ldap resource are correct and if anonymous access is permitted.', + $connection->getHostname(), + $connection->getPort(), + ldap_error($ds) + ); + } + + $entry = ldap_first_entry($ds, $result); + if ($entry === false) { + throw new LdapException( + 'Capabilities not available (%s:%d): %s. Discovery of root DSE probably not permitted.', + $connection->getHostname(), + $connection->getPort(), + ldap_error($ds) + ); + } + $cap = new LdapCapabilities( + $connection->cleanupAttributes( + ldap_get_attributes($ds, $entry), + array_flip($fields) + ) + ); + return $cap; + } + + /** + * Determine the active directory version using the available capabillities + * + * @return null|string The server version description or null when unknown + */ + protected function getAdObjectVersionName() + { + if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_W8_OID])) { + return 'Windows Server 2012 (or newer)'; + } + if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID])) { + return 'Windows Server 2008 R2 (or newer)'; + } + if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_V60_OID])) { + return 'Windows Server 2008 (or newer)'; + } + return null; + } +} diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php new file mode 100644 index 000000000..841e855da --- /dev/null +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -0,0 +1,1148 @@ +hostname = $config->hostname; + $this->bindDn = $config->bind_dn; + $this->bindPw = $config->bind_pw; + $this->rootDn = $config->root_dn; + $this->port = $config->get('port', 389); + $this->validateCertificate = (bool) $config->get('reqcert', true); + + $this->encryption = $config->encryption; + if ($this->encryption !== null) { + $this->encryption = strtolower($this->encryption); + } + } + + /** + * Return the ip address, hostname or ldap URI being used to connect with the LDAP server + * + * @return string + */ + public function getHostname() + { + return $this->hostname; + } + + /** + * Return the port being used to connect with the LDAP server + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Return the distinguished name being used as the base path for queries which do not provide one theirselves + * + * @return string + */ + public function getDn() + { + return $this->rootDn; + } + + /** + * Return the root node for this connection + * + * @return Root + */ + public function root() + { + if ($this->root === null) { + $this->root = Root::forConnection($this); + } + + return $this->root; + } + + /** + * Return the LDAP link identifier being used + * + * Establishes a connection if necessary. + * + * @return resource + */ + public function getConnection() + { + if ($this->ds === null) { + $this->ds = $this->prepareNewConnection(); + } + + return $this->ds; + } + + /** + * Return the capabilities of the current connection + * + * @return LdapCapabilities + */ + public function getCapabilities() + { + if ($this->capabilities === null) { + try { + $this->capabilities = LdapCapabilities::discoverCapabilities($this); + $this->discoverySuccess = true; + } catch (LdapException $e) { + Logger::debug($e); + Logger::warning('LADP discovery failed, assuming default LDAP capabilities.'); + $this->capabilities = new LdapCapabilities(); // create empty default capabilities + $this->discoverySuccess = false; + } + } + + return $this->capabilities; + } + + /** + * Return whether discovery was successful + * + * @return bool true if the capabilities were successfully determined, false if the capabilities were guessed + */ + public function discoverySuccessful() + { + if ($this->discoverySuccess === null) { + $this->getCapabilities(); // Initializes self::$discoverySuccess + } + + return $this->discoverySuccess; + } + + /** + * Return whether the current connection is encrypted + * + * @return bool + */ + public function isEncrypted() + { + if ($this->encrypted === null) { + return false; + } + + return $this->encrypted; + } + + /** + * Establish a connection + * + * @throws LdapException In case the connection could not be established + * + * @deprecated The connection is established lazily now + */ + public function connect() + { + $this->getConnection(); + } + + /** + * Perform a LDAP bind on the current connection + * + * @throws LdapException In case the LDAP bind was unsuccessful or insecure + */ + public function bind() + { + if ($this->bound) { + return $this; + } + + $ds = $this->getConnection(); + + $success = @ldap_bind($ds, $this->bindDn, $this->bindPw); + if (! $success) { + throw new LdapException( + 'LDAP connection to %s:%s (%s / %s) failed: %s', + $this->hostname, + $this->port, + $this->bindDn, + '***' /* $this->bindPw */, + ldap_error($ds) + ); + } + + $this->bound = true; + return $this; + } + + /** + * Provide a query on this connection + * + * @return LdapQuery + */ + public function select() + { + return new LdapQuery($this); + } + + /** + * Fetch and return all rows of the given query's result set using an iterator + * + * @param LdapQuery $query The query returning the result set + * + * @return ArrayIterator + */ + public function query(LdapQuery $query) + { + return new ArrayIterator($this->fetchAll($query)); + } + + /** + * Count all rows of the given query's result set + * + * @param LdapQuery $query The query returning the result set + * + * @return int + */ + public function count(LdapQuery $query) + { + $this->bind(); + + $res = $this->runQuery($query, array()); + return count($res); + } + + /** + * Retrieve an array containing all rows of the result set + * + * @param LdapQuery $query The query returning the result set + * @param array $fields Request these attributes instead of the ones registered in the given query + * + * @return array + */ + public function fetchAll(LdapQuery $query, array $fields = null) + { + $this->bind(); + + if ($query->getUsePagedResults() + && version_compare(PHP_VERSION, '5.4.0') >= 0 + && $this->getCapabilities()->hasPagedResult() + ) { + return $this->runPagedQuery($query, $fields); + } else { + return $this->runQuery($query, $fields); + } + } + + /** + * Fetch the first row of the result set + * + * @param LdapQuery $query The query returning the result set + * @param array $fields Request these attributes instead of the ones registered in the given query + * + * @return mixed + */ + public function fetchRow(LdapQuery $query, array $fields = null) + { + $clonedQuery = clone $query; + $clonedQuery->limit(1); + $clonedQuery->setUsePagedResults(false); + $results = $this->fetchAll($clonedQuery, $fields); + return array_shift($results) ?: false; + } + + /** + * Fetch the first column of all rows of the result set as an array + * + * @param LdapQuery $query The query returning the result set + * @param array $fields Request these attributes instead of the ones registered in the given query + * + * @return array + * + * @throws ProgrammingError In case no attribute is being requested + */ + public function fetchColumn(LdapQuery $query, array $fields = null) + { + if ($fields === null) { + $fields = $query->getColumns(); + } + + if (empty($fields)) { + throw new ProgrammingError('You must request at least one attribute when fetching a single column'); + } + + $alias = key($fields); + $results = $this->fetchAll($query, array($alias => current($fields))); + $column = is_int($alias) ? current($fields) : $alias; + $values = array(); + foreach ($results as $row) { + if (isset($row->$column)) { + $values[] = $row->$column; + } + } + + return $values; + } + + /** + * Fetch the first column of the first row of the result set + * + * @param LdapQuery $query The query returning the result set + * @param array $fields Request these attributes instead of the ones registered in the given query + * + * @return string + */ + public function fetchOne(LdapQuery $query, array $fields = null) + { + $row = (array) $this->fetchRow($query, $fields); + return array_shift($row) ?: false; + } + + /** + * Fetch all rows of the result set as an array of key-value pairs + * + * The first column is the key, the second column is the value. + * + * @param LdapQuery $query The query returning the result set + * @param array $fields Request these attributes instead of the ones registered in the given query + * + * @return array + * + * @throws ProgrammingError In case there are less than two attributes being requested + */ + public function fetchPairs(LdapQuery $query, array $fields = null) + { + if ($fields === null) { + $fields = $query->getColumns(); + } + + if (count($fields) < 2) { + throw new ProgrammingError('You are required to request at least two attributes'); + } + + $columns = $desiredColumnNames = array(); + foreach ($fields as $alias => $column) { + if (is_int($alias)) { + $columns[] = $column; + $desiredColumnNames[] = $column; + } else { + $columns[$alias] = $column; + $desiredColumnNames[] = $alias; + } + + if (count($desiredColumnNames) === 2) { + break; + } + } + + $results = $this->fetchAll($query, $columns); + $pairs = array(); + foreach ($results as $row) { + $colOne = $desiredColumnNames[0]; + $colTwo = $desiredColumnNames[1]; + $pairs[$row->$colOne] = $row->$colTwo; + } + + return $pairs; + } + + /** + * Test the given LDAP credentials by establishing a connection and attempting a LDAP bind + * + * @param string $bindDn + * @param string $bindPw + * + * @return bool Whether the given credentials are valid + * + * @throws LdapException In case an error occured while establishing the connection or attempting the bind + */ + public function testCredentials($bindDn, $bindPw) + { + $ds = $this->getConnection(); + $success = @ldap_bind($ds, $bindDn, $bindPw); + if (! $success) { + if (ldap_errno($ds) === self::LDAP_INVALID_CREDENTIALS) { + Logger::debug( + 'Testing LDAP credentials (%s / %s) failed: %s', + $bindDn, + '***', + ldap_error($ds) + ); + return false; + } + + throw new LdapException(ldap_error($ds)); + } + + return true; + } + + /** + * Return whether an entry identified by the given distinguished name exists + * + * @param string $dn + * + * @return bool + */ + public function hasDn($dn) + { + $this->bind(); + + $ds = $this->getConnection(); + $result = ldap_read($ds, $dn, '(objectClass=*)', array('objectClass')); + return ldap_count_entries($ds, $result) > 0; + } + + /** + * Delete a root entry and all of its children identified by the given distinguished name + * + * @param string $dn + * + * @return bool + * + * @throws LdapException In case an error occured while deleting an entry + */ + public function deleteRecursively($dn) + { + $this->bind(); + + $ds = $this->getConnection(); + $result = @ldap_list($ds, $dn, '(objectClass=*)', array('objectClass')); + if ($result === false) { + if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) { + return false; + } + + throw new LdapException('LDAP list for "%s" failed: %s', $dn, ldap_error($ds)); + } + + $children = ldap_get_entries($ds, $result); + for ($i = 0; $i < $children['count']; $i++) { + $result = $this->deleteRecursively($children[$i]['dn']); + if (! $result) { + // TODO: return result code, if delete fails + throw new LdapException('Recursively deleting "%s" failed', $dn); + } + } + + return $this->deleteDn($dn); + } + + /** + * Delete a single entry identified by the given distinguished name + * + * @param string $dn + * + * @return bool + * + * @throws LdapException In case an error occured while deleting the entry + */ + public function deleteDn($dn) + { + $this->bind(); + + $ds = $this->getConnection(); + $result = @ldap_delete($ds, $dn); + if ($result === false) { + if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) { + return false; // TODO: Isn't it a success if something i'd like to remove is not existing at all??? + } + + throw new LdapException('LDAP delete for "%s" failed: %s', $dn, ldap_error($ds)); + } + + return true; + } + + /** + * Fetch the distinguished name of the result of the given query + * + * @param LdapQuery $query The query returning the result set + * + * @return string The distinguished name, or false when the given query yields no results + * + * @throws LdapException In case the query yields multiple results + */ + public function fetchDn(LdapQuery $query) + { + $rows = $this->fetchAll($query, array()); + if (count($rows) > 1) { + throw new LdapException('Cannot fetch single DN for %s', $query); + } + + return key($rows); + } + + /** + * Run the given LDAP query and return the resulting entries + * + * @param LdapQuery $query The query to fetch results with + * @param array $fields Request these attributes instead of the ones registered in the given query + * + * @return array + * + * @throws LdapException In case an error occured while fetching the results + */ + protected function runQuery(LdapQuery $query, array $fields = null) + { + $limit = $query->getLimit(); + $offset = $query->hasOffset() ? $query->getOffset() - 1 : 0; + + if ($fields === null) { + $fields = $query->getColumns(); + } + + $ds = $this->getConnection(); + + $serverSorting = false;//$this->capabilities->hasOid(Capability::LDAP_SERVER_SORT_OID); + if ($serverSorting && $query->hasOrder()) { + ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array( + array( + 'oid' => LdapCapabilities::LDAP_SERVER_SORT_OID, + 'value' => $this->encodeSortRules($query->getOrder()) + ) + )); + } elseif ($query->hasOrder()) { + foreach ($query->getOrder() as $rule) { + if (! in_array($rule[0], $fields)) { + $fields[] = $rule[0]; + } + } + } + + $results = @ldap_search( + $ds, + $query->getBase() ?: $this->rootDn, + (string) $query, + array_values($fields), + 0, // Attributes and values + $serverSorting && $limit ? $offset + $limit : 0 + ); + if ($results === false) { + if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) { + return array(); + } + + throw new LdapException( + 'LDAP query "%s" (base %s) failed. Error: %s', + $query, + $query->getBase() ?: $this->rootDn, + ldap_error($ds) + ); + } elseif (ldap_count_entries($ds, $results) === 0) { + return array(); + } + + $count = 0; + $entries = array(); + $entry = ldap_first_entry($ds, $results); + do { + $count += 1; + if (! $serverSorting || $offset === 0 || $offset < $count) { + $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes( + ldap_get_attributes($ds, $entry), + array_flip($fields) + ); + } + } while ((! $serverSorting || $limit === 0 || $limit !== count($entries)) + && ($entry = ldap_next_entry($ds, $entry)) + ); + + if (! $serverSorting && $query->hasOrder()) { + uasort($entries, array($query, 'compare')); + if ($limit && $count > $limit) { + $entries = array_splice($entries, $query->hasOffset() ? $query->getOffset() : 0, $limit); + } + } + + ldap_free_result($results); + return $entries; + } + + /** + * Run the given LDAP query and return the resulting entries + * + * This utilizes paged search requests as defined in RFC 2696. + * + * @param LdapQuery $query The query to fetch results with + * @param array $fields Request these attributes instead of the ones registered in the given query + * @param int $pageSize The maximum page size, defaults to self::PAGE_SIZE + * + * @return array + * + * @throws LdapException In case an error occured while fetching the results + */ + protected function runPagedQuery(LdapQuery $query, array $fields = null, $pageSize = null) + { + if ($pageSize === null) { + $pageSize = static::PAGE_SIZE; + } + + $limit = $query->getLimit(); + $offset = $query->hasOffset() ? $query->getOffset() - 1 : 0; + $queryString = (string) $query; + $base = $query->getBase() ?: $this->rootDn; + + if ($fields === null) { + $fields = $query->getColumns(); + } + + $ds = $this->getConnection(); + + $serverSorting = false;//$this->capabilities->hasOid(Capability::LDAP_SERVER_SORT_OID); + if ($serverSorting && $query->hasOrder()) { + ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array( + array( + 'oid' => LdapCapabilities::LDAP_SERVER_SORT_OID, + 'value' => $this->encodeSortRules($query->getOrder()) + ) + )); + } elseif ($query->hasOrder()) { + foreach ($query->getOrder() as $rule) { + if (! in_array($rule[0], $fields)) { + $fields[] = $rule[0]; + } + } + } + + $count = 0; + $cookie = ''; + $entries = array(); + do { + // Do not request the pagination control as a critical extension, as we want the + // server to return results even if the paged search request cannot be satisfied + ldap_control_paged_result($ds, $pageSize, false, $cookie); + + $results = @ldap_search( + $ds, + $base, + $queryString, + array_values($fields), + 0, // Attributes and values + $serverSorting && $limit ? $offset + $limit : 0 + ); + if ($results === false) { + if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) { + break; + } + + throw new LdapException( + 'LDAP query "%s" (base %s) failed. Error: %s', + $queryString, + $base, + ldap_error($ds) + ); + } elseif (ldap_count_entries($ds, $results) === 0) { + if (in_array( + ldap_errno($ds), + array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED) + )) { + Logger::warning( + 'Unable to request more than %u results. Does the server allow paged search requests? (%s)', + $count, + ldap_error($ds) + ); + } + + break; + } + + $entry = ldap_first_entry($ds, $results); + do { + $count += 1; + if (! $serverSorting || $offset === 0 || $offset < $count) { + $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes( + ldap_get_attributes($ds, $entry), + array_flip($fields) + ); + } + } while ( + (! $serverSorting || $limit === 0 || $limit !== count($entries)) + && ($entry = ldap_next_entry($ds, $entry)) + ); + + if (false === @ldap_control_paged_result_response($ds, $results, $cookie)) { + // If the page size is greater than or equal to the sizeLimit value, the server should ignore the + // control as the request can be satisfied in a single page: https://www.ietf.org/rfc/rfc2696.txt + // This applies no matter whether paged search requests are permitted or not. You're done once you + // got everything you were out for. + if ($serverSorting && count($entries) !== $limit) { + // The server does not support pagination, but still returned a response by ignoring the + // pagedResultsControl. We output a warning to indicate that the pagination control was ignored. + Logger::warning( + 'Unable to request paged LDAP results. Does the server allow paged search requests?' + ); + } + } + + ldap_free_result($results); + } while ($cookie && (! $serverSorting || $limit === 0 || count($entries) < $limit)); + + if ($cookie) { + // A sequence of paged search requests is abandoned by the client sending a search request containing a + // pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by + // the server: https://www.ietf.org/rfc/rfc2696.txt + ldap_control_paged_result($ds, 0, false, $cookie); + ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size + } else { + // Reset the paged search request so that subsequent requests succeed + ldap_control_paged_result($ds, 0); + } + + if (! $serverSorting && $query->hasOrder()) { + uasort($entries, array($query, 'compare')); + if ($limit && $count > $limit) { + $entries = array_splice($entries, $query->hasOffset() ? $query->getOffset() : 0, $limit); + } + } + + return $entries; + } + + /** + * Clean up the given attributes and return them as simple object + * + * Applies column aliases, aggregates multi-value attributes as array and sets null for each missing attribute. + * + * @param array $attributes + * @param array $requestedFields + * + * @return object + */ + public function cleanupAttributes($attributes, array $requestedFields) + { + // In case the result contains attributes with a differing case than the requested fields, it is + // necessary to create another array to map attributes case insensitively to their requested counterparts. + // This does also apply the virtual alias handling. (Since an LDAP server does not handle such) + $loweredFieldMap = array(); + foreach ($requestedFields as $name => $alias) { + $loweredFieldMap[strtolower($name)] = is_string($alias) ? $alias : $name; + } + + $cleanedAttributes = array(); + for ($i = 0; $i < $attributes['count']; $i++) { + $attribute_name = $attributes[$i]; + if ($attributes[$attribute_name]['count'] === 1) { + $attribute_value = $attributes[$attribute_name][0]; + } else { + $attribute_value = array(); + for ($j = 0; $j < $attributes[$attribute_name]['count']; $j++) { + $attribute_value[] = $attributes[$attribute_name][$j]; + } + } + + $requestedAttributeName = isset($loweredFieldMap[strtolower($attribute_name)]) + ? $loweredFieldMap[strtolower($attribute_name)] + : $attribute_name; + $cleanedAttributes[$requestedAttributeName] = $attribute_value; + } + + // The result may not contain all requested fields, so populate the cleaned + // result with the missing fields and their value being set to null + foreach ($requestedFields as $name => $alias) { + if (! is_string($alias)) { + $alias = $name; + } + + if (! array_key_exists($alias, $cleanedAttributes)) { + $cleanedAttributes[$alias] = null; + Logger::debug('LDAP query result does not provide the requested field "%s"', $name); + } + } + + return (object) $cleanedAttributes; + } + + /** + * Encode the given array of sort rules as ASN.1 octet stream according to RFC 2891 + * + * @param array $sortRules + * + * @return string + * @throws ProgrammingError + * + * @todo Produces an invalid stream, obviously + */ + protected function encodeSortRules(array $sortRules) + { + if (count($sortRules) > 127) { + throw new ProgrammingError( + 'Cannot encode more than 127 sort rules. Only length octets in short form are supported' + ); + } + + $seq = '30' . str_pad(dechex(count($sortRules)), 2, '0', STR_PAD_LEFT); + foreach ($sortRules as $rule) { + $hexdAttribute = unpack('H*', $rule[0]); + $seq .= '3002' + . '04' . str_pad(dechex(strlen($rule[0])), 2, '0', STR_PAD_LEFT) . $hexdAttribute[1] + . '0101' . ($rule[1] === Sortable::SORT_DESC ? 'ff' : '00'); + } + + return $seq; + } + + /** + * Prepare and establish a connection with the LDAP server + * + * @param Inspection $info Optional inspection to fill with diagnostic info + * + * @return resource A LDAP link identifier + * + * @throws LdapException In case the connection is not possible + */ + protected function prepareNewConnection(Inspection $info = null) + { + if (! isset($info)) { + $info = new Inspection(''); + } + + if ($this->encryption === static::STARTTLS || $this->encryption === static::LDAPS) { + $this->prepareTlsEnvironment(); + } + + $hostname = $this->hostname; + if ($this->encryption === static::LDAPS) { + $info->write('Connect using LDAPS'); + if (! $this->validateCertificate) { + $info->write('Skip certificate validation'); + } + $hostname = 'ldaps://' . $hostname; + } + + $ds = ldap_connect($hostname, $this->port); + + // Usage of ldap_rename, setting LDAP_OPT_REFERRALS to 0 or using STARTTLS requires LDAPv3. + // If this does not work we're probably not in a PHP 5.3+ environment as it is VERY + // unlikely that the server complains about it by itself prior to a bind request + ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); + + // Not setting this results in "Operations error" on AD when using the whole domain as search base + ldap_set_option($ds, LDAP_OPT_REFERRALS, 0); + + if ($this->encryption === static::STARTTLS) { + $this->encrypted = true; + $info->write('Connect using STARTTLS'); + if (! $this->validateCertificate) { + $info->write('Skip certificate validation'); + } + if (! ldap_start_tls($ds)) { + throw new LdapException('LDAP STARTTLS failed: %s', ldap_error($ds)); + } + + } elseif ($this->encryption !== static::LDAPS) { + $this->encrypted = false; + $info->write('Connect without encryption'); + } + + return $ds; + } + + /** + * Set up how to handle StartTLS connections + * + * @throws LdapException In case the LDAPRC environment variable cannot be set + */ + protected function prepareTlsEnvironment() + { + // TODO: allow variable known CA location (system VS Icinga) + if (Platform::isWindows()) { + putenv('LDAPTLS_REQCERT=never'); + } else { + if ($this->validateCertificate) { + $ldap_conf = $this->getConfigDir('ldap_ca.conf'); + } else { + $ldap_conf = $this->getConfigDir('ldap_nocert.conf'); + } + + putenv('LDAPRC=' . $ldap_conf); // TODO: Does not have any effect + if (getenv('LDAPRC') !== $ldap_conf) { + throw new LdapException('putenv failed'); + } + } + } + + /** + * Create an LDAP entry + * + * @param string $dn The distinguished name to use + * @param array $attributes The entry's attributes + * + * @return bool Whether the operation was successful + */ + public function addEntry($dn, array $attributes) + { + return ldap_add($this->getConnection(), $dn, $attributes); + } + + /** + * Modify an LDAP entry + * + * @param string $dn The distinguished name to use + * @param array $attributes The attributes to update the entry with + * + * @return bool Whether the operation was successful + */ + public function modifyEntry($dn, array $attributes) + { + return ldap_modify($this->getConnection(), $dn, $attributes); + } + + /** + * Change the distinguished name of an LDAP entry + * + * @param string $dn The entry's current distinguished name + * @param string $newRdn The new relative distinguished name + * @param string $newParentDn The new parent or superior entry's distinguished name + * + * @return resource The resulting search result identifier + * + * @throws LdapException In case an error occured + */ + public function moveEntry($dn, $newRdn, $newParentDn) + { + $ds = $this->getConnection(); + $result = ldap_rename($ds, $dn, $newRdn, $newParentDn, false); + if ($result === false) { + throw new LdapException('Could not move entry "%s" to "%s": %s', $dn, $newRdn, ldap_error($ds)); + } + + return $result; + } + + /** + * Return the LDAP specific configuration directory with the given relative path being appended + * + * @param string $sub + * + * @return string + */ + protected function getConfigDir($sub = null) + { + $dir = Config::$configDir . '/ldap'; + if ($sub !== null) { + $dir .= '/' . $sub; + } + + return $dir; + } + + /** + * Inspect if this LDAP Connection is working as expected + * + * Check if connection, bind and encryption is working as expected and get additional + * information about the used + * + * @return Inspection Inspection result + */ + public function inspect() + { + $insp = new Inspection('Ldap Connection'); + + // Try to connect to the server with the given connection parameters + try { + $ds = $this->prepareNewConnection($insp); + } catch (Exception $e) { + return $insp->error($e->getMessage()); + } + + // Try a bind-command with the given user credentials, this must not fail + $success = @ldap_bind($ds, $this->bindDn, $this->bindPw); + $msg = sprintf( + 'LDAP bind to %s:%s (%s / %s)', + $this->hostname, + $this->port, + $this->bindDn, + '***' /* $this->bindPw */ + ); + if (! $success) { + return $insp->error(sprintf('%s failed: %s', $msg, ldap_error($ds))); + } + $insp->write(sprintf($msg . ' successful')); + + // Try to execute a schema discovery this may fail if schema discovery is not supported + try { + $cap = LdapCapabilities::discoverCapabilities($this); + $discovery = new Inspection('Discovery Results'); + $discovery->write($cap->getVendor()); + $version = $cap->getVersion(); + if (isset($version)) { + $discovery->write($version); + } + $discovery->write('Supports STARTTLS: ' . ($cap->hasStartTls() ? 'True' : 'False')); + $discovery->write('Default naming context: ' . $cap->getDefaultNamingContext()); + $insp->write($discovery); + } catch (Exception $e) { + $insp->write('Schema discovery not possible: ' . $e->getMessage()); + } + return $insp; + } + + /** + * Reset the environment variables set by self::prepareTlsEnvironment() + */ + public function __destruct() + { + putenv('LDAPRC'); + } +} diff --git a/library/Icinga/Protocol/Ldap/LdapException.php b/library/Icinga/Protocol/Ldap/LdapException.php new file mode 100644 index 000000000..aec4a88db --- /dev/null +++ b/library/Icinga/Protocol/Ldap/LdapException.php @@ -0,0 +1,14 @@ +filters = array(); + $this->usePagedResults = true; + } + + /** + * Set the base dn to be used for this query + * + * @param string $base + * + * @return $this + */ + public function setBase($base) + { + $this->base = $base; + return $this; + } + + /** + * Return the base dn being used for this query + * + * @return string + */ + public function getBase() + { + return $this->base; + } + + /** + * Set whether this query is permitted to utilize paged results + * + * @param bool $state + * + * @return $this + */ + public function setUsePagedResults($state = true) + { + $this->usePagedResults = (bool) $state; + return $this; + } + + /** + * Return whether this query is permitted to utilize paged results + * + * @return bool + */ + public function getUsePagedResults() + { + return $this->usePagedResults; + } + + /** + * Choose an objectClass and the columns you are interested in + * + * {@inheritdoc} This creates an objectClass filter. + */ + public function from($target, array $fields = null) + { + $this->filters['objectClass'] = $target; + return parent::from($target, $fields); + } + + /** + * Add a new filter to the query + * + * @param string $condition Column to search in + * @param mixed $value Value to look for (asterisk wildcards are allowed) + * + * @return $this + */ + public function where($condition, $value = null) + { + // TODO: Adjust this once support for Icinga\Data\Filter is available + if ($condition instanceof Expression) { + $this->filters[] = $condition; + } else { + $this->filters[$condition] = $value; + } + + return $this; + } + + public function getFilter() + { + throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead'); + } + + public function addFilter(Filter $filter) + { + // TODO: This should be considered a quick fix only. + // Drop this entirely once support for Icinga\Data\Filter is available + if ($filter->isExpression()) { + $this->where($filter->getColumn(), $filter->getExpression()); + } elseif ($filter->isChain()) { + foreach ($filter->filters() as $chainOrExpression) { + $this->addFilter($chainOrExpression); + } + } + } + + public function setFilter(Filter $filter) + { + throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead'); + } + + /** + * Fetch result as tree + * + * @return Root + * + * @todo This is untested waste, not being used anywhere and ignores the query's order and base dn. + * Evaluate whether it's reasonable to properly implement and test it. + */ + public function fetchTree() + { + $result = $this->fetchAll(); + $sorted = array(); + $quotedDn = preg_quote($this->ds->getDn(), '/'); + foreach ($result as $key => & $item) { + $new_key = LdapUtils::implodeDN( + array_reverse( + LdapUtils::explodeDN( + preg_replace('/,' . $quotedDn . '$/', '', $key) + ) + ) + ); + $sorted[$new_key] = $key; + } + unset($groups); + ksort($sorted); + + $tree = Root::forConnection($this->ds); + $root_dn = $tree->getDN(); + foreach ($sorted as $sort_key => & $key) { + if ($key === $root_dn) { + continue; + } + $tree->createChildByDN($key, $result[$key]); + } + return $tree; + } + + /** + * Fetch the distinguished name of the first result + * + * @return string|false The distinguished name or false in case it's not possible to fetch a result + * + * @throws LdapException In case the query returns multiple results + * (i.e. it's not possible to fetch a unique DN) + */ + public function fetchDn() + { + return $this->ds->fetchDn($this); + } + + /** + * Return the LDAP filter to be applied on this query + * + * @return string + * + * @throws LdapException In case the objectClass filter does not exist + */ + protected function renderFilter() + { + if (! isset($this->filters['objectClass'])) { + throw new LdapException('Object class is mandatory'); + } + + $parts = array(); + foreach ($this->filters as $key => $value) { + if ($value instanceof Expression) { + $parts[] = (string) $value; + } else { + $parts[] = sprintf( + '%s=%s', + LdapUtils::quoteForSearch($key), + LdapUtils::quoteForSearch($value, true) + ); + } + } + + if (count($parts) > 1) { + return '(&(' . implode(')(', $parts) . '))'; + } else { + return '(' . $parts[0] . ')'; + } + } + + /** + * Return the LDAP filter to be applied on this query + * + * @return string + */ + public function __toString() + { + return $this->renderFilter(); + } +} diff --git a/library/Icinga/Protocol/Ldap/LdapUtils.php b/library/Icinga/Protocol/Ldap/LdapUtils.php index 8232ebf67..c151ebc0d 100644 --- a/library/Icinga/Protocol/Ldap/LdapUtils.php +++ b/library/Icinga/Protocol/Ldap/LdapUtils.php @@ -1,6 +1,5 @@ - * $connection->select()->from('user')->where('sAMAccountName = ?', 'icinga'); - * - * - * @copyright Copyright (c) 2013 Icinga-Web Team - * @author Icinga-Web Team - * @package Icinga\Protocol\Ldap - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License - */ -class Query -{ - protected $connection; - protected $filters = array(); - protected $fields = array(); - protected $limit_count; - protected $limit_offset; - protected $sort_columns = array(); - protected $count; - protected $base; - - /** - * Constructor - * - * @param Connection LDAP Connection object - * @return void - */ - public function __construct(Connection $connection) - { - $this->connection = $connection; - } - - public function setBase($base) - { - $this->base = $base; - return $this; - } - - public function hasBase() - { - return $this->base !== null; - } - - public function getBase() - { - return $this->base; - } - - /** - * Count result set, ignoring limits - * - * @return int - */ - public function count() - { - if ($this->count === null) { - $this->count = $this->connection->count($this); - } - return $this->count; - } - - /** - * Count result set, ignoring limits - * - * @return int - */ - public function limit($count = null, $offset = null) - { - if (! preg_match('~^\d+~', $count . $offset)) { - throw new Exception( - 'Got invalid limit: %s, %s', - $count, - $offset - ); - } - $this->limit_count = (int) $count; - $this->limit_offset = (int) $offset; - return $this; - } - - /** - * Whether a limit has been set - * - * @return boolean - */ - public function hasLimit() - { - return $this->limit_count !== null; - } - - /** - * Whether an offset (limit) has been set - * - * @return boolean - */ - public function hasOffset() - { - return $this->limit_offset > 0; - } - - /** - * Retrieve result limit - * - * @return int - */ - public function getLimit() - { - return $this->limit_count; - } - - /** - * Retrieve result offset - * - * @return int - */ - public function getOffset() - { - return $this->limit_offset; - } - - /** - * Fetch result as tree - * - * @return Node - */ - public function fetchTree() - { - $result = $this->fetchAll(); - $sorted = array(); - $quotedDn = preg_quote($this->connection->getDN(), '/'); - foreach ($result as $key => & $item) { - $new_key = LdapUtils::implodeDN( - array_reverse( - LdapUtils::explodeDN( - preg_replace('/,' . $quotedDn . '$/', '', $key) - ) - ) - ); - $sorted[$new_key] = $key; - } - unset($groups); - ksort($sorted); - - $tree = Root::forConnection($this->connection); - $root_dn = $tree->getDN(); - foreach ($sorted as $sort_key => & $key) { - if ($key === $root_dn) { - continue; - } - $tree->createChildByDN($key, $result[$key]); - } - return $tree; - } - - /** - * Fetch result as an array of objects - * - * @return array - */ - public function fetchAll() - { - return $this->connection->fetchAll($this); - } - - /** - * Fetch first result row - * - * @return object - */ - public function fetchRow() - { - return $this->connection->fetchRow($this); - } - - /** - * Fetch first column value from first result row - * - * @return mixed - */ - public function fetchOne() - { - return $this->connection->fetchOne($this); - } - - /** - * Fetch a key/value list, first column is key, second is value - * - * @return array - */ - public function fetchPairs() - { - // STILL TODO!! - return $this->connection->fetchPairs($this); - } - - /** - * Where to select (which fields) from - * - * This creates an objectClass filter - * - * @return Query - */ - public function from($objectClass, $fields = array()) - { - $this->filters['objectClass'] = $objectClass; - $this->fields = $fields; - return $this; - } - - /** - * Add a new filter to the query - * - * @param string Column to search in - * @param string Filter text (asterisks are allowed) - * @return Query - */ - public function where($key, $val) - { - $this->filters[$key] = $val; - return $this; - } - - /** - * Sort by given column - * - * TODO: Sort direction is not implemented yet - * - * @param string Order column - * @param string Order direction - * @return Query - */ - public function order($column, $direction = 'ASC') - { - $this->sort_columns[] = array($column, $direction); - return $this; - } - - /** - * Retrieve a list of the desired fields - * - * @return array - */ - public function listFields() - { - return $this->fields; - } - - /** - * Retrieve a list containing current sort columns - * - * @return array - */ - public function getSortColumns() - { - return $this->sort_columns; - } - - /** - * Return a pagination adapter for the current query - * - * @return \Zend_Paginator - */ - public function paginate($limit = null, $page = null) - { - if ($page === null || $limit === null) { - $request = \Zend_Controller_Front::getInstance()->getRequest(); - if ($page === null) { - $page = $request->getParam('page', 0); - } - if ($limit === null) { - $limit = $request->getParam('limit', 20); - } - } - $paginator = new \Zend_Paginator( - // TODO: Adapter doesn't fit yet: - new \Icinga\Web\Paginator\Adapter\QueryAdapter($this) - ); - $paginator->setItemCountPerPage($limit); - $paginator->setCurrentPageNumber($page); - return $paginator; - } - - /** - * Returns the LDAP filter that will be applied - * - * @string - */ - public function create() - { - $parts = array(); - if (! isset($this->filters['objectClass']) || $this->filters['objectClass'] === null) { - throw new Exception('Object class is mandatory'); - } - foreach ($this->filters as $key => $value) { - $parts[] = sprintf( - '%s=%s', - LdapUtils::quoteForSearch($key), - LdapUtils::quoteForSearch($value, true) - ); - } - if (count($parts) > 1) { - return '(&(' . implode(')(', $parts) . '))'; - } else { - return '(' . $parts[0] . ')'; - } - } - - /** - * Descructor - */ - public function __destruct() - { - // To be on the safe side: - unset($this->connection); - } -} diff --git a/library/Icinga/Protocol/Ldap/Root.php b/library/Icinga/Protocol/Ldap/Root.php index 56abb4175..23729d10f 100644 --- a/library/Icinga/Protocol/Ldap/Root.php +++ b/library/Icinga/Protocol/Ldap/Root.php @@ -1,6 +1,5 @@ connection = $connection; } @@ -54,10 +53,10 @@ class Root } /** - * @param Connection $connection + * @param LdapConnection $connection * @return Root */ - public static function forConnection(Connection $connection) + public static function forConnection(LdapConnection $connection) { $root = new Root($connection); return $root; @@ -178,17 +177,17 @@ class Root } /** - * @param Connection $connection + * @param LdapConnection $connection * @return $this */ - public function setConnection(Connection $connection) + public function setConnection(LdapConnection $connection) { $this->connection = $connection; return $this; } /** - * @return Connection + * @return LdapConnection */ public function getConnection() { @@ -216,7 +215,7 @@ class Root */ public function getDN() { - return $this->connection->getDN(); + return $this->connection->getDn(); } /** diff --git a/library/Icinga/Protocol/Livestatus/Connection.php b/library/Icinga/Protocol/Livestatus/Connection.php index b2340eeec..b854e6d36 100644 --- a/library/Icinga/Protocol/Livestatus/Connection.php +++ b/library/Icinga/Protocol/Livestatus/Connection.php @@ -1,6 +1,5 @@ matches($res)) continue; $result[] = $res; } - + if ($query->hasOrder()) { usort($result, array($query, 'compare')); } @@ -417,7 +416,7 @@ if ($col > $size - 1) return $res; /** * Disconnect in case we are connected to a Livestatus socket * - * @return self + * @return $this */ public function disconnect() { diff --git a/library/Icinga/Protocol/Livestatus/Query.php b/library/Icinga/Protocol/Livestatus/Query.php index a421c7e71..cb13cd818 100644 --- a/library/Icinga/Protocol/Livestatus/Query.php +++ b/library/Icinga/Protocol/Livestatus/Query.php @@ -1,6 +1,5 @@ raw SplArray // $res -> object - // $cv -> + // $cv -> // $result -> object to be returned $result = (object) array(); $res = $this->withHeaders($row); @@ -142,7 +141,7 @@ class Query extends SimpleQuery } } - + return $result; } @@ -313,7 +312,7 @@ class Query extends SimpleQuery } elseif (is_array($col)) { foreach ($col as $k) { $columns[$k] = true; - } + } } else { $columns[$col] = true; } diff --git a/library/Icinga/Protocol/Livestatus/ResponseRow.php b/library/Icinga/Protocol/Livestatus/ResponseRow.php index d4b0fd7cb..f3cf055a0 100644 --- a/library/Icinga/Protocol/Livestatus/ResponseRow.php +++ b/library/Icinga/Protocol/Livestatus/ResponseRow.php @@ -1,4 +1,5 @@ + *
  • Support for table aliases
  • + *
  • Automatic table prefix handling
  • + *
  • Insert, update and delete capabilities
  • + *
  • Differentiation between statement and query columns
  • + *
  • Capability to join additional tables depending on the columns being selected or used in a filter
  • + * + */ +abstract class DbRepository extends Repository implements Extensible, Updatable, Reducible +{ + /** + * The datasource being used + * + * @var DbConnection + */ + protected $ds; + + /** + * The table aliases being applied + * + * This must be initialized by repositories which are going to make use of table aliases. Every table for which + * aliased columns are provided must be defined in this array using its name as key and the alias being used as + * value. Failure to do so will result in invalid queries. + * + * @var array + */ + protected $tableAliases; + + /** + * The statement columns being provided + * + * This may be initialized by repositories which are going to make use of table aliases. It allows to provide + * alias-less column names to be used for a statement. The array needs to be in the following format: + *
    
    +     *  array(
    +     *      'table_name' => array(
    +     *          'column1',
    +     *          'alias1' => 'column2',
    +     *          'alias2' => 'column3'
    +     *      )
    +     *  )
    +     * 
    
    +     *
    +     * @var array
    +     */
    +    protected $statementColumns;
    +
    +    /**
    +     * An array to map table names to statement columns/aliases
    +     *
    +     * @var array
    +     */
    +    protected $statementAliasTableMap;
    +
    +    /**
    +     * A flattened array to map statement columns to aliases
    +     *
    +     * @var array
    +     */
    +    protected $statementAliasColumnMap;
    +
    +    /**
    +     * An array to map table names to statement columns
    +     *
    +     * @var array
    +     */
    +    protected $statementColumnTableMap;
    +
    +    /**
    +     * A flattened array to map aliases to statement columns
    +     *
    +     * @var array
    +     */
    +    protected $statementColumnAliasMap;
    +
    +    /**
    +     * List of columns where the COLLATE SQL-instruction has been removed
    +     *
    +     * This list is being populated in case of a PostgreSQL backend only,
    +     * to ensure case-insensitive string comparison in WHERE clauses.
    +     *
    +     * @var array
    +     */
    +    protected $columnsWithoutCollation;
    +
    +    /**
    +     * Create a new DB repository object
    +     *
    +     * In case $this->queryColumns has already been initialized, this initializes
    +     * $this->columnsWithoutCollation in case of a PostgreSQL connection.
    +     *
    +     * @param   DbConnection    $ds     The datasource to use
    +     */
    +    public function __construct(DbConnection $ds)
    +    {
    +        parent::__construct($ds);
    +
    +        $this->columnsWithoutCollation = array();
    +        if ($ds->getDbType() === 'pgsql' && $this->queryColumns !== null) {
    +            $this->queryColumns = $this->removeCollateInstruction($this->queryColumns);
    +        }
    +    }
    +
    +    /**
    +     * Return the query columns being provided
    +     *
    +     * Initializes $this->columnsWithoutCollation in case of a PostgreSQL connection.
    +     *
    +     * @return  array
    +     */
    +    public function getQueryColumns()
    +    {
    +        if ($this->queryColumns === null) {
    +            $this->queryColumns = parent::getQueryColumns();
    +            if ($this->ds->getDbType() === 'pgsql') {
    +                $this->queryColumns = $this->removeCollateInstruction($this->queryColumns);
    +            }
    +        }
    +
    +        return $this->queryColumns;
    +    }
    +
    +    /**
    +     * Return the table aliases to be applied
    +     *
    +     * Calls $this->initializeTableAliases() in case $this->tableAliases is null.
    +     *
    +     * @return  array
    +     */
    +    public function getTableAliases()
    +    {
    +        if ($this->tableAliases === null) {
    +            $this->tableAliases = $this->initializeTableAliases();
    +        }
    +
    +        return $this->tableAliases;
    +    }
    +
    +    /**
    +     * Overwrite this in your repository implementation in case you need to initialize the table aliases lazily
    +     *
    +     * @return  array
    +     */
    +    protected function initializeTableAliases()
    +    {
    +        return array();
    +    }
    +
    +    /**
    +     * Remove each COLLATE SQL-instruction from all given query columns
    +     *
    +     * @param   array   $queryColumns
    +     *
    +     * @return  array                   $queryColumns, the updated version
    +     */
    +    protected function removeCollateInstruction($queryColumns)
    +    {
    +        foreach ($queryColumns as & $columns) {
    +            foreach ($columns as & $column) {
    +                $column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
    +                if ($count > 0) {
    +                    $this->columnsWithoutCollation[] = $column;
    +                }
    +            }
    +        }
    +
    +        return $queryColumns;
    +    }
    +
    +    /**
    +     * Return the given table with the datasource's prefix being prepended
    +     *
    +     * @param   array|string    $table
    +     *
    +     * @return  array|string
    +     *
    +     * @throws  IcingaException         In case $table is not of a supported type
    +     */
    +    protected function prependTablePrefix($table)
    +    {
    +        $prefix = $this->ds->getTablePrefix();
    +        if (! $prefix) {
    +            return $table;
    +        }
    +
    +        if (is_array($table)) {
    +            foreach ($table as & $tableName) {
    +                if (strpos($tableName, $prefix) === false) {
    +                    $tableName = $prefix . $tableName;
    +                }
    +            }
    +        } elseif (is_string($table)) {
    +            $table = (strpos($table, $prefix) === false ? $prefix : '') . $table;
    +        } else {
    +            throw new IcingaException('Table prefix handling for type "%s" is not supported', type($table));
    +        }
    +
    +        return $table;
    +    }
    +
    +    /**
    +     * Remove the datasource's prefix from the given table name and return the remaining part
    +     *
    +     * @param   array|string    $table
    +     *
    +     * @return  array|string
    +     *
    +     * @throws  IcingaException         In case $table is not of a supported type
    +     */
    +    protected function removeTablePrefix($table)
    +    {
    +        $prefix = $this->ds->getTablePrefix();
    +        if (! $prefix) {
    +            return $table;
    +        }
    +
    +        if (is_array($table)) {
    +            foreach ($table as & $tableName) {
    +                if (strpos($tableName, $prefix) === 0) {
    +                    $tableName = str_replace($prefix, '', $tableName);
    +                }
    +            }
    +        } elseif (is_string($table)) {
    +            if (strpos($table, $prefix) === 0) {
    +                $table = str_replace($prefix, '', $table);
    +            }
    +        } else {
    +            throw new IcingaException('Table prefix handling for type "%s" is not supported', type($table));
    +        }
    +
    +        return $table;
    +    }
    +
    +    /**
    +     * Return the given table with its alias being applied
    +     *
    +     * @param   array|string    $table
    +     *
    +     * @return  array|string
    +     */
    +    protected function applyTableAlias($table)
    +    {
    +        $tableAliases = $this->getTableAliases();
    +        if (is_array($table) || !isset($tableAliases[($nonPrefixedTable = $this->removeTablePrefix($table))])) {
    +            return $table;
    +        }
    +
    +        return array($tableAliases[$nonPrefixedTable] => $table);
    +    }
    +
    +    /**
    +     * Return the given table with its alias being cleared
    +     *
    +     * @param   array|string    $table
    +     *
    +     * @return  string
    +     *
    +     * @throws  IcingaException         In case $table is not of a supported type
    +     */
    +    protected function clearTableAlias($table)
    +    {
    +        if (is_string($table)) {
    +            return $table;
    +        }
    +
    +        if (is_array($table)) {
    +            return reset($table);
    +        }
    +
    +        throw new IcingaException('Table alias handling for type "%s" is not supported', type($table));
    +    }
    +
    +    /**
    +     * Insert a table row with the given data
    +     *
    +     * @param   string  $table
    +     * @param   array   $bind
    +     */
    +    public function insert($table, array $bind)
    +    {
    +        $this->ds->insert($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind));
    +    }
    +
    +    /**
    +     * Update table rows with the given data, optionally limited by using a filter
    +     *
    +     * @param   string  $table
    +     * @param   array   $bind
    +     * @param   Filter  $filter
    +     */
    +    public function update($table, array $bind, Filter $filter = null)
    +    {
    +        if ($filter) {
    +            $filter = $this->requireFilter($table, $filter);
    +        }
    +
    +        $this->ds->update($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind), $filter);
    +    }
    +
    +    /**
    +     * Delete table rows, optionally limited by using a filter
    +     *
    +     * @param   string  $table
    +     * @param   Filter  $filter
    +     */
    +    public function delete($table, Filter $filter = null)
    +    {
    +        if ($filter) {
    +            $filter = $this->requireFilter($table, $filter);
    +        }
    +
    +        $this->ds->delete($this->prependTablePrefix($table), $filter);
    +    }
    +
    +    /**
    +     * Return the statement columns being provided
    +     *
    +     * Calls $this->initializeStatementColumns() in case $this->statementColumns is null.
    +     *
    +     * @return  array
    +     */
    +    public function getStatementColumns()
    +    {
    +        if ($this->statementColumns === null) {
    +            $this->statementColumns = $this->initializeStatementColumns();
    +        }
    +
    +        return $this->statementColumns;
    +    }
    +
    +    /**
    +     * Overwrite this in your repository implementation in case you need to initialize the statement columns lazily
    +     *
    +     * @return  array
    +     */
    +    protected function initializeStatementColumns()
    +    {
    +        return array();
    +    }
    +
    +    /**
    +     * Return an array to map table names to statement columns/aliases
    +     *
    +     * @return  array
    +     */
    +    protected function getStatementAliasTableMap()
    +    {
    +        if ($this->statementAliasTableMap === null) {
    +            $this->initializeStatementMaps();
    +        }
    +
    +        return $this->statementAliasTableMap;
    +    }
    +
    +    /**
    +     * Return a flattened array to map statement columns to aliases
    +     *
    +     * @return  array
    +     */
    +    protected function getStatementAliasColumnMap()
    +    {
    +        if ($this->statementAliasColumnMap === null) {
    +            $this->initializeStatementMaps();
    +        }
    +
    +        return $this->statementAliasColumnMap;
    +    }
    +
    +    /**
    +     * Return an array to map table names to statement columns
    +     *
    +     * @return  array
    +     */
    +    protected function getStatementColumnTableMap()
    +    {
    +        if ($this->statementColumnTableMap === null) {
    +            $this->initializeStatementMaps();
    +        }
    +
    +        return $this->statementColumnTableMap;
    +    }
    +
    +    /**
    +     * Return a flattened array to map aliases to statement columns
    +     *
    +     * @return  array
    +     */
    +    protected function getStatementColumnAliasMap()
    +    {
    +        if ($this->statementColumnAliasMap === null) {
    +            $this->initializeStatementMaps();
    +        }
    +
    +        return $this->statementColumnAliasMap;
    +    }
    +
    +    /**
    +     * Initialize $this->statementAliasTableMap and $this->statementAliasColumnMap
    +     */
    +    protected function initializeStatementMaps()
    +    {
    +        $this->statementAliasTableMap = array();
    +        $this->statementAliasColumnMap = array();
    +        $this->statementColumnTableMap = array();
    +        $this->statementColumnAliasMap = array();
    +        foreach ($this->getStatementColumns() as $table => $columns) {
    +            foreach ($columns as $alias => $column) {
    +                $key = is_string($alias) ? $alias : $column;
    +                if (array_key_exists($key, $this->statementAliasTableMap)) {
    +                    if ($this->statementAliasTableMap[$key] !== null) {
    +                        $existingTable = $this->statementAliasTableMap[$key];
    +                        $existingColumn = $this->statementAliasColumnMap[$key];
    +                        $this->statementAliasTableMap[$existingTable . '.' . $key] = $existingTable;
    +                        $this->statementAliasColumnMap[$existingTable . '.' . $key] = $existingColumn;
    +                        $this->statementAliasTableMap[$key] = null;
    +                        $this->statementAliasColumnMap[$key] = null;
    +                    }
    +
    +                    $this->statementAliasTableMap[$table . '.' . $key] = $table;
    +                    $this->statementAliasColumnMap[$table . '.' . $key] = $column;
    +                } else {
    +                    $this->statementAliasTableMap[$key] = $table;
    +                    $this->statementAliasColumnMap[$key] = $column;
    +                }
    +
    +                if (array_key_exists($column, $this->statementColumnTableMap)) {
    +                    if ($this->statementColumnTableMap[$column] !== null) {
    +                        $existingTable = $this->statementColumnTableMap[$column];
    +                        $existingAlias = $this->statementColumnAliasMap[$column];
    +                        $this->statementColumnTableMap[$existingTable . '.' . $column] = $existingTable;
    +                        $this->statementColumnAliasMap[$existingTable . '.' . $column] = $existingAlias;
    +                        $this->statementColumnTableMap[$column] = null;
    +                        $this->statementColumnAliasMap[$column] = null;
    +                    }
    +
    +                    $this->statementColumnTableMap[$table . '.' . $column] = $table;
    +                    $this->statementColumnAliasMap[$table . '.' . $column] = $key;
    +                } else {
    +                    $this->statementColumnTableMap[$column] = $table;
    +                    $this->statementColumnAliasMap[$column] = $key;
    +                }
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Return whether this repository is capable of converting values for the given table and optional column
    +     *
    +     * This does not check whether any conversion for the given table is available if $column is not given, as it
    +     * may be possible that columns from another table where joined in which would otherwise not being converted.
    +     *
    +     * @param   array|string    $table
    +     * @param   string          $column
    +     *
    +     * @return  bool
    +     */
    +    public function providesValueConversion($table, $column = null)
    +    {
    +        if ($column !== null) {
    +            if ($this->validateQueryColumnAssociation($table, $column)) {
    +                return parent::providesValueConversion(
    +                    $this->removeTablePrefix($this->clearTableAlias($table)),
    +                    $column
    +                );
    +            }
    +
    +            if (($tableName = $this->findTableName($column))) {
    +                return parent::providesValueConversion($tableName, $column);
    +            }
    +
    +            return false;
    +        }
    +
    +        $conversionRules = $this->getConversionRules();
    +        return !empty($conversionRules);
    +    }
    +
    +    /**
    +     * Return the name of the conversion method for the given alias or column name and context
    +     *
    +     * If a query column or a filter column, which is part of a query filter, needs to be converted,
    +     * you'll need to pass $query, otherwise the column is considered a statement column.
    +     *
    +     * @param   string              $table      The datasource's table
    +     * @param   string              $name       The alias or column name for which to return a conversion method
    +     * @param   string              $context    The context of the conversion: persist or retrieve
    +     * @param   RepositoryQuery     $query      If given the column is considered a query column,
    +     *                                          statement column otherwise
    +     *
    +     * @return  string
    +     *
    +     * @throws  ProgrammingError    In case a conversion rule is found but not any conversion method
    +     */
    +    protected function getConverter($table, $name, $context, RepositoryQuery $query = null)
    +    {
    +        if (
    +            ($query !== null && $this->validateQueryColumnAssociation($table, $name))
    +            || ($query === null && $this->validateStatementColumnAssociation($table, $name))
    +        ) {
    +            $table = $this->removeTablePrefix($this->clearTableAlias($table));
    +        } else {
    +            $table = $this->findTableName($name);
    +            if (! $table) {
    +                throw new ProgrammingError('Column name validation seems to have failed. Did you require the column?');
    +            }
    +        }
    +
    +        return parent::getConverter($table, $name, $context, $query);
    +    }
    +
    +    /**
    +     * Validate that the requested table exists
    +     *
    +     * This will prepend the datasource's table prefix and will apply the table's alias, if any.
    +     *
    +     * @param   string              $table      The table to validate
    +     * @param   RepositoryQuery     $query      An optional query to pass as context
    +     *                                          (unused by the base implementation)
    +     *
    +     * @return  array|string
    +     *
    +     * @throws  ProgrammingError                In case the given table does not exist
    +     */
    +    public function requireTable($table, RepositoryQuery $query = null)
    +    {
    +        $statementColumns = $this->getStatementColumns();
    +        if (! isset($statementColumns[$table])) {
    +            $table = parent::requireTable($table);
    +        }
    +
    +        return $this->prependTablePrefix($this->applyTableAlias($table));
    +    }
    +
    +    /**
    +     * Recurse the given filter, require each column for the given table and convert all values
    +     *
    +     * In case of a PostgreSQL connection, this applies LOWER() on the column and strtolower()
    +     * on the value if a COLLATE SQL-instruction is part of the resolved column.
    +     *
    +     * @param   string              $table      The table being filtered
    +     * @param   Filter              $filter     The filter to recurse
    +     * @param   RepositoryQuery     $query      An optional query to pass as context
    +     *                                          (Directly passed through to $this->requireFilterColumn)
    +     * @param   bool                $clone      Whether to clone $filter first
    +     *
    +     * @return  Filter                          The udpated filter
    +     */
    +    public function requireFilter($table, Filter $filter, RepositoryQuery $query = null, $clone = true)
    +    {
    +        $filter = parent::requireFilter($table, $filter, $query, $clone);
    +
    +        if ($filter->isExpression()) {
    +            $column = $filter->getColumn();
    +            if (in_array($column, $this->columnsWithoutCollation) && strpos($column, 'LOWER') !== 0) {
    +                $filter->setColumn('LOWER(' . $column . ')');
    +                $expression = $filter->getExpression();
    +                if (is_array($expression)) {
    +                    $filter->setExpression(array_map('strtolower', $expression));
    +                } else {
    +                    $filter->setExpression(strtolower($expression));
    +                }
    +            }
    +        }
    +
    +        return $filter;
    +    }
    +
    +    /**
    +     * Return this repository's query columns of the given table mapped to their respective aliases
    +     *
    +     * @param   array|string    $table
    +     *
    +     * @return  array
    +     *
    +     * @throws  ProgrammingError    In case $table does not exist
    +     */
    +    public function requireAllQueryColumns($table)
    +    {
    +        return parent::requireAllQueryColumns($this->removeTablePrefix($this->clearTableAlias($table)));
    +    }
    +
    +    /**
    +     * Return the query column name for the given alias or null in case the alias does not exist
    +     *
    +     * @param   array|string    $table
    +     * @param   string          $alias
    +     *
    +     * @return  string|null
    +     */
    +    public function resolveQueryColumnAlias($table, $alias)
    +    {
    +        return parent::resolveQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $alias);
    +    }
    +
    +    /**
    +     * Return the alias for the given query column name or null in case the query column name does not exist
    +     *
    +     * @param   array|string    $table
    +     * @param   string          $column
    +     *
    +     * @return  string|null
    +     */
    +    public function reassembleQueryColumnAlias($table, $column)
    +    {
    +        $alias = parent::reassembleQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $column);
    +        if (
    +            $alias === null
    +            && !$this->validateQueryColumnAssociation($table, $column)
    +            && ($tableName = $this->findTableName($column))
    +        ) {
    +            return parent::reassembleQueryColumnAlias($tableName, $column);
    +        }
    +
    +        return $alias;
    +    }
    +
    +    /**
    +     * Return whether the given query column name or alias is available in the given table
    +     *
    +     * @param   array|string    $table
    +     * @param   string          $column
    +     *
    +     * @return  bool
    +     */
    +    public function validateQueryColumnAssociation($table, $column)
    +    {
    +        return parent::validateQueryColumnAssociation(
    +            $this->removeTablePrefix($this->clearTableAlias($table)),
    +            $column
    +        );
    +    }
    +
    +    /**
    +     * Validate that the given column is a valid query target and return it or the actual name if it's an alias
    +     *
    +     * Attempts to join the given column from a different table if its association to the given table cannot be
    +     * verified.
    +     *
    +     * @param   array|string        $table  The table where to look for the column or alias
    +     * @param   string              $name   The name or alias of the column to validate
    +     * @param   RepositoryQuery     $query  An optional query to pass as context,
    +     *                                      if not given no join will be attempted
    +     *
    +     * @return  string                      The given column's name
    +     *
    +     * @throws  QueryException              In case the given column is not a valid query column
    +     */
    +    public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
    +    {
    +        if ($query === null || $this->validateQueryColumnAssociation($table, $name)) {
    +            return parent::requireQueryColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query);
    +        }
    +
    +        return $this->joinColumn($name, $table, $query);
    +    }
    +
    +    /**
    +     * Validate that the given column is a valid filter target and return it or the actual name if it's an alias
    +     *
    +     * Attempts to join the given column from a different table if its association to the given table cannot be
    +     * verified.
    +     *
    +     * @param   array|string        $table  The table where to look for the column or alias
    +     * @param   string              $name   The name or alias of the column to validate
    +     * @param   RepositoryQuery     $query  An optional query to pass as context,
    +     *                                      if not given the column is considered being used for a statement filter
    +     *
    +     * @return  string                      The given column's name
    +     *
    +     * @throws  QueryException              In case the given column is not a valid filter column
    +     */
    +    public function requireFilterColumn($table, $name, RepositoryQuery $query = null)
    +    {
    +        if ($query === null) {
    +            return $this->requireStatementColumn($table, $name);
    +        }
    +
    +        if ($this->validateQueryColumnAssociation($table, $name)) {
    +            return parent::requireFilterColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query);
    +        }
    +
    +        return $this->joinColumn($name, $table, $query);
    +    }
    +
    +    /**
    +     * Return the statement column name for the given alias or null in case the alias does not exist
    +     *
    +     * @param   array|string    $table
    +     * @param   string          $alias
    +     *
    +     * @return  string|null
    +     */
    +    public function resolveStatementColumnAlias($table, $alias)
    +    {
    +        $statementAliasColumnMap = $this->getStatementAliasColumnMap();
    +        if (isset($statementAliasColumnMap[$alias])) {
    +            return $statementAliasColumnMap[$alias];
    +        }
    +
    +        $prefixedAlias = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $alias;
    +        if (isset($statementAliasColumnMap[$prefixedAlias])) {
    +            return $statementAliasColumnMap[$prefixedAlias];
    +        }
    +    }
    +
    +    /**
    +     * Return the alias for the given statement column name or null in case the statement column does not exist
    +     *
    +     * @param   array|string    $table
    +     * @param   string          $column
    +     *
    +     * @return  string|null
    +     */
    +    public function reassembleStatementColumnAlias($table, $column)
    +    {
    +        $statementColumnAliasMap = $this->getStatementColumnAliasMap();
    +        if (isset($statementColumnAliasMap[$column])) {
    +            return $statementColumnAliasMap[$column];
    +        }
    +
    +        $prefixedColumn = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $column;
    +        if (isset($statementColumnAliasMap[$prefixedColumn])) {
    +            return $statementColumnAliasMap[$prefixedColumn];
    +        }
    +    }
    +
    +    /**
    +     * Return whether the given alias or statement column name is available in the given table
    +     *
    +     * @param   array|string    $table
    +     * @param   string          $alias
    +     *
    +     * @return  bool
    +     */
    +    public function validateStatementColumnAssociation($table, $alias)
    +    {
    +        $tableName = $this->removeTablePrefix($this->clearTableAlias($table));
    +
    +        $statementAliasTableMap = $this->getStatementAliasTableMap();
    +        if (isset($statementAliasTableMap[$alias])) {
    +            return $statementAliasTableMap[$alias] === $tableName;
    +        }
    +
    +        $statementColumnTableMap = $this->getStatementColumnTableMap();
    +        if (isset($statementColumnTableMap[$alias])) {
    +            return $statementColumnTableMap[$alias] === $tableName;
    +        }
    +
    +        $prefixedAlias = $tableName . '.' . $alias;
    +        return isset($statementAliasTableMap[$prefixedAlias]) || isset($statementColumnTableMap[$prefixedAlias]);
    +    }
    +
    +    /**
    +     * Return whether the given column name or alias of the given table is a valid statement column
    +     *
    +     * @param   array|string    $table  The table where to look for the column or alias
    +     * @param   string          $name   The column name or alias to check
    +     *
    +     * @return  bool
    +     */
    +    public function hasStatementColumn($table, $name)
    +    {
    +        if (
    +            ($this->resolveStatementColumnAlias($table, $name) === null
    +             && $this->reassembleStatementColumnAlias($table, $name) === null)
    +            || !$this->validateStatementColumnAssociation($table, $name)
    +        ) {
    +            return parent::hasStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name);
    +        }
    +
    +        return true;
    +    }
    +
    +    /**
    +     * Validate that the given column is a valid statement column and return it or the actual name if it's an alias
    +     *
    +     * @param   array|string    $table  The table for which to require the column
    +     * @param   string          $name   The name or alias of the column to validate
    +     *
    +     * @return  string                  The given column's name
    +     *
    +     * @throws  StatementException      In case the given column is not a statement column
    +     */
    +    public function requireStatementColumn($table, $name)
    +    {
    +        if (($column = $this->resolveStatementColumnAlias($table, $name)) !== null) {
    +            $alias = $name;
    +        } elseif (($alias = $this->reassembleStatementColumnAlias($table, $name)) !== null) {
    +            $column = $name;
    +        } else {
    +            return parent::requireStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name);
    +        }
    +
    +        if (! $this->validateStatementColumnAssociation($table, $alias)) {
    +            throw new StatementException(
    +                'Statement column "%s" not found in table "%s"',
    +                $name,
    +                $this->removeTablePrefix($this->clearTableAlias($table))
    +            );
    +        }
    +
    +        return $column;
    +    }
    +
    +    /**
    +     * Join alias or column $name into $table using $query
    +     *
    +     * Attempts to find a valid table for the given alias or column name and a method labelled join
    +     * to process the actual join logic. If neither of those is found, ProgrammingError will be thrown.
    +     * The method is called with the same parameters but in reversed order.
    +     *
    +     * @param   string              $name       The alias or column name to join into $target
    +     * @param   array|string        $target     The table to join $name into
    +     * @param   RepositoryQUery     $query      The query to apply the JOIN-clause on
    +     *
    +     * @return  string                          The resolved alias or $name
    +     *
    +     * @throws  ProgrammingError                In case no valid table or join-method is found
    +     */
    +    public function joinColumn($name, $target, RepositoryQuery $query)
    +    {
    +        $tableName = $this->findTableName($name);
    +        if (! $tableName) {
    +            throw new ProgrammingError(
    +                'Unable to find a valid table for column "%s" to join into "%s"',
    +                $name,
    +                $this->removeTablePrefix($this->clearTableAlias($target))
    +            );
    +        }
    +
    +        if (($column = $this->resolveQueryColumnAlias($tableName, $name)) === null) {
    +            $column = $name;
    +        }
    +
    +        $prefixedTableName = $this->prependTablePrefix($tableName);
    +        if ($query->getQuery()->hasJoinedTable($prefixedTableName)) {
    +            return $column;
    +        }
    +
    +        $joinMethod = 'join' . String::cname($tableName);
    +        if (! method_exists($this, $joinMethod)) {
    +            throw new ProgrammingError(
    +                'Unable to join table "%s" into "%s". Method "%s" not found',
    +                $tableName,
    +                $this->removeTablePrefix($this->clearTableAlias($target)),
    +                $joinMethod
    +            );
    +        }
    +
    +        $this->$joinMethod($query, $target, $name);
    +        return $column;
    +    }
    +
    +    /**
    +     * Return the table name for the given alias or column name
    +     *
    +     * @param   string  $column
    +     *
    +     * @return  string|null         null in case no table is found
    +     */
    +    protected function findTableName($column)
    +    {
    +        $aliasTableMap = $this->getAliasTableMap();
    +        if (isset($aliasTableMap[$column])) {
    +            return $aliasTableMap[$column];
    +        }
    +
    +        $columnTableMap = $this->getColumnTableMap();
    +        if (isset($columnTableMap[$column])) {
    +            return $columnTableMap[$column];
    +        }
    +
    +        // TODO(jom): Elaborate whether it makes sense to throw ProgrammingError
    +        //            instead (duplicate aliases in different tables?)
    +        foreach ($aliasTableMap as $prefixedAlias => $table) {
    +            if (strpos($prefixedAlias, '.') !== false) {
    +                list($_, $alias) = explode('.', $prefixedAlias, 2);
    +                if ($alias === $column) {
    +                    return $table;
    +                }
    +            }
    +        }
    +    }
    +}
    diff --git a/library/Icinga/Repository/IniRepository.php b/library/Icinga/Repository/IniRepository.php
    new file mode 100644
    index 000000000..3c73464e5
    --- /dev/null
    +++ b/library/Icinga/Repository/IniRepository.php
    @@ -0,0 +1,186 @@
    +
    + *  
  • Insert, update and delete capabilities
  • + * + */ +abstract class IniRepository extends Repository implements Extensible, Updatable, Reducible +{ + /** + * The datasource being used + * + * @var Config + */ + protected $ds; + + /** + * Create a new INI repository object + * + * @param Config $ds The data source to use + * + * @throws ProgrammingError In case the given data source does not provide a valid key column + */ + public function __construct(Config $ds) + { + parent::__construct($ds); // First! Due to init(). + + if (! $ds->getConfigObject()->getKeyColumn()) { + throw new ProgrammingError('INI repositories require their data source to provide a valid key column'); + } + } + + /** + * Insert the given data for the given target + * + * $data must provide a proper value for the data source's key column. + * + * @param string $target + * @param array $data + * + * @throws StatementException In case the operation has failed + */ + public function insert($target, array $data) + { + $newData = $this->requireStatementColumns($target, $data); + $section = $this->extractSectionName($newData); + + if ($this->ds->hasSection($section)) { + throw new StatementException(t('Cannot insert. Section "%s" does already exist'), $section); + } + + $this->ds->setSection($section, $newData); + + try { + $this->ds->saveIni(); + } catch (Exception $e) { + throw new StatementException(t('Failed to insert. An error occurred: %s'), $e->getMessage()); + } + } + + /** + * Update the target with the given data and optionally limit the affected entries by using a filter + * + * @param string $target + * @param array $data + * @param Filter $filter + * + * @throws StatementException In case the operation has failed + */ + public function update($target, array $data, Filter $filter = null) + { + $newData = $this->requireStatementColumns($target, $data); + $keyColumn = $this->ds->getConfigObject()->getKeyColumn(); + if ($filter === null && isset($newData[$keyColumn])) { + throw new StatementException( + t('Cannot update. Column "%s" holds a section\'s name which must be unique'), + $keyColumn + ); + } + + if ($filter !== null) { + $filter = $this->requireFilter($target, $filter); + } + + $newSection = null; + foreach (iterator_to_array($this->ds) as $section => $config) { + if ($filter !== null && !$filter->matches($config)) { + continue; + } + + if ($newSection !== null) { + throw new StatementException( + t('Cannot update. Column "%s" holds a section\'s name which must be unique'), + $keyColumn + ); + } + + foreach ($newData as $column => $value) { + if ($column === $keyColumn) { + $newSection = $value; + } else { + $config->$column = $value; + } + } + + if ($newSection) { + if ($this->ds->hasSection($newSection)) { + throw new StatementException(t('Cannot update. Section "%s" does already exist'), $newSection); + } + + $this->ds->removeSection($section)->setSection($newSection, $config); + } else { + $this->ds->setSection($section, $config); + } + } + + try { + $this->ds->saveIni(); + } catch (Exception $e) { + throw new StatementException(t('Failed to update. An error occurred: %s'), $e->getMessage()); + } + } + + /** + * Delete entries in the given target, optionally limiting the affected entries by using a filter + * + * @param string $target + * @param Filter $filter + * + * @throws StatementException In case the operation has failed + */ + public function delete($target, Filter $filter = null) + { + if ($filter !== null) { + $filter = $this->requireFilter($target, $filter); + } + + foreach (iterator_to_array($this->ds) as $section => $config) { + if ($filter === null || $filter->matches($config)) { + $this->ds->removeSection($section); + } + } + + try { + $this->ds->saveIni(); + } catch (Exception $e) { + throw new StatementException(t('Failed to delete. An error occurred: %s'), $e->getMessage()); + } + } + + /** + * Extract and return the section name off of the given $data + * + * @param array $data + * + * @return string + * + * @throws ProgrammingError In case no valid section name is available + */ + protected function extractSectionName(array & $data) + { + $keyColumn = $this->ds->getConfigObject()->getKeyColumn(); + if (! isset($data[$keyColumn])) { + throw new ProgrammingError('$data does not provide a value for key column "%s"', $keyColumn); + } + + $section = $data[$keyColumn]; + unset($data[$keyColumn]); + return $section; + } +} diff --git a/library/Icinga/Repository/LdapRepository.php b/library/Icinga/Repository/LdapRepository.php new file mode 100644 index 000000000..ac210516d --- /dev/null +++ b/library/Icinga/Repository/LdapRepository.php @@ -0,0 +1,66 @@ + + *
  • Attribute name normalization
  • + * + */ +abstract class LdapRepository extends Repository +{ + /** + * The datasource being used + * + * @var LdapConnection + */ + protected $ds; + + /** + * Normed attribute names based on known LDAP environments + * + * @var array + */ + protected $normedAttributes = array( + 'uid' => 'uid', + 'gid' => 'gid', + 'user' => 'user', + 'group' => 'group', + 'member' => 'member', + 'inetorgperson' => 'inetOrgPerson', + 'samaccountname' => 'sAMAccountName' + ); + + /** + * Create a new LDAP repository object + * + * @param LdapConnection $ds The data source to use + */ + public function __construct(LdapConnection $ds) + { + parent::__construct($ds); + } + + /** + * Return the given attribute name normed to known LDAP enviroments, if possible + * + * @param string $name + * + * @return string + */ + protected function getNormedAttribute($name) + { + $loweredName = strtolower($name); + if (array_key_exists($loweredName, $this->normedAttributes)) { + return $this->normedAttributes[$loweredName]; + } + + return $name; + } +} \ No newline at end of file diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php new file mode 100644 index 000000000..7ca12a43d --- /dev/null +++ b/library/Icinga/Repository/Repository.php @@ -0,0 +1,1016 @@ + + *
  • Concrete implementations need to initialize Repository::$queryColumns
  • + *
  • The datasource passed to a repository must implement the Selectable interface
  • + *
  • The datasource must yield an instance of Queryable when its select() method is called
  • + * + */ +abstract class Repository implements Selectable +{ + /** + * The format to use when converting values of type date_time + */ + const DATETIME_FORMAT = 'd/m/Y g:i A'; + + /** + * The name of this repository + * + * @var string + */ + protected $name; + + /** + * The datasource being used + * + * @var Selectable + */ + protected $ds; + + /** + * The base table name this repository is responsible for + * + * This will be automatically set to the first key of $queryColumns if not explicitly set. + * + * @var string + */ + protected $baseTable; + + /** + * The query columns being provided + * + * This must be initialized by concrete repository implementations, in the following format + *
    
    +     *  array(
    +     *      'baseTable' => array(
    +     *          'column1',
    +     *          'alias1' => 'column2',
    +     *          'alias2' => 'column3'
    +     *      )
    +     *  )
    +     * 
    
    +     *
    +     * @var array
    +     */
    +    protected $queryColumns;
    +
    +    /**
    +     * The columns (or aliases) which are not permitted to be queried. (by design)
    +     *
    +     * @var array   An array of strings
    +     */
    +    protected $filterColumns;
    +
    +    /**
    +     * The sort rules to be applied on a query
    +     *
    +     * This may be initialized by concrete repository implementations, in the following format
    +     * 
    
    +     *  array(
    +     *      'alias_or_column_name' => array(
    +     *          'order'     => 'asc'
    +     *      ),
    +     *      'alias_or_column_name' => array(
    +     *          'columns'   => array(
    +     *              'once_more_the_alias_or_column_name_as_in_the_parent_key',
    +     *              'an_additional_alias_or_column_name_with_a_specific_direction asc'
    +     *          ),
    +     *          'order'     => 'desc'
    +     *      ),
    +     *      'alias_or_column_name' => array(
    +     *          'columns'   => array('a_different_alias_or_column_name_designated_to_act_as_the_only_sort_column')
    +     *          // Ascendant sort by default
    +     *      )
    +     *  )
    +     * 
    
    +     * Note that it's mandatory to supply the alias name in case there is one.
    +     *
    +     * @var array
    +     */
    +    protected $sortRules;
    +
    +    /**
    +     * The value conversion rules to apply on a query or statement
    +     *
    +     * This may be initialized by concrete repository implementations and describes for which aliases or column
    +     * names what type of conversion is available. For entries, where the key is the alias/column and the value
    +     * is the type identifier, the repository attempts to find a conversion method for the alias/column first and,
    +     * if none is found, then for the type. If an entry only provides a value, which is the alias/column, the
    +     * repository only attempts to find a conversion method for the alias/column. The name of a conversion method
    +     * is expected to be declared using lowerCamelCase. (e.g. user_name will be translated to persistUserName and
    +     * groupname will be translated to retrieveGroupname)
    +     *
    +     * @var array
    +     */
    +    protected $conversionRules;
    +
    +    /**
    +     * An array to map table names to aliases
    +     *
    +     * @var array
    +     */
    +    protected $aliasTableMap;
    +
    +    /**
    +     * A flattened array to map query columns to aliases
    +     *
    +     * @var array
    +     */
    +    protected $aliasColumnMap;
    +
    +    /**
    +     * An array to map table names to query columns
    +     *
    +     * @var array
    +     */
    +    protected $columnTableMap;
    +
    +    /**
    +     * A flattened array to map aliases to query columns
    +     *
    +     * @var array
    +     */
    +    protected $columnAliasMap;
    +
    +    /**
    +     * Create a new repository object
    +     *
    +     * @param   Selectable  $ds     The datasource to use
    +     */
    +    public function __construct(Selectable $ds)
    +    {
    +        $this->ds = $ds;
    +        $this->aliasTableMap = array();
    +        $this->aliasColumnMap = array();
    +        $this->columnTableMap = array();
    +        $this->columnAliasMap = array();
    +
    +        $this->init();
    +    }
    +
    +    /**
    +     * Initialize this repository
    +     *
    +     * Supposed to be overwritten by concrete repository implementations.
    +     */
    +    protected function init()
    +    {
    +
    +    }
    +
    +    /**
    +     * Set this repository's name
    +     *
    +     * @param   string  $name
    +     *
    +     * @return  $this
    +     */
    +    public function setName($name)
    +    {
    +        $this->name = $name;
    +        return $this;
    +    }
    +
    +    /**
    +     * Return this repository's name
    +     *
    +     * In case no name has been explicitly set yet, the class name is returned.
    +     *
    +     * @return  string
    +     */
    +    public function getName()
    +    {
    +        return $this->name ?: __CLASS__;
    +    }
    +
    +    /**
    +     * Return the datasource being used
    +     *
    +     * @return  Selectable
    +     */
    +    public function getDataSource()
    +    {
    +        return $this->ds;
    +    }
    +
    +    /**
    +     * Return the base table name this repository is responsible for
    +     *
    +     * @return  string
    +     *
    +     * @throws  ProgrammingError    In case no base table name has been set and
    +     *                               $this->queryColumns does not provide one either
    +     */
    +    public function getBaseTable()
    +    {
    +        if ($this->baseTable === null) {
    +            $queryColumns = $this->getQueryColumns();
    +            reset($queryColumns);
    +            $this->baseTable = key($queryColumns);
    +            if (is_int($this->baseTable) || !is_array($queryColumns[$this->baseTable])) {
    +                throw new ProgrammingError('"%s" is not a valid base table', $this->baseTable);
    +            }
    +        }
    +
    +        return $this->baseTable;
    +    }
    +
    +    /**
    +     * Return the query columns being provided
    +     *
    +     * Calls $this->initializeQueryColumns() in case $this->queryColumns is null.
    +     *
    +     * @return  array
    +     */
    +    public function getQueryColumns()
    +    {
    +        if ($this->queryColumns === null) {
    +            $this->queryColumns = $this->initializeQueryColumns();
    +        }
    +
    +        return $this->queryColumns;
    +    }
    +
    +    /**
    +     * Overwrite this in your repository implementation in case you need to initialize the query columns lazily
    +     *
    +     * @return  array
    +     */
    +    protected function initializeQueryColumns()
    +    {
    +        return array();
    +    }
    +
    +    /**
    +     * Return the columns (or aliases) which are not permitted to be queried
    +     *
    +     * Calls $this->initializeFilterColumns() in case $this->filterColumns is null.
    +     *
    +     * @return  array
    +     */
    +    public function getFilterColumns()
    +    {
    +        if ($this->filterColumns === null) {
    +            $this->filterColumns = $this->initializeFilterColumns();
    +        }
    +
    +        return $this->filterColumns;
    +    }
    +
    +    /**
    +     * Overwrite this in your repository implementation in case you need to initialize the filter columns lazily
    +     *
    +     * @return  array
    +     */
    +    protected function initializeFilterColumns()
    +    {
    +        return array();
    +    }
    +
    +    /**
    +     * Return the sort rules to be applied on a query
    +     *
    +     * Calls $this->initializeSortRules() in case $this->sortRules is null.
    +     *
    +     * @return  array
    +     */
    +    public function getSortRules()
    +    {
    +        if ($this->sortRules === null) {
    +            $this->sortRules = $this->initializeSortRules();
    +        }
    +
    +        return $this->sortRules;
    +    }
    +
    +    /**
    +     * Overwrite this in your repository implementation in case you need to initialize the sort rules lazily
    +     *
    +     * @return  array
    +     */
    +    protected function initializeSortRules()
    +    {
    +        return array();
    +    }
    +
    +    /**
    +     * Return the value conversion rules to apply on a query
    +     *
    +     * Calls $this->initializeConversionRules() in case $this->conversionRules is null.
    +     *
    +     * @return  array
    +     */
    +    public function getConversionRules()
    +    {
    +        if ($this->conversionRules === null) {
    +            $this->conversionRules = $this->initializeConversionRules();
    +        }
    +
    +        return $this->conversionRules;
    +    }
    +
    +    /**
    +     * Overwrite this in your repository implementation in case you need to initialize the conversion rules lazily
    +     *
    +     * @return  array
    +     */
    +    protected function initializeConversionRules()
    +    {
    +        return array();
    +    }
    +
    +    /**
    +     * Return an array to map table names to aliases
    +     *
    +     * @return  array
    +     */
    +    protected function getAliasTableMap()
    +    {
    +        if (empty($this->aliasTableMap)) {
    +            $this->initializeAliasMaps();
    +        }
    +
    +        return $this->aliasTableMap;
    +    }
    +
    +    /**
    +     * Return a flattened array to map query columns to aliases
    +     *
    +     * @return  array
    +     */
    +    protected function getAliasColumnMap()
    +    {
    +        if (empty($this->aliasColumnMap)) {
    +            $this->initializeAliasMaps();
    +        }
    +
    +        return $this->aliasColumnMap;
    +    }
    +
    +    /**
    +     * Return an array to map table names to query columns
    +     *
    +     * @return  array
    +     */
    +    protected function getColumnTableMap()
    +    {
    +        if (empty($this->columnTableMap)) {
    +            $this->initializeAliasMaps();
    +        }
    +
    +        return $this->columnTableMap;
    +    }
    +
    +    /**
    +     * Return a flattened array to map aliases to query columns
    +     *
    +     * @return  array
    +     */
    +    protected function getColumnAliasMap()
    +    {
    +        if (empty($this->columnAliasMap)) {
    +            $this->initializeAliasMaps();
    +        }
    +
    +        return $this->columnAliasMap;
    +    }
    +
    +    /**
    +     * Initialize $this->aliasTableMap and $this->aliasColumnMap
    +     *
    +     * @throws  ProgrammingError    In case $this->queryColumns does not provide any column information
    +     */
    +    protected function initializeAliasMaps()
    +    {
    +        $queryColumns = $this->getQueryColumns();
    +        if (empty($queryColumns)) {
    +            throw new ProgrammingError('Repositories are required to initialize $this->queryColumns first');
    +        }
    +
    +        foreach ($queryColumns as $table => $columns) {
    +            foreach ($columns as $alias => $column) {
    +                if (! is_string($alias)) {
    +                    $key = $column;
    +                } else {
    +                    $key = $alias;
    +                    $column = preg_replace('~\n\s*~', ' ', $column);
    +                }
    +
    +                if (array_key_exists($key, $this->aliasTableMap)) {
    +                    if ($this->aliasTableMap[$key] !== null) {
    +                        $existingTable = $this->aliasTableMap[$key];
    +                        $existingColumn = $this->aliasColumnMap[$key];
    +                        $this->aliasTableMap[$existingTable . '.' . $key] = $existingTable;
    +                        $this->aliasColumnMap[$existingTable . '.' . $key] = $existingColumn;
    +                        $this->aliasTableMap[$key] = null;
    +                        $this->aliasColumnMap[$key] = null;
    +                    }
    +
    +                    $this->aliasTableMap[$table . '.' . $key] = $table;
    +                    $this->aliasColumnMap[$table . '.' . $key] = $column;
    +                } else {
    +                    $this->aliasTableMap[$key] = $table;
    +                    $this->aliasColumnMap[$key] = $column;
    +                }
    +
    +                if (array_key_exists($column, $this->columnTableMap)) {
    +                    if ($this->columnTableMap[$column] !== null) {
    +                        $existingTable = $this->columnTableMap[$column];
    +                        $existingAlias = $this->columnAliasMap[$column];
    +                        $this->columnTableMap[$existingTable . '.' . $column] = $existingTable;
    +                        $this->columnAliasMap[$existingTable . '.' . $column] = $existingAlias;
    +                        $this->columnTableMap[$column] = null;
    +                        $this->columnAliasMap[$column] = null;
    +                    }
    +
    +                    $this->columnTableMap[$table . '.' . $column] = $table;
    +                    $this->columnAliasMap[$table . '.' . $column] = $key;
    +                } else {
    +                    $this->columnTableMap[$column] = $table;
    +                    $this->columnAliasMap[$column] = $key;
    +                }
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Return a new query for the given columns
    +     *
    +     * @param   array   $columns    The desired columns, if null all columns will be queried
    +     *
    +     * @return  RepositoryQuery
    +     */
    +    public function select(array $columns = null)
    +    {
    +        $query = new RepositoryQuery($this);
    +        $query->from($this->getBaseTable(), $columns);
    +        return $query;
    +    }
    +
    +    /**
    +     * Return whether this repository is capable of converting values for the given table and optional column
    +     *
    +     * @param   string  $table
    +     * @param   string  $column
    +     *
    +     * @return  bool
    +     */
    +    public function providesValueConversion($table, $column = null)
    +    {
    +        $conversionRules = $this->getConversionRules();
    +        if (empty($conversionRules)) {
    +            return false;
    +        }
    +
    +        if (! isset($conversionRules[$table])) {
    +            return false;
    +        } elseif ($column === null) {
    +            return true;
    +        }
    +
    +        $alias = $this->reassembleQueryColumnAlias($table, $column) ?: $column;
    +        return array_key_exists($alias, $conversionRules[$table]) || in_array($alias, $conversionRules[$table]);
    +    }
    +
    +    /**
    +     * Convert a value supposed to be transmitted to the data source
    +     *
    +     * @param   string              $table      The table where to persist the value
    +     * @param   string              $name       The alias or column name
    +     * @param   mixed               $value      The value to convert
    +     * @param   RepositoryQuery     $query      An optional query to pass as context
    +     *                                          (Directly passed through to $this->getConverter)
    +     *
    +     * @return  mixed                           If conversion was possible, the converted value,
    +     *                                          otherwise the unchanged value
    +     */
    +    public function persistColumn($table, $name, $value, RepositoryQuery $query = null)
    +    {
    +        $converter = $this->getConverter($table, $name, 'persist', $query);
    +        if ($converter !== null) {
    +            $value = $this->$converter($value);
    +        }
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * Convert a value which was fetched from the data source
    +     *
    +     * @param   string              $table      The table the value has been fetched from
    +     * @param   string              $name       The alias or column name
    +     * @param   mixed               $value      The value to convert
    +     * @param   RepositoryQuery     $query      An optional query to pass as context
    +     *                                          (Directly passed through to $this->getConverter)
    +     *
    +     * @return  mixed                           If conversion was possible, the converted value,
    +     *                                          otherwise the unchanged value
    +     */
    +    public function retrieveColumn($table, $name, $value, RepositoryQuery $query = null)
    +    {
    +        $converter = $this->getConverter($table, $name, 'retrieve', $query);
    +        if ($converter !== null) {
    +            $value = $this->$converter($value);
    +        }
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * Return the name of the conversion method for the given alias or column name and context
    +     *
    +     * @param   string              $table      The datasource's table
    +     * @param   string              $name       The alias or column name for which to return a conversion method
    +     * @param   string              $context    The context of the conversion: persist or retrieve
    +     * @param   RepositoryQuery     $query      An optional query to pass as context
    +     *                                          (unused by the base implementation)
    +     *
    +     * @return  string
    +     *
    +     * @throws  ProgrammingError    In case a conversion rule is found but not any conversion method
    +     */
    +    protected function getConverter($table, $name, $context, RepositoryQuery $query = null)
    +    {
    +        $conversionRules = $this->getConversionRules();
    +        if (! isset($conversionRules[$table])) {
    +            return;
    +        }
    +
    +        $tableRules = $conversionRules[$table];
    +        if (($alias = $this->reassembleQueryColumnAlias($table, $name)) === null) {
    +            $alias = $name;
    +        }
    +
    +        // Check for a conversion method for the alias/column first
    +        if (array_key_exists($alias, $tableRules) || in_array($alias, $tableRules)) {
    +            $methodName = $context . join('', array_map('ucfirst', explode('_', $alias)));
    +            if (method_exists($this, $methodName)) {
    +                return $methodName;
    +            }
    +        }
    +
    +        // The conversion method for the type is just a fallback, but it is required to exist if defined
    +        if (isset($tableRules[$alias])) {
    +            $identifier = join('', array_map('ucfirst', explode('_', $tableRules[$alias])));
    +            if (! method_exists($this, $context . $identifier)) {
    +                // Do not throw an error in case at least one conversion method exists
    +                if (! method_exists($this, ($context === 'persist' ? 'retrieve' : 'persist') . $identifier)) {
    +                    throw new ProgrammingError(
    +                        'Cannot find any conversion method for type "%s"'
    +                        . '. Add a proper conversion method or remove the type definition',
    +                        $tableRules[$alias]
    +                    );
    +                }
    +
    +                Logger::debug(
    +                    'Conversion method "%s" for type definition "%s" does not exist in repository "%s".',
    +                    $context . $identifier,
    +                    $tableRules[$alias],
    +                    $this->getName()
    +                );
    +            } else {
    +                return $context . $identifier;
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Convert a timestamp or DateTime object to a string formatted using static::DATETIME_FORMAT
    +     *
    +     * @param   mixed   $value
    +     *
    +     * @return  string
    +     */
    +    protected function persistDateTime($value)
    +    {
    +        if (is_numeric($value)) {
    +            $value = date(static::DATETIME_FORMAT, $value);
    +        } elseif ($value instanceof DateTime) {
    +            $value = date(static::DATETIME_FORMAT, $value->getTimestamp()); // Using date here, to ignore any timezone
    +        } elseif ($value !== null) {
    +            throw new ProgrammingError(
    +                'Cannot persist value "%s" as type date_time. It\'s not a timestamp or DateTime object',
    +                $value
    +            );
    +        }
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * Convert a string formatted using static::DATETIME_FORMAT to a unix timestamp
    +     *
    +     * @param   string  $value
    +     *
    +     * @return  int
    +     */
    +    protected function retrieveDateTime($value)
    +    {
    +        if (is_numeric($value)) {
    +            $value = (int) $value;
    +        } elseif (is_string($value)) {
    +            $dateTime = DateTime::createFromFormat(static::DATETIME_FORMAT, $value);
    +            if ($dateTime === false) {
    +                Logger::debug(
    +                    'Unable to parse string "%s" as type date_time with format "%s" in repository "%s"',
    +                    $value,
    +                    static::DATETIME_FORMAT,
    +                    $this->getName()
    +                );
    +                $value = null;
    +            } else {
    +                $value = $dateTime->getTimestamp();
    +            }
    +        } elseif ($value !== null) {
    +            throw new ProgrammingError(
    +                'Cannot retrieve value "%s" as type date_time. It\'s not a integer or (numeric) string',
    +                $value
    +            );
    +        }
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * Convert the given array to an comma separated string
    +     *
    +     * @param   array|string    $value
    +     *
    +     * @return  string
    +     */
    +    protected function persistCommaSeparatedString($value)
    +    {
    +        if (is_array($value)) {
    +            $value = join(',', array_map('trim', $value));
    +        } elseif ($value !== null && !is_string($value)) {
    +            throw new ProgrammingError('Cannot persist value "%s" as comma separated string', $value);
    +        }
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * Convert the given comma separated string to an array
    +     *
    +     * @param   string  $value
    +     *
    +     * @return  array
    +     */
    +    protected function retrieveCommaSeparatedString($value)
    +    {
    +        if ($value && is_string($value)) {
    +            $value = String::trimSplit($value);
    +        } elseif ($value !== null) {
    +            throw new ProgrammingError('Cannot retrieve value "%s" as array. It\'s not a string', $value);
    +        }
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * Parse the given value based on the ASN.1 standard (GeneralizedTime) and return its timestamp representation
    +     *
    +     * @param   string|null     $value
    +     *
    +     * @return  int
    +     */
    +    protected function retrieveGeneralizedTime($value)
    +    {
    +        if ($value === null) {
    +            return $value;
    +        }
    +
    +        if (
    +            ($dateTime = DateTime::createFromFormat('YmdHis.uO', $value)) !== false
    +            || ($dateTime = DateTime::createFromFormat('YmdHis.uZ', $value)) !== false
    +            || ($dateTime = DateTime::createFromFormat('YmdHis.u', $value)) !== false
    +            || ($dateTime = DateTime::createFromFormat('YmdHis', $value)) !== false
    +            || ($dateTime = DateTime::createFromFormat('YmdHi', $value)) !== false
    +            || ($dateTime = DateTime::createFromFormat('YmdH', $value)) !== false
    +        ) {
    +            return $dateTime->getTimeStamp();
    +        } else {
    +            Logger::debug(sprintf(
    +                'Failed to parse "%s" based on the ASN.1 standard (GeneralizedTime) in repository "%s".',
    +                $value,
    +                $this->getName()
    +            ));
    +        }
    +    }
    +
    +    /**
    +     * Validate that the requested table exists
    +     *
    +     * @param   string              $table      The table to validate
    +     * @param   RepositoryQuery     $query      An optional query to pass as context
    +     *                                          (unused by the base implementation)
    +     *
    +     * @return  string                          The table's name, may differ from the given one
    +     *
    +     * @throws  ProgrammingError                In case the given table does not exist
    +     */
    +    public function requireTable($table, RepositoryQuery $query = null)
    +    {
    +        $queryColumns = $this->getQueryColumns();
    +        if (! isset($queryColumns[$table])) {
    +            throw new ProgrammingError('Table "%s" not found', $table);
    +        }
    +
    +        return $table;
    +    }
    +
    +    /**
    +     * Recurse the given filter, require each column for the given table and convert all values
    +     *
    +     * @param   string              $table      The table being filtered
    +     * @param   Filter              $filter     The filter to recurse
    +     * @param   RepositoryQuery     $query      An optional query to pass as context
    +     *                                          (Directly passed through to $this->requireFilterColumn)
    +     * @param   bool                $clone      Whether to clone $filter first
    +     *
    +     * @return  Filter                          The udpated filter
    +     */
    +    public function requireFilter($table, Filter $filter, RepositoryQuery $query = null, $clone = true)
    +    {
    +        if ($clone) {
    +            $filter = clone $filter;
    +        }
    +
    +        if ($filter->isExpression()) {
    +            $column = $filter->getColumn();
    +            $filter->setColumn($this->requireFilterColumn($table, $column, $query));
    +            $filter->setExpression($this->persistColumn($table, $column, $filter->getExpression()));
    +        } elseif ($filter->isChain()) {
    +            foreach ($filter->filters() as $chainOrExpression) {
    +                $this->requireFilter($table, $chainOrExpression, $query, false);
    +            }
    +        }
    +
    +        return $filter;
    +    }
    +
    +    /**
    +     * Return this repository's query columns of the given table mapped to their respective aliases
    +     *
    +     * @param   string  $table
    +     *
    +     * @return  array
    +     *
    +     * @throws  ProgrammingError    In case $table does not exist
    +     */
    +    public function requireAllQueryColumns($table)
    +    {
    +        $queryColumns = $this->getQueryColumns();
    +        if (! array_key_exists($table, $queryColumns)) {
    +            throw new ProgrammingError('Table name "%s" not found', $table);
    +        }
    +
    +        $filterColumns = $this->getFilterColumns();
    +        $columns = array();
    +        foreach ($queryColumns[$table] as $alias => $column) {
    +            if (! in_array(is_string($alias) ? $alias : $column, $filterColumns)) {
    +                $columns[$alias] = $column;
    +            }
    +        }
    +
    +        return $columns;
    +    }
    +
    +    /**
    +     * Return the query column name for the given alias or null in case the alias does not exist
    +     *
    +     * @param   string  $table
    +     * @param   string  $alias
    +     *
    +     * @return  string|null
    +     */
    +    public function resolveQueryColumnAlias($table, $alias)
    +    {
    +        $aliasColumnMap = $this->getAliasColumnMap();
    +        if (isset($aliasColumnMap[$alias])) {
    +            return $aliasColumnMap[$alias];
    +        }
    +
    +        $prefixedAlias = $table . '.' . $alias;
    +        if (isset($aliasColumnMap[$prefixedAlias])) {
    +            return $aliasColumnMap[$prefixedAlias];
    +        }
    +    }
    +
    +    /**
    +     * Return the alias for the given query column name or null in case the query column name does not exist
    +     *
    +     * @param   string  $table
    +     * @param   string  $column
    +     *
    +     * @return  string|null
    +     */
    +    public function reassembleQueryColumnAlias($table, $column)
    +    {
    +        $columnAliasMap = $this->getColumnAliasMap();
    +        if (isset($columnAliasMap[$column])) {
    +            return $columnAliasMap[$column];
    +        }
    +
    +        $prefixedColumn = $table . '.' . $column;
    +        if (isset($columnAliasMap[$prefixedColumn])) {
    +            return $columnAliasMap[$prefixedColumn];
    +        }
    +    }
    +
    +    /**
    +     * Return whether the given alias or query column name is available in the given table
    +     *
    +     * @param   string  $table
    +     * @param   string  $alias
    +     *
    +     * @return  bool
    +     */
    +    public function validateQueryColumnAssociation($table, $alias)
    +    {
    +        $aliasTableMap = $this->getAliasTableMap();
    +        if (isset($aliasTableMap[$alias])) {
    +            return $aliasTableMap[$alias] === $table;
    +        }
    +
    +        $columnTableMap = $this->getColumnTableMap();
    +        if (isset($columnTableMap[$alias])) {
    +            return $columnTableMap[$alias] === $table;
    +        }
    +
    +        $prefixedAlias = $table . '.' . $alias;
    +        return isset($aliasTableMap[$prefixedAlias]) || isset($columnTableMap[$prefixedAlias]);
    +    }
    +
    +    /**
    +     * Return whether the given column name or alias is a valid query column
    +     *
    +     * @param   string  $table  The table where to look for the column or alias
    +     * @param   string  $name   The column name or alias to check
    +     *
    +     * @return  bool
    +     */
    +    public function hasQueryColumn($table, $name)
    +    {
    +        if ($this->resolveQueryColumnAlias($table, $name) !== null) {
    +            $alias = $name;
    +        } elseif (($alias = $this->reassembleQueryColumnAlias($table, $name)) === null) {
    +            return false;
    +        }
    +
    +        return !in_array($alias, $this->getFilterColumns()) && $this->validateQueryColumnAssociation($table, $name);
    +    }
    +
    +    /**
    +     * Validate that the given column is a valid query target and return it or the actual name if it's an alias
    +     *
    +     * @param   string              $table  The table where to look for the column or alias
    +     * @param   string              $name   The name or alias of the column to validate
    +     * @param   RepositoryQuery     $query  An optional query to pass as context (unused by the base implementation)
    +     *
    +     * @return  string                      The given column's name
    +     *
    +     * @throws  QueryException              In case the given column is not a valid query column
    +     */
    +    public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
    +    {
    +        if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) {
    +            $alias = $name;
    +        } elseif (($alias = $this->reassembleQueryColumnAlias($table, $name)) !== null) {
    +            $column = $name;
    +        } else {
    +            throw new QueryException(t('Query column "%s" not found'), $name);
    +        }
    +
    +        if (in_array($alias, $this->getFilterColumns())) {
    +            throw new QueryException(t('Filter column "%s" cannot be queried'), $name);
    +        }
    +
    +        if (! $this->validateQueryColumnAssociation($table, $alias)) {
    +            throw new QueryException(t('Query column "%s" not found in table "%s"'), $name, $table);
    +        }
    +
    +        return $column;
    +    }
    +
    +    /**
    +     * Return whether the given column name or alias is a valid filter column
    +     *
    +     * @param   string  $table  The table where to look for the column or alias
    +     * @param   string  $name   The column name or alias to check
    +     *
    +     * @return  bool
    +     */
    +    public function hasFilterColumn($table, $name)
    +    {
    +        return ($this->resolveQueryColumnAlias($table, $name) !== null
    +            || $this->reassembleQueryColumnAlias($table, $name) !== null)
    +            && $this->validateQueryColumnAssociation($table, $name);
    +    }
    +
    +    /**
    +     * Validate that the given column is a valid filter target and return it or the actual name if it's an alias
    +     *
    +     * @param   string              $table  The table where to look for the column or alias
    +     * @param   string              $name   The name or alias of the column to validate
    +     * @param   RepositoryQuery     $query  An optional query to pass as context (unused by the base implementation)
    +     *
    +     * @return  string                      The given column's name
    +     *
    +     * @throws  QueryException              In case the given column is not a valid filter column
    +     */
    +    public function requireFilterColumn($table, $name, RepositoryQuery $query = null)
    +    {
    +        if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) {
    +            $alias = $name;
    +        } elseif (($alias = $this->reassembleQueryColumnAlias($table, $name)) !== null) {
    +            $column = $name;
    +        } else {
    +            throw new QueryException(t('Filter column "%s" not found'), $name);
    +        }
    +
    +        if (! $this->validateQueryColumnAssociation($table, $alias)) {
    +            throw new QueryException(t('Filter column "%s" not found in table "%s"'), $name, $table);
    +        }
    +
    +        return $column;
    +    }
    +
    +    /**
    +     * Return whether the given column name or alias of the given table is a valid statement column
    +     *
    +     * @param   string  $table  The table where to look for the column or alias
    +     * @param   string  $name   The column name or alias to check
    +     *
    +     * @return  bool
    +     */
    +    public function hasStatementColumn($table, $name)
    +    {
    +        return $this->hasQueryColumn($table, $name);
    +    }
    +
    +    /**
    +     * Validate that the given column is a valid statement column and return it or the actual name if it's an alias
    +     *
    +     * @param   string  $table      The table for which to require the column
    +     * @param   string  $name       The name or alias of the column to validate
    +     *
    +     * @return  string              The given column's name
    +     *
    +     * @throws  StatementException  In case the given column is not a statement column
    +     */
    +    public function requireStatementColumn($table, $name)
    +    {
    +        if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) {
    +            $alias = $name;
    +        } elseif (($alias = $this->reassembleQueryColumnAlias($table, $name)) !== null) {
    +            $column = $name;
    +        } else {
    +            throw new StatementException('Statement column "%s" not found', $name);
    +        }
    +
    +        if (in_array($alias, $this->getFilterColumns())) {
    +            throw new StatementException('Filter column "%s" cannot be referenced in a statement', $name);
    +        }
    +
    +        if (! $this->validateQueryColumnAssociation($table, $alias)) {
    +            throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table);
    +        }
    +
    +        return $column;
    +    }
    +
    +    /**
    +     * Resolve the given aliases or column names of the given table supposed to be persisted and convert their values
    +     *
    +     * @param   string  $table
    +     * @param   array   $data
    +     *
    +     * @return  array
    +     */
    +    public function requireStatementColumns($table, array $data)
    +    {
    +        $resolved = array();
    +        foreach ($data as $alias => $value) {
    +            $resolved[$this->requireStatementColumn($table, $alias)] = $this->persistColumn($table, $alias, $value);
    +        }
    +
    +        return $resolved;
    +    }
    +}
    diff --git a/library/Icinga/Repository/RepositoryQuery.php b/library/Icinga/Repository/RepositoryQuery.php
    new file mode 100644
    index 000000000..98ed6e81f
    --- /dev/null
    +++ b/library/Icinga/Repository/RepositoryQuery.php
    @@ -0,0 +1,698 @@
    +repository = $repository;
    +    }
    +
    +    /**
    +     * Return the real query being used
    +     *
    +     * @return  QueryInterface
    +     */
    +    public function getQuery()
    +    {
    +        return $this->query;
    +    }
    +
    +    /**
    +     * Set where to fetch which columns
    +     *
    +     * This notifies the repository about each desired query column.
    +     *
    +     * @param   mixed   $target     The target from which to fetch the columns
    +     * @param   array   $columns    If null or an empty array, all columns will be fetched
    +     *
    +     * @return  $this
    +     */
    +    public function from($target, array $columns = null)
    +    {
    +        $target = $this->repository->requireTable($target, $this);
    +        $this->query = $this->repository->getDataSource()->select()->from($target);
    +        $this->query->columns($this->prepareQueryColumns($target, $columns));
    +        $this->target = $target;
    +        return $this;
    +    }
    +
    +    /**
    +     * Return the columns to fetch
    +     *
    +     * @return  array
    +     */
    +    public function getColumns()
    +    {
    +        return $this->query->getColumns();
    +    }
    +
    +    /**
    +     * Set which columns to fetch
    +     *
    +     * This notifies the repository about each desired query column.
    +     *
    +     * @param   array   $columns    If null or an empty array, all columns will be fetched
    +     *
    +     * @return  $this
    +     */
    +    public function columns(array $columns)
    +    {
    +        $this->query->columns($this->prepareQueryColumns($this->target, $columns));
    +        return $this;
    +    }
    +
    +    /**
    +     * Resolve the given columns supposed to be fetched
    +     *
    +     * This notifies the repository about each desired query column.
    +     *
    +     * @param   mixed   $target             The target where to look for each column
    +     * @param   array   $desiredColumns     Pass null or an empty array to require all query columns
    +     *
    +     * @return  array                       The desired columns indexed by their respective alias
    +     */
    +    protected function prepareQueryColumns($target, array $desiredColumns = null)
    +    {
    +        if (empty($desiredColumns)) {
    +            $columns = $this->repository->requireAllQueryColumns($target);
    +        } else {
    +            $columns = array();
    +            foreach ($desiredColumns as $customAlias => $columnAlias) {
    +                $resolvedColumn = $this->repository->requireQueryColumn($target, $columnAlias, $this);
    +                if ($resolvedColumn !== $columnAlias) {
    +                    $columns[is_string($customAlias) ? $customAlias : $columnAlias] = $resolvedColumn;
    +                } elseif (is_string($customAlias)) {
    +                    $columns[$customAlias] = $columnAlias;
    +                } else {
    +                    $columns[] = $columnAlias;
    +                }
    +            }
    +        }
    +
    +        return $columns;
    +    }
    +
    +    /**
    +     * Filter this query using the given column and value
    +     *
    +     * This notifies the repository about the required filter column.
    +     *
    +     * @param   string  $column
    +     * @param   mixed   $value
    +     *
    +     * @return  $this
    +     */
    +    public function where($column, $value = null)
    +    {
    +        $this->query->where(
    +            $this->repository->requireFilterColumn($this->target, $column, $this),
    +            $this->repository->persistColumn($this->target, $column, $value, $this)
    +        );
    +        return $this;
    +    }
    +
    +    /**
    +     * Add an additional filter expression to this query
    +     *
    +     * This notifies the repository about each required filter column.
    +     *
    +     * @param   Filter  $filter
    +     *
    +     * @return  $this
    +     */
    +    public function applyFilter(Filter $filter)
    +    {
    +        return $this->addFilter($filter);
    +    }
    +
    +    /**
    +     * Set a filter for this query
    +     *
    +     * This notifies the repository about each required filter column.
    +     *
    +     * @param   Filter  $filter
    +     *
    +     * @return  $this
    +     */
    +    public function setFilter(Filter $filter)
    +    {
    +        $this->query->setFilter($this->repository->requireFilter($this->target, $filter, $this));
    +        return $this;
    +    }
    +
    +    /**
    +     * Add an additional filter expression to this query
    +     *
    +     * This notifies the repository about each required filter column.
    +     *
    +     * @param   Filter  $filter
    +     *
    +     * @return  $this
    +     */
    +    public function addFilter(Filter $filter)
    +    {
    +        $this->query->addFilter($this->repository->requireFilter($this->target, $filter, $this));
    +        return $this;
    +    }
    +
    +    /**
    +     * Return the current filter
    +     *
    +     * @return  Filter
    +     */
    +    public function getFilter()
    +    {
    +        return $this->query->getFilter();
    +    }
    +
    +    /**
    +     * Return the sort rules being applied on this query
    +     *
    +     * @return  array
    +     */
    +    public function getSortRules()
    +    {
    +        return $this->repository->getSortRules();
    +    }
    +
    +    /**
    +     * Add a sort rule for this query
    +     *
    +     * If called without a specific column, the repository's defaul sort rules will be applied.
    +     * This notifies the repository about each column being required as filter column.
    +     *
    +     * @param   string  $field          The name of the column by which to sort the query's result
    +     * @param   string  $direction      The direction to use when sorting (asc or desc, default is asc)
    +     * @param   bool    $ignoreDefault  Whether to ignore any default sort rules if $field is given
    +     *
    +     * @return  $this
    +     */
    +    public function order($field = null, $direction = null, $ignoreDefault = false)
    +    {
    +        $sortRules = $this->getSortRules();
    +        if ($field === null) {
    +            // Use first available sort rule as default
    +            if (empty($sortRules)) {
    +                // Return early in case of no sort defaults and no given $field
    +                return $this;
    +            }
    +
    +            $sortColumns = reset($sortRules);
    +            if (! array_key_exists('columns', $sortColumns)) {
    +                $sortColumns['columns'] = array(key($sortRules));
    +            }
    +            if ($direction !== null || !array_key_exists('order', $sortColumns)) {
    +                $sortColumns['order'] = $direction ?: static::SORT_ASC;
    +            }
    +        } else {
    +            $alias = $this->repository->reassembleQueryColumnAlias($this->target, $field) ?: $field;
    +            if (! $ignoreDefault && array_key_exists($alias, $sortRules)) {
    +                $sortColumns = $sortRules[$alias];
    +                if (! array_key_exists('columns', $sortColumns)) {
    +                    $sortColumns['columns'] = array($alias);
    +                }
    +                if ($direction !== null || !array_key_exists('order', $sortColumns)) {
    +                    $sortColumns['order'] = $direction ?: static::SORT_ASC;
    +                }
    +            } else {
    +                $sortColumns = array(
    +                    'columns'   => array($alias),
    +                    'order'     => $direction
    +                );
    +            }
    +        }
    +
    +        $baseDirection = strtoupper($sortColumns['order']) === static::SORT_DESC ? static::SORT_DESC : static::SORT_ASC;
    +
    +        foreach ($sortColumns['columns'] as $column) {
    +            list($column, $specificDirection) = $this->splitOrder($column);
    +
    +            if ($this->hasLimit() && $this->repository->providesValueConversion($this->target, $column)) {
    +                Logger::debug(
    +                    'Cannot order by column "%s" in repository "%s". The query is'
    +                    . ' limited and applies value conversion rules on the column',
    +                    $column,
    +                    $this->repository->getName()
    +                );
    +                continue;
    +            }
    +
    +            try {
    +                $this->query->order(
    +                    $this->repository->requireFilterColumn($this->target, $column, $this),
    +                    $specificDirection ?: $baseDirection
    +                    // I would have liked the following solution, but hey, a coder should be allowed to produce crap...
    +                    // $specificDirection && (! $direction || $column !== $field) ? $specificDirection : $baseDirection
    +                );
    +            } catch (QueryException $_) {
    +                Logger::info('Cannot order by column "%s" in repository "%s"', $column, $this->repository->getName());
    +            }
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Extract and return the name and direction of the given sort column definition
    +     *
    +     * @param   string  $field
    +     *
    +     * @return  array               An array of two items: $columnName, $direction
    +     */
    +    protected function splitOrder($field)
    +    {
    +        $columnAndDirection = explode(' ', $field, 2);
    +        if (count($columnAndDirection) === 1) {
    +            $column = $field;
    +            $direction = null;
    +        } else {
    +            $column = $columnAndDirection[0];
    +            $direction = strtoupper($columnAndDirection[1]) === static::SORT_DESC
    +                ? static::SORT_DESC
    +                : static::SORT_ASC;
    +        }
    +
    +        return array($column, $direction);
    +    }
    +
    +    /**
    +     * Return whether any sort rules were applied to this query
    +     *
    +     * @return  bool
    +     */
    +    public function hasOrder()
    +    {
    +        return $this->query->hasOrder();
    +    }
    +
    +    /**
    +     * Return the sort rules applied to this query
    +     *
    +     * @return  array
    +     */
    +    public function getOrder()
    +    {
    +        return $this->query->getOrder();
    +    }
    +
    +    /**
    +     * Set whether this query should peek ahead for more results
    +     *
    +     * Enabling this causes the current query limit to be increased by one. The potential extra row being yielded will
    +     * be removed from the result set. Note that this only applies when fetching multiple results of limited queries.
    +     *
    +     * @return  $this
    +     */
    +    public function peekAhead($state = true)
    +    {
    +        return $this->query->peekAhead($state);
    +    }
    +
    +    /**
    +     * Return whether this query did not yield all available results
    +     *
    +     * @return  bool
    +     */
    +    public function hasMore()
    +    {
    +        return $this->query->hasMore();
    +    }
    +
    +    /**
    +     * Return whether this query will or has yielded any result
    +     *
    +     * @return  bool
    +     */
    +    public function hasResult()
    +    {
    +        return $this->query->hasResult();
    +    }
    +
    +    /**
    +     * Limit this query's results
    +     *
    +     * @param   int     $count      When to stop returning results
    +     * @param   int     $offset     When to start returning results
    +     *
    +     * @return  $this
    +     */
    +    public function limit($count = null, $offset = null)
    +    {
    +        $this->query->limit($count, $offset);
    +        return $this;
    +    }
    +
    +    /**
    +     * Return whether this query does not return all available entries from its result
    +     *
    +     * @return  bool
    +     */
    +    public function hasLimit()
    +    {
    +        return $this->query->hasLimit();
    +    }
    +
    +    /**
    +     * Return the limit when to stop returning results
    +     *
    +     * @return  int
    +     */
    +    public function getLimit()
    +    {
    +        return $this->query->getLimit();
    +    }
    +
    +    /**
    +     * Return whether this query does not start returning results at the very first entry
    +     *
    +     * @return  bool
    +     */
    +    public function hasOffset()
    +    {
    +        return $this->query->hasOffset();
    +    }
    +
    +    /**
    +     * Return the offset when to start returning results
    +     *
    +     * @return  int
    +     */
    +    public function getOffset()
    +    {
    +        return $this->query->getOffset();
    +    }
    +
    +    /**
    +     * Fetch and return the first column of this query's first row
    +     *
    +     * @return  mixed|false     False in case of no result
    +     */
    +    public function fetchOne()
    +    {
    +        if (! $this->hasOrder()) {
    +            $this->order();
    +        }
    +
    +        $result = $this->query->fetchOne();
    +        if ($result !== false && $this->repository->providesValueConversion($this->target)) {
    +            $columns = $this->getColumns();
    +            $column = isset($columns[0]) ? $columns[0] : key($columns);
    +            return $this->repository->retrieveColumn($this->target, $column, $result, $this);
    +        }
    +
    +        return $result;
    +    }
    +
    +    /**
    +     * Fetch and return the first row of this query's result
    +     *
    +     * @return  object|false    False in case of no result
    +     */
    +    public function fetchRow()
    +    {
    +        if (! $this->hasOrder()) {
    +            $this->order();
    +        }
    +
    +        $result = $this->query->fetchRow();
    +        if ($result !== false && $this->repository->providesValueConversion($this->target)) {
    +            foreach ($this->getColumns() as $alias => $column) {
    +                if (! is_string($alias)) {
    +                    $alias = $column;
    +                }
    +
    +                $result->$alias = $this->repository->retrieveColumn($this->target, $alias, $result->$alias, $this);
    +            }
    +        }
    +
    +        return $result;
    +    }
    +
    +    /**
    +     * Fetch and return the first column of all rows of the result set as an array
    +     *
    +     * @return  array
    +     */
    +    public function fetchColumn()
    +    {
    +        if (! $this->hasOrder()) {
    +            $this->order();
    +        }
    +
    +        $results = $this->query->fetchColumn();
    +        if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
    +            $columns = $this->getColumns();
    +            $aliases = array_keys($columns);
    +            $column = is_int($aliases[0]) ? $columns[0] : $aliases[0];
    +            if ($this->repository->providesValueConversion($this->target, $column)) {
    +                foreach ($results as & $value) {
    +                    $value = $this->repository->retrieveColumn($this->target, $column, $value, $this);
    +                }
    +            }
    +        }
    +
    +        return $results;
    +    }
    +
    +    /**
    +     * Fetch and return all rows of this query's result set as an array of key-value pairs
    +     *
    +     * The first column is the key, the second column is the value.
    +     *
    +     * @return  array
    +     */
    +    public function fetchPairs()
    +    {
    +        if (! $this->hasOrder()) {
    +            $this->order();
    +        }
    +
    +        $results = $this->query->fetchPairs();
    +        if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
    +            $columns = $this->getColumns();
    +            $aliases = array_keys($columns);
    +            $colOne = $aliases[0] !== 0 ? $aliases[0] : $columns[0];
    +            $colTwo = count($aliases) < 2 ? $colOne : ($aliases[1] !== 1 ? $aliases[1] : $columns[1]);
    +            if (
    +                $this->repository->providesValueConversion($this->target, $colOne)
    +                || $this->repository->providesValueConversion($this->target, $colTwo)
    +            ) {
    +                $newResults = array();
    +                foreach ($results as $colOneValue => $colTwoValue) {
    +                    $colOneValue = $this->repository->retrieveColumn($this->target, $colOne, $colOneValue, $this);
    +                    $newResults[$colOneValue] = $this->repository->retrieveColumn(
    +                        $this->target,
    +                        $colTwo,
    +                        $colTwoValue,
    +                        $this
    +                    );
    +                }
    +
    +                $results = $newResults;
    +            }
    +        }
    +
    +        return $results;
    +    }
    +
    +    /**
    +     * Fetch and return all results of this query
    +     *
    +     * @return  array
    +     */
    +    public function fetchAll()
    +    {
    +        if (! $this->hasOrder()) {
    +            $this->order();
    +        }
    +
    +        $results = $this->query->fetchAll();
    +        if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
    +            $updateOrder = false;
    +            $columns = $this->getColumns();
    +            $flippedColumns = array_flip($columns);
    +            foreach ($results as $row) {
    +                foreach ($columns as $alias => $column) {
    +                    if (! is_string($alias)) {
    +                        $alias = $column;
    +                    }
    +
    +                    $row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias, $this);
    +                }
    +
    +                foreach (($this->getOrder() ?: array()) as $rule) {
    +                    if (! array_key_exists($rule[0], $flippedColumns) && property_exists($row, $rule[0])) {
    +                        if ($this->repository->providesValueConversion($this->target, $rule[0])) {
    +                            $updateOrder = true;
    +                            $row->{$rule[0]} = $this->repository->retrieveColumn(
    +                                $this->target,
    +                                $rule[0],
    +                                $row->{$rule[0]}
    +                            );
    +                        }
    +                    } elseif (array_key_exists($rule[0], $flippedColumns)) {
    +                        if ($this->repository->providesValueConversion($this->target, $rule[0])) {
    +                            $updateOrder = true;
    +                        }
    +                    }
    +                }
    +            }
    +
    +            if ($updateOrder) {
    +                uasort($results, array($this->query, 'compare'));
    +            }
    +        }
    +
    +        return $results;
    +    }
    +
    +    /**
    +     * Count all results of this query
    +     *
    +     * @return  int
    +     */
    +    public function count()
    +    {
    +        return $this->query->count();
    +    }
    +
    +    /**
    +     * Return the current position of this query's iterator
    +     *
    +     * @return  int
    +     */
    +    public function getIteratorPosition()
    +    {
    +        return $this->query->getIteratorPosition();
    +    }
    +
    +    /**
    +     * Start or rewind the iteration
    +     */
    +    public function rewind()
    +    {
    +        if ($this->iterator === null) {
    +            if (! $this->hasOrder()) {
    +                $this->order();
    +            }
    +
    +            if ($this->query instanceof Traversable) {
    +                $iterator = $this->query;
    +            } else {
    +                $iterator = $this->repository->getDataSource()->query($this->query);
    +            }
    +
    +            if ($iterator instanceof IteratorAggregate) {
    +                $this->iterator = $iterator->getIterator();
    +            } else {
    +                $this->iterator = $iterator;
    +            }
    +        }
    +
    +        $this->iterator->rewind();
    +        Benchmark::measure('Query result iteration started');
    +    }
    +
    +    /**
    +     * Fetch and return the current row of this query's result
    +     *
    +     * @return  object
    +     */
    +    public function current()
    +    {
    +        $row = $this->iterator->current();
    +        if ($this->repository->providesValueConversion($this->target)) {
    +            foreach ($this->getColumns() as $alias => $column) {
    +                if (! is_string($alias)) {
    +                    $alias = $column;
    +                }
    +
    +                $row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias, $this);
    +            }
    +        }
    +
    +        return $row;
    +    }
    +
    +    /**
    +     * Return whether the current row of this query's result is valid
    +     *
    +     * @return  bool
    +     */
    +    public function valid()
    +    {
    +        if (! $this->iterator->valid()) {
    +            Benchmark::measure('Query result iteration finished');
    +            return false;
    +        }
    +
    +        return true;
    +    }
    +
    +    /**
    +     * Return the key for the current row of this query's result
    +     *
    +     * @return  mixed
    +     */
    +    public function key()
    +    {
    +        return $this->iterator->key();
    +    }
    +
    +    /**
    +     * Advance to the next row of this query's result
    +     */
    +    public function next()
    +    {
    +        $this->iterator->next();
    +    }
    +}
    diff --git a/library/Icinga/Security/SecurityException.php b/library/Icinga/Security/SecurityException.php
    new file mode 100644
    index 000000000..e935d12f6
    --- /dev/null
    +++ b/library/Icinga/Security/SecurityException.php
    @@ -0,0 +1,13 @@
    + 'UTC'));
             }
     
             /**
    @@ -183,7 +180,7 @@ namespace Icinga\Test {
              */
             public function getRequestMock()
             {
    -            return Icinga::app()->getFrontController()->getRequest();
    +            return Icinga::app()->getRequest();
             }
     
             /**
    diff --git a/library/Icinga/Test/DbTest.php b/library/Icinga/Test/DbTest.php
    index cf6887285..c394ae0ae 100644
    --- a/library/Icinga/Test/DbTest.php
    +++ b/library/Icinga/Test/DbTest.php
    @@ -1,6 +1,5 @@
     permissions = array_combine($permissions, $permissions);
             }
             return $this;
    @@ -381,58 +387,87 @@ class User
         }
     
         /**
    -     * Set additional remote user information
    +     * Set additional external user information
          *
    -     * @param stirng    $username
    +     * @param string    $username
          * @param string    $field
          */
    -    public function setRemoteUserInformation($username, $field)
    +    public function setExternalUserInformation($username, $field)
         {
    -        $this->remoteUserInformation = array($username, $field);
    +        $this->externalUserInformation = array($username, $field);
         }
     
         /**
    -     * Get additional remote user information
    +     * Get additional external user information
          *
          * @return array
          */
    -    public function getRemoteUserInformation()
    +    public function getExternalUserInformation()
         {
    -        return $this->remoteUserInformation;
    +        return $this->externalUserInformation;
         }
     
         /**
    -     * Return true if user has remote user information set
    +     * Return true if user has external user information set
          *
          * @return bool
          */
    -    public function isRemoteUser()
    +    public function isExternalUser()
         {
    -        return ! empty($this->remoteUserInformation);
    +        return ! empty($this->externalUserInformation);
    +    }
    +
    +    /**
    +     * Get whether the user is authenticated using a HTTP authentication mechanism
    +     *
    +     * @return bool
    +     */
    +    public function getIsHttpUser()
    +    {
    +        return $this->isHttpUser;
    +    }
    +
    +    /**
    +     * Set whether the user is authenticated using a HTTP authentication mechanism
    +     *
    +     * @param   bool $isHttpUser
    +     *
    +     * @return  $this
    +     */
    +    public function setIsHttpUser($isHttpUser = true)
    +    {
    +        $this->isHttpUser = (bool) $isHttpUser;
    +        return $this;
         }
     
         /**
          * Whether the user has a given permission
          *
    -     * @param   string $permission
    +     * @param   string $requiredPermission
          *
          * @return  bool
          */
    -    public function can($permission)
    +    public function can($requiredPermission)
         {
    -        if (isset($this->permissions['*']) || isset($this->permissions[$permission])) {
    +        if (isset($this->permissions['*']) || isset($this->permissions[$requiredPermission])) {
                 return true;
             }
    -        foreach ($this->permissions as $permitted) {
    -            $wildcard = strpos($permitted, '*');
    +        // If the permission to check contains a wildcard, grant the permission if any permit related to the permission
    +        // matches
    +        $any = strpos($requiredPermission, '*');
    +        foreach ($this->permissions as $grantedPermission) {
    +            if ($any !== false) {
    +                $wildcard = $any;
    +            } else {
    +                // If the permit contains a wildcard, grant the permission if it's related to the permit
    +                $wildcard = strpos($grantedPermission, '*');
    +            }
                 if ($wildcard !== false) {
    -                if (substr($permission, 0, $wildcard) === substr($permitted, 0, $wildcard)) {
    +                if (substr($requiredPermission, 0, $wildcard) === substr($grantedPermission, 0, $wildcard)) {
                         return true;
    -                } else {
    -                    if ($permission === $permitted) {
    -                        return true;
    -                    }
                     }
    +            } elseif ($requiredPermission === $grantedPermission) {
    +                return true;
                 }
             }
             return false;
    diff --git a/library/Icinga/User/Preferences.php b/library/Icinga/User/Preferences.php
    index 364112a9e..b64af221a 100644
    --- a/library/Icinga/User/Preferences.php
    +++ b/library/Icinga/User/Preferences.php
    @@ -1,6 +1,5 @@
      'ini',
    + *         'store'       => 'ini',
      *         'config_path' => '/path/to/preferences'
      *     ),
      *     $user // Instance of \Icinga\User
    @@ -117,13 +116,7 @@ abstract class PreferencesStore
          */
         public static function create(ConfigObject $config, User $user)
         {
    -        if (($type = $config->type) === null) {
    -            throw new ConfigurationError(
    -                'Preferences configuration is missing the type directive'
    -            );
    -        }
    -
    -        $type = ucfirst(strtolower($type));
    +        $type = ucfirst(strtolower($config->get('store', 'ini')));
             $storeClass = 'Icinga\\User\\Preferences\\Store\\' . $type . 'Store';
             if (!class_exists($storeClass)) {
                 throw new ConfigurationError(
    diff --git a/library/Icinga/User/Preferences/Store/DbStore.php b/library/Icinga/User/Preferences/Store/DbStore.php
    index 4d7b10584..347fc81df 100644
    --- a/library/Icinga/User/Preferences/Store/DbStore.php
    +++ b/library/Icinga/User/Preferences/Store/DbStore.php
    @@ -1,6 +1,5 @@
      $value) {
                     $db->update(
                         $this->table,
    -                    array(self::COLUMN_VALUE => $value),
    +                    array(
    +                        self::COLUMN_VALUE => $value,
    +                        self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()')
    +                    ),
                         array(
                             self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(),
                             $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section,
    -                        $db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key,
    -                        self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()')
    +                        $db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key
                         )
                     );
                 }
    diff --git a/library/Icinga/User/Preferences/Store/IniStore.php b/library/Icinga/User/Preferences/Store/IniStore.php
    index 1ba780184..88fa7edec 100644
    --- a/library/Icinga/User/Preferences/Store/IniStore.php
    +++ b/library/Icinga/User/Preferences/Store/IniStore.php
    @@ -1,16 +1,13 @@
     writer === null) {
    -            if (! file_exists($this->preferencesFile)) {
    -                if (! is_writable($this->getStoreConfig()->location)) {
    -                    throw new NotWritableError(
    -                        'Path to the preferences INI files %s is not writable',
    -                        $this->getStoreConfig()->location
    -                    );
    -                }
    -
    -                File::create($this->preferencesFile, 0664);
    -            }
    -
    -            if (! is_writable($this->preferencesFile)) {
    -                throw new NotWritableError(
    -                    'Preferences INI file %s for user %s is not writable',
    -                    $this->preferencesFile,
    -                    $this->getUser()->getUsername()
    -                );
    -            }
    -
    -            $this->writer = new IniWriter(
    -                array(
    -                    'config'    => Config::fromArray($this->preferences),
    -                    'filename'  => $this->preferencesFile
    -                )
    -            );
    -        }
    -
    -        $this->writer->write();
    +        Config::fromArray($this->preferences)->saveIni($this->preferencesFile);
         }
     
         /**
    diff --git a/library/Icinga/Util/Color.php b/library/Icinga/Util/Color.php
    index 8058f0152..616bb308a 100644
    --- a/library/Icinga/Util/Color.php
    +++ b/library/Icinga/Util/Color.php
    @@ -1,13 +1,13 @@
     = ~PHP_INT_MAX);
    -    }
    -}
    diff --git a/library/Icinga/Util/Dimension.php b/library/Icinga/Util/Dimension.php
    index 2f367483b..cab81c145 100644
    --- a/library/Icinga/Util/Dimension.php
    +++ b/library/Icinga/Util/Dimension.php
    @@ -1,6 +1,5 @@
      3600 * 24 * 3) {
    -            if (date('Y') === date('Y', $timestamp)) {
    -                return ($includePrefix ? t('since') . ' ' : '') . date('d.m.', $timestamp);
    -            }
    -            return ($includePrefix ? t('since') . ' ' : '') . date('m.Y', $timestamp);
    -        }
    -        return $prefix . self::showHourMin(abs($diff), $includePrefix);
    -    }
    -
    -    public static function timeSince($timestamp)
    -    {
    -        return self::smartTimeDiff(time() - $timestamp, $timestamp);
    -    }
    -
    -    public static function prefixedTimeSince($timestamp, $ucfirst = false)
    -    {
    -        $result = self::smartTimeDiff(time() - $timestamp, $timestamp, true);
    -        if ($ucfirst) {
    -            $result = ucfirst($result);
    -        }
    -        return $result;
    -    }
    -
    -    public static function timeUntil($timestamp)
    -    {
    -        return self::smartTimeDiff($timestamp - time(), $timestamp);
    -    }
    -
    -    public static function prefixedTimeUntil($timestamp, $ucfirst)
    -    {
    -        $result = self::smartTimeDiff($timestamp - time(), $timestamp, true);
    -        if ($ucfirst) {
    -            $result = ucfirst($result);
    -        }
    -        return $result;
    +        return sprintf('%0.2f d', $value / 86400);
         }
     
         protected static function formatForUnits($value, & $units, $base)
    diff --git a/library/Icinga/Util/String.php b/library/Icinga/Util/String.php
    index 0bebb06e7..d67035b3f 100644
    --- a/library/Icinga/Util/String.php
    +++ b/library/Icinga/Util/String.php
    @@ -1,6 +1,5 @@
      $maxLength) {
    +            return substr($string, 0, $maxLength - strlen($ellipsis)) . $ellipsis;
    +        }
    +
    +        return $string;
    +    }
    +
    +    /**
    +     * Add ellipsis in the center of a string when a string is longer than max length
    +     *
    +     * @param   string  $string
    +     * @param   int     $maxLength
    +     * @param   string  $ellipsis
    +     *
    +     * @return  string
    +     */
    +    public static function ellipsisCenter($string, $maxLength, $ellipsis = '...')
    +    {
    +        $start = ceil($maxLength / 2.0);
    +        $end = floor($maxLength / 2.0);
    +        if (strlen($string) > $maxLength) {
    +            return substr($string, 0, $start - strlen($ellipsis)) . $ellipsis . substr($string, - $end);
    +        }
    +
    +        return $string;
    +    }
    +
    +    /**
    +     * Find and return all similar strings in $possibilites matching $string with the given minimum $similarity
    +     *
    +     * @param   string  $string
    +     * @param   array   $possibilities
    +     * @param   float   $similarity
    +     *
    +     * @return  array
    +     */
    +    public static function findSimilar($string, array $possibilities, $similarity = 0.33)
    +    {
    +        if (empty($string)) {
    +            return array();
    +        }
    +
    +        $matches = array();
    +        foreach ($possibilities as $possibility) {
    +            $distance = levenshtein($string, $possibility);
    +            if ($distance / strlen($string) <= $similarity) {
    +                $matches[] = $possibility;
    +            }
    +        }
    +
    +        return $matches;
    +    }
    +
    +    /**
    +     * Check if a string ends with a different string
    +     *
    +     * @param $haystack The string to search for matches
    +     * @param $needle   The string to match at the start of the haystack
    +     *
    +     * @return bool     Whether or not needle is at the beginning of haystack
    +     */
    +    public static function endsWith($haystack, $needle)
    +    {
    +        return $needle === '' ||
    +             (($temp = strlen($haystack) - strlen($needle)) >= 0 && false !== strpos($haystack, $needle, $temp));
    +    }
    +
    +    /**
    +     * Generates an array of strings that constitutes the cartesian product of all passed sets, with all
    +     * string combinations concatenated using the passed join-operator.
    +     *
    +     * 
    +     *  cartesianProduct(
    +     *      array(array('foo', 'bar'), array('mumble', 'grumble', null)),
    +     *      '_'
    +     *  );
    +     *     => array('foo_mumble', 'foo_grumble', 'bar_mumble', 'bar_grumble', 'foo', 'bar')
    +     * 
    + * + * @param array $sets An array of arrays containing all sets for which the cartesian + * product should be calculated. + * @param string $glue The glue used to join the strings, defaults to ''. + * + * @returns array The cartesian product in one array of strings. + */ + public static function cartesianProduct(array $sets, $glue = '') + { + $product = null; + foreach ($sets as $set) { + if (! isset($product)) { + $product = $set; + } else { + $newProduct = array(); + foreach ($product as $strA) { + foreach ($set as $strB) { + if ($strB === null) { + $newProduct []= $strA; + } else { + $newProduct []= $strA . $glue . $strB; + } + } + } + $product = $newProduct; + } + } + return $product; } } diff --git a/library/Icinga/Util/TimezoneDetect.php b/library/Icinga/Util/TimezoneDetect.php index 084c3f8e7..5c89bd7ec 100644 --- a/library/Icinga/Util/TimezoneDetect.php +++ b/library/Icinga/Util/TimezoneDetect.php @@ -1,6 +1,5 @@ handleSortControlSubmit(); + } + + /** + * Check whether the sort control has been submitted and redirect using GET parameters + */ + protected function handleSortControlSubmit() + { + $request = $this->getRequest(); + if (! $request->isPost()) { + return; + } + + if (($sort = $request->getPost('sort')) || ($direction = $request->getPost('dir'))) { + $url = Url::fromRequest(); + if ($sort) { + $url->setParam('sort', $sort); + $url->remove('dir'); + } else { + $url->setParam('dir', $direction); + } + + $this->redirectNow($url); + } + } + + /** + * Immediately respond w/ HTTP 404 + * + * @param string $message Exception message or exception format string + * @param mixed ...$arg Format string argument + * + * @throws HttpNotFoundException + */ + public function httpNotFound($message) + { + throw HttpNotFoundException::create(func_get_args()); + } + + /** + * Create a SortBox widget and apply its sort rules on the given query + * + * The widget is set on the `sortBox' view property only if the current view has not been requested as compact + * + * @param array $columns An array containing the sort columns, with the + * submit value as the key and the label as the value + * @param Sortable $query Query to apply the user chosen sort rules on + * + * @return $this + */ + protected function setupSortControl(array $columns, Sortable $query = null) + { + $request = $this->getRequest(); + $sortBox = SortBox::create('sortbox-' . $request->getActionName(), $columns); + $sortBox->setRequest($request); + + if ($query) { + $sortBox->setQuery($query); + $sortBox->handleRequest($request); + } + + if (! $this->view->compact) { + $this->view->sortBox = $sortBox; + } + + return $this; + } + + /** + * Create a Limiter widget at the `limiter' view property + * + * In case the current view has been requested as compact this method does nothing. + * + * @param int $itemsPerPage Default number of items per page + * + * @return $this + */ + protected function setupLimitControl($itemsPerPage = 25) + { + if (! $this->view->compact) { + $this->view->limiter = new Limiter(); + $this->view->limiter->setDefaultLimit($itemsPerPage); + } + + return $this; + } + + /** + * Apply the given page limit and number on the given query and setup a paginator for it + * + * The $itemsPerPage and $pageNumber parameters are only applied if not available in the current request. + * The paginator is set on the `paginator' view property only if the current view has not been requested as compact. + * + * @param QueryInterface $query The query to create a paginator for + * @param int $itemsPerPage Default number of items per page + * @param int $pageNumber Default page number + * + * @return $this + */ + protected function setupPaginationControl(QueryInterface $query, $itemsPerPage = 25, $pageNumber = 0) + { + $request = $this->getRequest(); + $limit = $request->getParam('limit', $itemsPerPage); + $page = $request->getParam('page', $pageNumber); + $query->limit($limit, $page > 0 ? ($page - 1) * $limit : 0); + + if (! $this->view->compact) { + $paginator = new Paginator(); + $paginator->setQuery($query); + $this->view->paginator = $paginator; + } + + return $this; + } + + /** + * Set the view property `filterEditor' to the given FilterEditor + * + * In case the current view has been requested as compact this method does nothing. + * + * @param Form $editor The FilterEditor + * + * @return $this + */ + protected function setupFilterControl($editor) + { + if (! $this->view->compact) { + $this->view->filterEditor = $editor; + } + + return $this; + } } diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index a41670159..5d1055321 100644 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -1,23 +1,25 @@ handlerBrowserWindows(); $this->view->translationDomain = 'icinga'; - $this->_helper->layout()->isIframe = $this->params->shift('isIframe'); + $this->_helper->layout()->isIframe = $request->getUrl()->shift('isIframe'); + $this->_helper->layout()->showFullscreen = $request->getUrl()->shift('showFullscreen'); $this->_helper->layout()->moduleName = false; - if ($this->rerenderLayout = $this->params->shift('renderLayout')) { + $this->view->compact = $request->getParam('view') === 'compact'; + if ($request->getUrl()->shift('showCompact')) { + $this->view->compact = true; + } + if ($this->rerenderLayout = $request->getUrl()->shift('renderLayout')) { $this->xhrLayout = 'body'; } + if ($request->getUrl()->shift('_disableLayout')) { + $this->_helper->layout()->disableLayout(); + } if ($this->requiresLogin()) { $this->redirectToLogin(Url::fromRequest()); @@ -101,6 +121,45 @@ class ActionController extends Zend_Controller_Action { } + /** + * Get the authentication manager + * + * @return Auth + */ + public function Auth() + { + if ($this->auth === null) { + $this->auth = Auth::getInstance(); + } + return $this->auth; + } + + /** + * Whether the current user has the given permission + * + * @param string $permission Name of the permission + * + * @return bool + */ + public function hasPermission($permission) + { + return $this->Auth()->hasPermission($permission); + } + + /** + * Assert that the current user has the given permission + * + * @param string $permission Name of the permission + * + * @throws SecurityException If the current user lacks the given permission + */ + public function assertPermission($permission) + { + if ($this->requiresAuthentication && ! $this->Auth()->hasPermission($permission)) { + throw new SecurityException('No permission for %s', $permission); + } + } + public function Config($file = null) { if ($file === null) { @@ -110,14 +169,6 @@ class ActionController extends Zend_Controller_Action } } - public function Auth() - { - if ($this->auth === null) { - $this->auth = AuthManager::getInstance(); - } - return $this->auth; - } - public function Window() { if ($this->window === null) { @@ -146,6 +197,23 @@ class ActionController extends Zend_Controller_Action return $this; } + /** + * Respond with HTTP 405 if the current request's method is not one of the given methods + * + * @param string $httpMethod Unlimited number of allowed HTTP methods + * + * @throws HttpMethodNotAllowedException If the request method is not one of the given methods + */ + public function assertHttpMethod($httpMethod) + { + $httpMethods = array_flip(array_map('strtoupper', func_get_args())); + if (! isset($httpMethods[$this->getRequest()->getMethod()])) { + $e = new HttpMethodNotAllowedException($this->translate('Method Not Allowed')); + $e->setAllowedMethods(implode(', ', array_keys($httpMethods))); + throw $e; + } + } + /** * Return restriction information for an eventually authenticated user * @@ -157,34 +225,6 @@ class ActionController extends Zend_Controller_Action return $this->Auth()->getRestrictions($name); } - /** - * Whether the user currently authenticated has the given permission - * - * @param string $name Permission name - * @return bool - */ - public function hasPermission($name) - { - return $this->Auth()->hasPermission($name); - } - - /** - * Throws an exception if user lacks the given permission - * - * @param string $name Permission name - * @throws Exception - */ - public function assertPermission($name) - { - if (! $this->Auth()->hasPermission($name)) { - // TODO: Shall this be an Auth Exception? Or a 404? - throw new IcingaException( - 'Auth error, no permission for "%s"', - $name - ); - } - } - /** * Check whether the controller requires a login. That is when the controller requires authentication and the * user is currently not authenticated @@ -268,34 +308,36 @@ class ActionController extends Zend_Controller_Action } /** - * Redirect to the login path + * Redirect to login * - * @param string $afterLogin The action to call when the login was successful. Defaults to '/index/welcome' + * XHR will always redirect to __SELF__ if an URL to redirect to after successful login is set. __SELF__ instructs + * JavaScript to redirect to the current window's URL if it's an auto-refresh request or to redirect to the URL + * which required login if it's not an auto-refreshing one. * - * @throws \Exception + * XHR will respond with HTTP status code 403 Forbidden. + * + * @param Url|string $redirect URL to redirect to after successful login */ - protected function redirectToLogin($afterLogin = null) + protected function redirectToLogin($redirect = null) { - $redir = null; - if ($afterLogin !== null) { - if (! $afterLogin instanceof Url) { - $afterLogin = Url::fromPath($afterLogin); + $login = Url::fromPath('authentication/login'); + if ($this->isXhr()) { + if ($redirect !== null) { + $login->setParam('redirect', '__SELF__'); } - if ($this->isXhr()) { - $redir = '__SELF__'; - } else { - // TODO: Ignore /? - $redir = $afterLogin->getRelativeUrl(); + + $this->_response->setHttpResponseCode(403); + } elseif ($redirect !== null) { + if (! $redirect instanceof Url) { + $redirect = Url::fromPath($redirect); + } + + if (($relativeUrl = $redirect->getRelativeUrl())) { + $login->setParam('redirect', $relativeUrl); } } - $url = Url::fromPath('authentication/login'); - - if ($redir) { - $url->setParam('redirect', $redir); - } - - $this->rerenderLayout()->redirectNow($url); + $this->rerenderLayout()->redirectNow($login); } protected function rerenderLayout() @@ -355,6 +397,16 @@ class ActionController extends Zend_Controller_Action } } + /** + * @see Zend_Controller_Action::preDispatch() + */ + public function preDispatch() + { + $form = new AutoRefreshForm(); + $form->handleRequest(); + $this->_helper->layout()->autoRefreshForm = $form; + } + /** * Detect whether the current request requires changes in the layout and apply them before rendering * @@ -375,10 +427,13 @@ class ActionController extends Zend_Controller_Action $layout->benchmark = $this->renderBenchmark(); } } + + if ((bool) $user->getPreferences()->getValue('icingaweb', 'auto_refresh', true) === false) { + $this->disableAutoRefresh(); + } } if ($req->getParam('format') === 'pdf') { - $layout->setLayout('pdf'); $this->shutdownSession(); $this->sendAsPdf(); exit; @@ -400,14 +455,14 @@ class ActionController extends Zend_Controller_Action $notifications = Notification::getInstance(); if ($notifications->hasMessages()) { $notificationList = array(); - foreach ($notifications->getMessages() as $m) { + foreach ($notifications->popMessages() as $m) { $notificationList[] = rawurlencode($m->type . ' ' . $m->message); } - $resp->setHeader('X-Icinga-Notification', implode('&', $notificationList)); + $resp->setHeader('X-Icinga-Notification', implode('&', $notificationList), true); } if ($this->reloadCss) { - $resp->setHeader('X-Icinga-CssReload', 'now'); + $resp->setHeader('X-Icinga-CssReload', 'now', true); } if ($this->view->title) { @@ -417,18 +472,19 @@ class ActionController extends Zend_Controller_Action } $resp->setHeader( 'X-Icinga-Title', - rawurlencode($this->view->title . ' :: Icinga Web') + rawurlencode($this->view->title . ' :: Icinga Web'), + true ); } else { - $resp->setHeader('X-Icinga-Title', rawurlencode('Icinga Web')); + $resp->setHeader('X-Icinga-Title', rawurlencode('Icinga Web'), true); } if ($this->rerenderLayout) { - $this->getResponse()->setHeader('X-Icinga-Container', 'layout'); + $this->getResponse()->setHeader('X-Icinga-Container', 'layout', true); } if ($this->autorefreshInterval !== null) { - $resp->setHeader('X-Icinga-Refresh', $this->autorefreshInterval); + $resp->setHeader('X-Icinga-Refresh', $this->autorefreshInterval, true); } } @@ -453,7 +509,7 @@ class ActionController extends Zend_Controller_Action */ protected function renderBenchmark() { - $this->render(); + $this->_helper->viewRenderer->postDispatch(); Benchmark::measure('Response ready'); return Benchmark::renderToHtml(); } diff --git a/library/Icinga/Web/Controller/AuthBackendController.php b/library/Icinga/Web/Controller/AuthBackendController.php new file mode 100644 index 000000000..1fea0eb4f --- /dev/null +++ b/library/Icinga/Web/Controller/AuthBackendController.php @@ -0,0 +1,142 @@ +redirectNow($this->getRequest()->getControllerName() . '/list'); + } + + /** + * Return all user backends implementing the given interface + * + * @param string $interface The class path of the interface, or null if no interface check should be made + * + * @return array + */ + protected function loadUserBackends($interface = null) + { + $backends = array(); + foreach (Config::app('authentication') as $backendName => $backendConfig) { + $candidate = UserBackend::create($backendName, $backendConfig); + if (! $interface || $candidate instanceof $interface) { + $backends[] = $candidate; + } + } + + return $backends; + } + + /** + * Return the given user backend or the first match in order + * + * @param string $name The name of the backend, or null in case the first match should be returned + * @param string $interface The interface the backend should implement, no interface check if null + * + * @return UserBackendInterface + * + * @throws Zend_Controller_Action_Exception In case the given backend name is invalid + */ + protected function getUserBackend($name = null, $interface = 'Icinga\Data\Selectable') + { + if ($name !== null) { + $config = Config::app('authentication'); + if (! $config->hasSection($name)) { + $this->httpNotFound(sprintf($this->translate('Authentication backend "%s" not found'), $name)); + } else { + $backend = UserBackend::create($name, $config->getSection($name)); + if ($interface && !$backend instanceof $interface) { + $interfaceParts = explode('\\', strtolower($interface)); + throw new Zend_Controller_Action_Exception( + sprintf( + $this->translate('Authentication backend "%s" is not %s'), + $name, + array_pop($interfaceParts) + ), + 400 + ); + } + } + } else { + $backends = $this->loadUserBackends($interface); + $backend = array_shift($backends); + } + + return $backend; + } + + /** + * Return all user group backends implementing the given interface + * + * @param string $interface The class path of the interface, or null if no interface check should be made + * + * @return array + */ + protected function loadUserGroupBackends($interface = null) + { + $backends = array(); + foreach (Config::app('groups') as $backendName => $backendConfig) { + $candidate = UserGroupBackend::create($backendName, $backendConfig); + if (! $interface || $candidate instanceof $interface) { + $backends[] = $candidate; + } + } + + return $backends; + } + + /** + * Return the given user group backend or the first match in order + * + * @param string $name The name of the backend, or null in case the first match should be returned + * @param string $interface The interface the backend should implement, no interface check if null + * + * @return UserGroupBackendInterface + * + * @throws Zend_Controller_Action_Exception In case the given backend name is invalid + */ + protected function getUserGroupBackend($name = null, $interface = 'Icinga\Data\Selectable') + { + if ($name !== null) { + $config = Config::app('groups'); + if (! $config->hasSection($name)) { + $this->httpNotFound(sprintf($this->translate('User group backend "%s" not found'), $name)); + } else { + $backend = UserGroupBackend::create($name, $config->getSection($name)); + if ($interface && !$backend instanceof $interface) { + $interfaceParts = explode('\\', strtolower($interface)); + throw new Zend_Controller_Action_Exception( + sprintf( + $this->translate('User group backend "%s" is not %s'), + $name, + array_pop($interfaceParts) + ), + 400 + ); + } + } + } else { + $backends = $this->loadUserGroupBackends($interface); + $backend = array_shift($backends); + } + + return $backend; + } +} diff --git a/library/Icinga/Web/Controller/BasePreferenceController.php b/library/Icinga/Web/Controller/BasePreferenceController.php index f7d45aa5f..84e2b045b 100644 --- a/library/Icinga/Web/Controller/BasePreferenceController.php +++ b/library/Icinga/Web/Controller/BasePreferenceController.php @@ -1,6 +1,5 @@ _helper->layout()->moduleName = $this->moduleName; $this->view->translationDomain = $this->moduleName; $this->moduleInit(); + if ($this->getFrontController()->getDefaultModule() !== $this->moduleName) { + $this->assertPermission(Manager::MODULE_PERMISSION_NS . $this->moduleName); + } } /** @@ -74,6 +77,6 @@ class ModuleActionController extends ActionController public function postDispatchXhr() { parent::postDispatchXhr(); - $this->getResponse()->setHeader('X-Icinga-Module', $this->moduleName); + $this->getResponse()->setHeader('X-Icinga-Module', $this->moduleName, true); } } diff --git a/library/Icinga/Web/Dom/DomNodeIterator.php b/library/Icinga/Web/Dom/DomNodeIterator.php new file mode 100644 index 000000000..5c1542d5e --- /dev/null +++ b/library/Icinga/Web/Dom/DomNodeIterator.php @@ -0,0 +1,106 @@ + + * loadHTML(...); + * $dom = new RecursiveIteratorIterator(new DomNodeIterator($doc), RecursiveIteratorIterator::SELF_FIRST); + * foreach ($dom as $node) { + * .... + * } + *
    + */ +class DomNodeIterator implements RecursiveIterator +{ + /** + * The node's children + * + * @var IteratorIterator + */ + protected $children; + + /** + * Create a new iterator over a DOMNode's children + * + * @param DOMNode $node + */ + public function __construct(DOMNode $node) + { + $this->children = new IteratorIterator($node->childNodes); + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->children->current(); + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->children->key(); + } + + /** + * {@inheritdoc} + */ + public function next() + { + $this->children->next(); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->children->rewind(); + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->children->valid(); + } + + /** + * {@inheritdoc} + */ + public function hasChildren() + { + return $this->current()->hasChildNodes(); + } + + /** + * {@inheritdoc} + * @return DomNodeIterator + */ + public function getChildren() + { + return new static($this->current()); + } +} diff --git a/library/Icinga/Web/FileCache.php b/library/Icinga/Web/FileCache.php index 16c89a2a4..57c0c0964 100644 --- a/library/Icinga/Web/FileCache.php +++ b/library/Icinga/Web/FileCache.php @@ -1,5 +1,5 @@ '')), array('Errors', array('separator' => '')), + array('Help', array('placement' => 'PREPEND')), array('Label', array('separator' => '')), array('HtmlTag', array('tag' => 'div', 'class' => 'element')) ); @@ -147,18 +223,18 @@ class Form extends Zend_Form /** * Set a callback that is called instead of this form's onSuccess method * - * It is called using the following signature: (Request $request, Form $form). + * It is called using the following signature: (Form $this). * * @param callable $onSuccess Callback * * @return $this * - * @throws LogicException If the callback is not callable + * @throws ProgrammingError If the callback is not callable */ public function setOnSuccess($onSuccess) { if (! is_callable($onSuccess)) { - throw new LogicException('The option `onSuccess\' is not callable'); + throw new ProgrammingError('The option `onSuccess\' is not callable'); } $this->onSuccess = $onSuccess; return $this; @@ -169,7 +245,7 @@ class Form extends Zend_Form * * @param string $label The label to use for the submit button * - * @return self + * @return $this */ public function setSubmitLabel($label) { @@ -192,10 +268,18 @@ class Form extends Zend_Form * * @param string|Url $url The url to redirect to * - * @return self + * @return $this + * + * @throws ProgrammingError In case $url is neither a string nor a instance of Icinga\Web\Url */ public function setRedirectUrl($url) { + if (is_string($url)) { + $url = Url::fromPath($url, array(), $this->getRequest()); + } elseif (! $url instanceof Url) { + throw new ProgrammingError('$url must be a string or instance of Icinga\Web\Url'); + } + $this->redirectUrl = $url; return $this; } @@ -203,14 +287,16 @@ class Form extends Zend_Form /** * Return the url to redirect to upon success * - * @return string|Url + * @return Url */ public function getRedirectUrl() { if ($this->redirectUrl === null) { - $url = Url::fromRequest(array(), $this->getRequest()); - // Be sure to remove all form dependent params because we do not want to submit it again - $this->redirectUrl = $url->without(array_keys($this->getElements())); + $this->redirectUrl = $this->getRequest()->getUrl(); + if ($this->getMethod() === 'get') { + // Be sure to remove all form dependent params because we do not want to submit it again + $this->redirectUrl = $this->redirectUrl->without(array_keys($this->getElements())); + } } return $this->redirectUrl; @@ -221,7 +307,7 @@ class Form extends Zend_Form * * @param string $viewScript The view script to use * - * @return self + * @return $this */ public function setViewScript($viewScript) { @@ -244,7 +330,7 @@ class Form extends Zend_Form * * @param bool $disabled Set true in order to disable CSRF protection for this form, otherwise false * - * @return self + * @return $this */ public function setTokenDisabled($disabled = true) { @@ -272,7 +358,7 @@ class Form extends Zend_Form * * @param string $name The name to set * - * @return self + * @return $this */ public function setTokenElementName($name) { @@ -295,7 +381,7 @@ class Form extends Zend_Form * * @param bool $disabled Set true in order to disable identification for this form, otherwise false * - * @return self + * @return $this */ public function setUidDisabled($disabled = true) { @@ -323,7 +409,7 @@ class Form extends Zend_Form * * @param string $name The name to set * - * @return self + * @return $this */ public function setUidElementName($name) { @@ -346,7 +432,7 @@ class Form extends Zend_Form * * @param bool $state * - * @return self + * @return $this */ public function setValidatePartial($state) { @@ -364,25 +450,236 @@ class Form extends Zend_Form return $this->validatePartial; } + /** + * Set whether each element's id should be altered to avoid duplicates + * + * @param bool $value + * + * @return Form + */ + public function setProtectIds($value = true) + { + $this->protectIds = (bool) $value; + return $this; + } + + /** + * Return whether each element's id is being altered to avoid duplicates + * + * @return bool + */ + public function getProtectIds() + { + return $this->protectIds; + } + + /** + * Set the cue to append to each element's label if it's required + * + * @param string $cue + * + * @return Form + */ + public function setRequiredCue($cue) + { + $this->requiredCue = $cue; + return $this; + } + + /** + * Return the cue being appended to each element's label if it's required + * + * @return string + */ + public function getRequiredCue() + { + return $this->requiredCue; + } + + /** + * Set the descriptions for this form + * + * @param array $descriptions + * + * @return Form + */ + public function setDescriptions(array $descriptions) + { + $this->descriptions = $descriptions; + return $this; + } + + /** + * Add a description for this form + * + * If $description is an array the second value should be + * an array as well containing additional HTML properties. + * + * @param string|array $description + * + * @return Form + */ + public function addDescription($description) + { + $this->descriptions[] = $description; + return $this; + } + + /** + * Return the descriptions of this form + * + * @return array + */ + public function getDescriptions() + { + if ($this->descriptions === null) { + return array(); + } + + return $this->descriptions; + } + + /** + * Set the notifications for this form + * + * @param array $notifications + * + * @return $this + */ + public function setNotifications(array $notifications) + { + $this->notifications = $notifications; + return $this; + } + + /** + * Add a notification for this form + * + * If $notification is an array the second value should be + * an array as well containing additional HTML properties. + * + * @param string|array $notification + * @param int $type + * + * @return $this + */ + public function addNotification($notification, $type) + { + $this->notifications[$type][] = $notification; + return $this; + } + + /** + * Return the notifications of this form + * + * @return array + */ + public function getNotifications() + { + if ($this->notifications === null) { + return array(); + } + + return $this->notifications; + } + + /** + * Set the hints for this form + * + * @param array $hints + * + * @return $this + */ + public function setHints(array $hints) + { + $this->hints = $hints; + return $this; + } + + /** + * Add a hint for this form + * + * If $hint is an array the second value should be an + * array as well containing additional HTML properties. + * + * @param string|array $hint + * + * @return $this + */ + public function addHint($hint) + { + $this->hints[] = $hint; + return $this; + } + + /** + * Return the hints of this form + * + * @return array + */ + public function getHints() + { + if ($this->hints === null) { + return array(); + } + + return $this->hints; + } + + /** + * Set whether the Autosubmit decorator should be applied to this form + * + * If true, the Autosubmit decorator is being applied to this form instead of to each of its elements. + * + * @param bool $state + * + * @return Form + */ + public function setUseFormAutosubmit($state = true) + { + $this->useFormAutosubmit = (bool) $state; + return $this; + } + + /** + * Return whether the Autosubmit decorator is being applied to this form + * + * @return bool + */ + public function getUseFormAutosubmit() + { + return $this->useFormAutosubmit; + } + /** * Create this form * * @param array $formData The data sent by the user * - * @return self + * @return $this */ public function create(array $formData = array()) { - if (false === $this->created) { + if (! $this->created) { $this->createElements($formData); $this->addFormIdentification() ->addCsrfCounterMeasure() ->addSubmitButton(); - if ($this->getAction() === '') { + // Use Form::getAttrib() instead of Form::getAction() here because we want to explicitly check against + // null. Form::getAction() would return the empty string '' if the action is not set. + // For not setting the action attribute use Form::setAction(''). This is required for for the + // accessibility's enable/disable auto-refresh mechanic + if ($this->getAttrib('action') === null) { + $action = $this->getRequest()->getUrl(); + if ($this->getMethod() === 'get') { + $action = $action->without(array_keys($this->getElements())); + } + + // TODO(el): Re-evalute this necessity. JavaScript could use the container's URL if there's no action set. // We MUST set an action as JS gets confused otherwise, if // this form is being displayed in an additional column - $this->setAction(Url::fromRequest()->without(array_keys($this->getElements()))); + $this->setAction($action); } $this->created = true; @@ -431,7 +728,7 @@ class Form extends Zend_Form * Uses the label previously set with Form::setSubmitLabel(). Overwrite this * method in order to add multiple submit buttons or one with a custom name. * - * @return self + * @return $this */ public function addSubmitButton() { @@ -466,7 +763,7 @@ class Form extends Zend_Form public function addSubForm(Zend_Form $form, $name = null, $order = null) { if ($form instanceof self) { - $form->removeDecorator('Form'); + $form->setDecorators(array('FormElements')); // TODO: Makes it difficult to customise subform decorators.. $form->setSubmitLabel(''); $form->setTokenDisabled(); $form->setUidDisabled(); @@ -510,24 +807,52 @@ class Form extends Zend_Form } $el = parent::createElement($type, $name, $options); + $el->setTranslator(new ErrorLabeller(array('element' => $el))); - if (($description = $el->getDescription()) !== null && ($label = $el->getDecorator('label')) !== null) { - $label->setOptions(array( - 'title' => $description, - 'class' => 'has-feedback' - )); + $el->addPrefixPaths(array( + array( + 'prefix' => 'Icinga\\Web\\Form\\Validator\\', + 'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Validator'), + 'type' => $el::VALIDATE + ) + )); + + if ($this->protectIds) { + $el->setAttrib('id', $this->getRequest()->protectId($this->getId(false) . '_' . $el->getId())); } if ($el->getAttrib('autosubmit')) { - $noScript = new NoScriptApply(); // Non-JS environments + if ($this->getUseFormAutosubmit()) { + $warningId = 'autosubmit_warning_' . $el->getId(); + $warningText = $this->getView()->escape($this->translate( + 'Upon its value has changed, this field issues an automatic update of this page.' + )); + $autosubmitDecorator = $this->_getDecorator('Callback', array( + 'placement' => 'PREPEND', + 'callback' => function ($content) use ($warningId, $warningText) { + return '' . $warningText . ''; + } + )); + } else { + $autosubmitDecorator = new Autosubmit(); + $autosubmitDecorator->setAccessible(); + $warningId = $autosubmitDecorator->getWarningId($el); + } + $decorators = $el->getDecorators(); - $pos = array_search('Zend_Form_Decorator_ViewHelper', array_keys($decorators)) + 1; + $pos = array_search('Zend_Form_Decorator_ViewHelper', array_keys($decorators), true) + 1; $el->setDecorators( array_slice($decorators, 0, $pos, true) - + array(get_class($noScript) => $noScript) + + array('autosubmit' => $autosubmitDecorator) + array_slice($decorators, $pos, count($decorators) - $pos, true) ); + if (($describedBy = $el->getAttrib('aria-describedby')) !== null) { + $el->setAttrib('aria-describedby', $describedBy . ' ' . $warningId); + } else { + $el->setAttrib('aria-describedby', $warningId); + } + $class = $el->getAttrib('class'); if (is_array($class)) { $class[] = 'autosubmit'; @@ -536,22 +861,72 @@ class Form extends Zend_Form } else { $class .= ' autosubmit'; } - $el->setAttrib('class', $class); // JS environments + $el->setAttrib('class', $class); unset($el->autosubmit); } - return $el; + if ($el->getAttrib('preserveDefault')) { + $el->addDecorator( + array('preserveDefault' => 'HtmlTag'), + array( + 'tag' => 'input', + 'type' => 'hidden', + 'name' => $name . static::DEFAULT_SUFFIX, + 'value' => $el->getValue() + ) + ); + + unset($el->preserveDefault); + } + + return $this->ensureElementAccessibility($el); + } + + /** + * Add accessibility related attributes + * + * @param Zend_Form_Element $element + * + * @return Zend_Form_Element + */ + public function ensureElementAccessibility(Zend_Form_Element $element) + { + if ($element->isRequired() && strpos(strtolower($element->getType()), 'checkbox') === false) { + $element->setAttrib('aria-required', 'true'); // ARIA + $element->setAttrib('required', ''); // HTML5 + if (($cue = $this->getRequiredCue()) !== null && ($label = $element->getDecorator('label')) !== false) { + $element->setLabel($this->getView()->escape($element->getLabel())); + $label->setOption('escape', false); + $label->setRequiredSuffix(sprintf(' ', $cue)); + } + } + + if ($element->getDescription() !== null && ($help = $element->getDecorator('help')) !== false) { + if (($describedBy = $element->getAttrib('aria-describedby')) !== null) { + // Assume that it's because of the element being of type autosubmit or + // that one who did set the property manually removes the help decorator + // in case it has already an aria-describedby property set + $element->setAttrib( + 'aria-describedby', + $help->setAccessible()->getDescriptionId($element) . ' ' . $describedBy + ); + } else { + $element->setAttrib('aria-describedby', $help->setAccessible()->getDescriptionId($element)); + } + } + + return $element; } /** * Add a field with a unique and form specific ID * - * @return self + * @return $this */ public function addFormIdentification() { - if (false === $this->uidDisabled && $this->getElement($this->uidElementName) === null) { + if (! $this->uidDisabled && $this->getElement($this->uidElementName) === null) { $this->addElement( 'hidden', $this->uidElementName, @@ -569,14 +944,21 @@ class Form extends Zend_Form /** * Add CSRF counter measure field to this form * - * @return self + * @return $this */ public function addCsrfCounterMeasure() { - if (false === $this->tokenDisabled && $this->getElement($this->tokenElementName) === null) { - $this->addElement(new CsrfCounterMeasure($this->tokenElementName)); + if (! $this->tokenDisabled) { + $request = $this->getRequest(); + if (! $request->isXmlHttpRequest() + && $request->getIsApiRequest() + ) { + return $this; + } + if ($this->getElement($this->tokenElementName) === null) { + $this->addElement(new CsrfCounterMeasure($this->tokenElementName)); + } } - return $this; } @@ -588,9 +970,33 @@ class Form extends Zend_Form public function populate(array $defaults) { $this->create($defaults); + $this->preserveDefaults($this, $defaults); return parent::populate($defaults); } + /** + * Recurse the given form and unset all unchanged default values + * + * @param Zend_Form $form + * @param array $defaults + */ + protected function preserveDefaults(Zend_Form $form, array & $defaults) + { + foreach ($form->getElements() as $name => $_) { + if ( + array_key_exists($name, $defaults) + && array_key_exists($name . static::DEFAULT_SUFFIX, $defaults) + && $defaults[$name] === $defaults[$name . static::DEFAULT_SUFFIX] + ) { + unset($defaults[$name]); + } + } + + foreach ($form->getSubForms() as $_ => $subForm) { + $this->preserveDefaults($subForm, $defaults); + } + } + /** * Process the given request using this form * @@ -611,12 +1017,20 @@ class Form extends Zend_Form $formData = $this->getRequestData(); if ($this->getUidDisabled() || $this->wasSent($formData)) { + if (($frameUpload = (bool) $request->getUrl()->shift('_frameUpload', false))) { + $this->getView()->layout()->setLayout('wrapped'); + } + $this->populate($formData); // Necessary to get isSubmitted() to work if (! $this->getSubmitLabel() || $this->isSubmitted()) { if ($this->isValid($formData) && (($this->onSuccess !== null && false !== call_user_func($this->onSuccess, $this)) || ($this->onSuccess === null && false !== $this->onSuccess()))) { - $this->getResponse()->redirectAndExit($this->getRedirectUrl()); + if (! $frameUpload) { + $this->getResponse()->redirectAndExit($this->getRedirectUrl()); + } else { + $this->getView()->layout()->redirectUrl = $this->getRedirectUrl()->getAbsoluteUrl(); + } } } elseif ($this->getValidatePartial()) { // The form can't be processed but we may want to show validation errors though @@ -674,10 +1088,18 @@ class Form extends Zend_Form { $this->create($formData); - // Ensure that disabled elements are not overwritten (http://www.zendframework.com/issues/browse/ZF-6909) foreach ($this->getElements() as $name => $element) { - if ($element->getAttrib('disabled')) { - $formData[$name] = $element->getValue(); + if (array_key_exists($name, $formData)) { + if ($element->getAttrib('disabled')) { + // Ensure that disabled elements are not overwritten + // (http://www.zendframework.com/issues/browse/ZF-6909) + $formData[$name] = $element->getValue(); + } elseif ( + array_key_exists($name . static::DEFAULT_SUFFIX, $formData) + && $formData[$name] === $formData[$name . static::DEFAULT_SUFFIX] + ) { + unset($formData[$name]); + } } } @@ -722,7 +1144,7 @@ class Form extends Zend_Form * Overwrites Zend_Form::loadDefaultDecorators to avoid having * the HtmlTag-Decorator added and to provide viewscript usage * - * @return self + * @return $this */ public function loadDefaultDecorators() { @@ -738,8 +1160,17 @@ class Form extends Zend_Form 'form' => $this )); } else { - $this->addDecorator('FormErrors', array('onlyCustomFormErrors' => true)) + $this->addDecorator('Description', array('tag' => 'h1')); + if ($this->getUseFormAutosubmit()) { + $this->addDecorator('Autosubmit', array('accessible' => true)) + ->addDecorator('HtmlTag', array('tag' => 'div', 'class' => 'header')); + } + + $this->addDecorator('FormDescriptions') + ->addDecorator('FormNotifications') + ->addDecorator('FormErrors', array('onlyCustomFormErrors' => true)) ->addDecorator('FormElements') + ->addDecorator('FormHints') //->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form')) ->addDecorator('Form'); } @@ -748,6 +1179,21 @@ class Form extends Zend_Form return $this; } + /** + * Get element id + * + * Returns the protected id, in case id protection is enabled. + * + * @param bool $protect + * + * @return string + */ + public function getId($protect = true) + { + $id = parent::getId(); + return $protect && $this->protectIds ? $this->getRequest()->protectId($id) : $id; + } + /** * Return the name of this form * @@ -764,6 +1210,38 @@ class Form extends Zend_Form return $name; } + /** + * Set the action to submit this form against + * + * Note that if you'll pass a instance of URL, Url::getAbsoluteUrl('&') is called to set the action. + * + * @param Url|string $action + * + * @return $this + */ + public function setAction($action) + { + if ($action instanceof Url) { + $action = $action->getAbsoluteUrl('&'); + } + + return parent::setAction($action); + } + + /** + * Set form description + * + * Alias for Zend_Form::setDescription(). + * + * @param string $value + * + * @return Form + */ + public function setTitle($value) + { + return $this->setDescription($value); + } + /** * Return the request associated with this form * @@ -774,7 +1252,7 @@ class Form extends Zend_Form public function getRequest() { if ($this->request === null) { - $this->request = Icinga::app()->getFrontController()->getRequest(); + $this->request = Icinga::app()->getRequest(); } return $this->request; @@ -804,6 +1282,59 @@ class Form extends Zend_Form return array(); } + /** + * Get the translation domain for this form + * + * The returned translation domain is either determined based on this form's qualified name or it is the default + * 'icinga' domain + * + * @return string + */ + protected function getTranslationDomain() + { + $parts = explode('\\', get_called_class()); + if (count($parts) > 1 && $parts[1] === 'Module') { + // Assume format Icinga\Module\ModuleName\Forms\... + return strtolower($parts[2]); + } + + return 'icinga'; + } + + /** + * Translate a string + * + * @param string $text The string to translate + * @param string|null $context Optional parameter for context based translation + * + * @return string The translated string + */ + protected function translate($text, $context = null) + { + return Translator::translate($text, $this->getTranslationDomain(), $context); + } + + /** + * Translate a plural string + * + * @param string $textSingular The string in singular form to translate + * @param string $textPlural The string in plural form to translate + * @param integer $number The amount to determine from whether to return singular or plural + * @param string|null $context Optional parameter for context based translation + * + * @return string The translated string + */ + protected function translatePlural($textSingular, $textPlural, $number, $context = null) + { + return Translator::translatePlural( + $textSingular, + $textPlural, + $number, + $this->getTranslationDomain(), + $context + ); + } + /** * Render this form * @@ -816,4 +1347,89 @@ class Form extends Zend_Form $this->create(); return parent::render($view); } + + /** + * Get the authentication manager + * + * @return Auth + */ + public function Auth() + { + if ($this->auth === null) { + $this->auth = Auth::getInstance(); + } + return $this->auth; + } + + /** + * Whether the current user has the given permission + * + * @param string $permission Name of the permission + * + * @return bool + */ + public function hasPermission($permission) + { + return $this->Auth()->hasPermission($permission); + } + + /** + * Assert that the current user has the given permission + * + * @param string $permission Name of the permission + * + * @throws SecurityException If the current user lacks the given permission + */ + public function assertPermission($permission) + { + if (! $this->Auth()->hasPermission($permission)) { + throw new SecurityException('No permission for %s', $permission); + } + } + + /** + * Add a error notification and prevent the form from being successfully validated + * + * @param string|array $message The notfication's message + * + * @return $this + */ + public function error($message) + { + $this->addNotification($message, self::NOTIFICATION_ERROR); + $this->markAsError(); + return $this; + } + + /** + * Add a warning notification and prevent the form from being successfully validated + * + * @param string|array $message The notfication's message + * + * @return $this + */ + public function warning($message) + { + $this->addNotification($message, self::NOTIFICATION_WARNING); + $this->markAsError(); + return $this; + } + + /** + * Add a info notification + * + * @param string|array $message The notfication's message + * @param bool $markAsError Whether to prevent the form from being successfully validated or not + * + * @return $this + */ + public function info($message, $markAsError = true) + { + $this->addNotification($message, self::NOTIFICATION_INFO); + if ($markAsError) { + $this->markAsError(); + } + + return $this; + } } diff --git a/library/Icinga/Web/Form/Decorator/Autosubmit.php b/library/Icinga/Web/Form/Decorator/Autosubmit.php new file mode 100644 index 000000000..6893c161e --- /dev/null +++ b/library/Icinga/Web/Form/Decorator/Autosubmit.php @@ -0,0 +1,128 @@ + should be created with the same warning as in the icon label + * + * @var bool + */ + protected $accessible; + + /** + * The id used to identify the auto-submit warning associated with the decorated form element + * + * @var string + */ + protected $warningId; + + /** + * Set whether a hidden should be created with the same warning as in the icon label + * + * @param bool $state + * + * @return Autosubmit + */ + public function setAccessible($state = true) + { + $this->accessible = (bool) $state; + return $this; + } + + /** + * Return whether a hidden is being created with the same warning as in the icon label + * + * @return bool + */ + public function getAccessible() + { + if ($this->accessible === null) { + $this->accessible = $this->getOption('accessible') ?: false; + } + + return $this->accessible; + } + + /** + * Return the id used to identify the auto-submit warning associated with the decorated element + * + * @param mixed $element The element for which to generate a id + * + * @return string + */ + public function getWarningId($element = null) + { + if ($this->warningId === null) { + $element = $element ?: $this->getElement(); + $this->warningId = 'autosubmit_warning_' . $element->getId(); + } + + return $this->warningId; + } + + /** + * Return the current view + * + * @return View + */ + protected function getView() + { + return Icinga::app()->getViewRenderer()->view; + } + + /** + * Add a auto-submit icon and submit button encapsulated in noscript-tags to the element + * + * @param string $content The html rendered so far + * + * @return string The updated html + */ + public function render($content = '') + { + if ($content) { + $isForm = $this->getElement() instanceof Form; + $warning = $isForm + ? t('Upon any of this form\'s fields were changed, this page is being updated automatically.') + : t('Upon its value has changed, this field issues an automatic update of this page.'); + $content .= $this->getView()->icon('cw', $warning, array( + 'aria-hidden' => $isForm ? 'false' : 'true', + 'class' => 'autosubmit-warning' + )); + if (! $isForm && $this->getAccessible()) { + $content = '' . $warning . '' . $content; + } + + $content .= sprintf( + '', + $isForm + ? t('Push this button to update the form to reflect the changes that were made below') + : t('Push this button to update the form to reflect the change' + . ' that was made in the field on the left'), + $this->getView()->icon('cw') . t('Apply') + ); + } + + return $content; + } +} diff --git a/library/Icinga/Web/Form/Decorator/ConditionalHidden.php b/library/Icinga/Web/Form/Decorator/ConditionalHidden.php index c24f637ba..a49c21603 100644 --- a/library/Icinga/Web/Form/Decorator/ConditionalHidden.php +++ b/library/Icinga/Web/Form/Decorator/ConditionalHidden.php @@ -1,6 +1,5 @@ getElement(); + if (! $form instanceof Form) { + return $content; + } + + $view = $form->getView(); + if ($view === null) { + return $content; + } + + $descriptions = $this->recurseForm($form); + if (empty($descriptions)) { + return $content; + } + + $html = '
      '; + foreach ($descriptions as $description) { + if (is_array($description)) { + list($description, $properties) = $description; + $html .= 'propertiesToString($properties) . '>' . $view->escape($description) . ''; + } else { + $html .= '
    • ' . $view->escape($description) . '
    • '; + } + } + + switch ($this->getPlacement()) { + case self::APPEND: + return $content . $html . '
    '; + case self::PREPEND: + return $html . '' . $content; + } + } + + /** + * Recurse the given form and return the descriptions for it and all of its subforms + * + * @param Form $form The form to recurse + * + * @return array + */ + protected function recurseForm(Form $form) + { + $descriptions = array($form->getDescriptions()); + foreach ($form->getSubForms() as $subForm) { + $descriptions[] = $this->recurseForm($subForm); + } + + return call_user_func_array('array_merge', $descriptions); + } +} diff --git a/library/Icinga/Web/Form/Decorator/FormHints.php b/library/Icinga/Web/Form/Decorator/FormHints.php new file mode 100644 index 000000000..91e906684 --- /dev/null +++ b/library/Icinga/Web/Form/Decorator/FormHints.php @@ -0,0 +1,142 @@ +blacklist = array( + 'Zend_Form_Element_Hidden', + 'Zend_Form_Element_Submit', + 'Zend_Form_Element_Button', + 'Icinga\Web\Form\Element\Note', + 'Icinga\Web\Form\Element\Button', + 'Icinga\Web\Form\Element\CsrfCounterMeasure' + ); + } + + /** + * Render form hints + * + * @param string $content The html rendered so far + * + * @return string The updated html + */ + public function render($content = '') + { + $form = $this->getElement(); + if (! $form instanceof Form) { + return $content; + } + + $view = $form->getView(); + if ($view === null) { + return $content; + } + + $hints = $this->recurseForm($form, $entirelyRequired); + if ($entirelyRequired !== null) { + $hints[] = $form->getView()->translate(sprintf( + 'Required fields are marked with %s and must be filled in to complete the form.', + $form->getRequiredCue() + )); + } + + if (empty($hints)) { + return $content; + } + + $html = '
      '; + foreach ($hints as $hint) { + if (is_array($hint)) { + list($hint, $properties) = $hint; + $html .= 'propertiesToString($properties) . '>' . $view->escape($hint) . ''; + } else { + $html .= '
    • ' . $view->escape($hint) . '
    • '; + } + } + + switch ($this->getPlacement()) { + case self::APPEND: + return $content . $html . '
    '; + case self::PREPEND: + return $html . '' . $content; + } + } + + /** + * Recurse the given form and return the hints for it and all of its subforms + * + * @param Form $form The form to recurse + * @param mixed $entirelyRequired Set by reference, true means all elements in the hierarchy are + * required, false only a partial subset and null none at all + * @param bool $elementsPassed Whether there were any elements passed during the recursion until now + * + * @return array + */ + protected function recurseForm(Form $form, & $entirelyRequired = null, $elementsPassed = false) + { + $requiredLabels = array(); + if ($form->getRequiredCue() !== null) { + $partiallyRequired = $partiallyOptional = false; + foreach ($form->getElements() as $element) { + if (! in_array($element->getType(), $this->blacklist)) { + if (! $element->isRequired()) { + $partiallyOptional = true; + if ($entirelyRequired) { + $entirelyRequired = false; + } + } else { + $partiallyRequired = true; + if (($label = $element->getDecorator('label')) !== false) { + $requiredLabels[] = $label; + } + } + } + } + + if (! $elementsPassed) { + $elementsPassed = $partiallyRequired || $partiallyOptional; + if ($entirelyRequired === null && $partiallyRequired) { + $entirelyRequired = ! $partiallyOptional; + } + } elseif ($entirelyRequired === null && $partiallyRequired) { + $entirelyRequired = false; + } + } + + $hints = array($form->getHints()); + foreach ($form->getSubForms() as $subForm) { + $hints[] = $this->recurseForm($subForm, $entirelyRequired, $elementsPassed); + } + + if ($entirelyRequired) { + foreach ($requiredLabels as $label) { + $label->setRequiredSuffix(''); + } + } + + return call_user_func_array('array_merge', $hints); + } +} diff --git a/library/Icinga/Web/Form/Decorator/FormNotifications.php b/library/Icinga/Web/Form/Decorator/FormNotifications.php new file mode 100644 index 000000000..935c0ce41 --- /dev/null +++ b/library/Icinga/Web/Form/Decorator/FormNotifications.php @@ -0,0 +1,109 @@ +getElement(); + if (! $form instanceof Form) { + return $content; + } + + $view = $form->getView(); + if ($view === null) { + return $content; + } + + $notifications = $this->recurseForm($form); + if (empty($notifications)) { + return $content; + } + + $html = '
      '; + foreach (array(Form::NOTIFICATION_ERROR, Form::NOTIFICATION_WARNING, Form::NOTIFICATION_INFO) as $type) { + if (isset($notifications[$type])) { + $html .= '
      • '; + foreach ($notifications[$type] as $message) { + if (is_array($message)) { + list($message, $properties) = $message; + $html .= 'propertiesToString($properties) . '>' + . $view->escape($message) + . ''; + } else { + $html .= '
      • ' . $view->escape($message) . '
      • '; + } + } + + $html .= '
    • '; + } + } + + switch ($this->getPlacement()) { + case self::APPEND: + return $content . $html . '
    '; + case self::PREPEND: + return $html . '' . $content; + } + } + + /** + * Recurse the given form and return the notifications for it and all of its subforms + * + * @param Form $form The form to recurse + * + * @return array + */ + protected function recurseForm(Form $form) + { + $notifications = $form->getNotifications(); + foreach ($form->getSubForms() as $subForm) { + foreach ($this->recurseForm($subForm) as $type => $messages) { + foreach ($messages as $message) { + $notifications[$type][] = $message; + } + } + } + + return $notifications; + } + + /** + * Return the name for the given notification type + * + * @param int $type + * + * @return string + * + * @throws ProgrammingError In case the given type is invalid + */ + protected function getNotificationTypeName($type) + { + switch ($type) { + case Form::NOTIFICATION_ERROR: + return 'error'; + case Form::NOTIFICATION_WARNING: + return 'warning'; + case Form::NOTIFICATION_INFO: + return 'info'; + default: + throw new ProgrammingError('Invalid notification type "%s" provided', $type); + } + } +} diff --git a/library/Icinga/Web/Form/Decorator/Help.php b/library/Icinga/Web/Form/Decorator/Help.php new file mode 100644 index 000000000..152d548af --- /dev/null +++ b/library/Icinga/Web/Form/Decorator/Help.php @@ -0,0 +1,110 @@ + should be created to describe the decorated form element + * + * @var bool + */ + protected $accessible = false; + + /** + * The id used to identify the description associated with the decorated form element + * + * @var string + */ + protected $descriptionId; + + /** + * Set whether a hidden should be created to describe the decorated form element + * + * @param bool $state + * + * @return Help + */ + public function setAccessible($state = true) + { + $this->accessible = (bool) $state; + return $this; + } + + /** + * Return the id used to identify the description associated with the decorated element + * + * @param Zend_Form_Element $element The element for which to generate a id + * + * @return string + */ + public function getDescriptionId(Zend_Form_Element $element = null) + { + if ($this->descriptionId === null) { + $element = $element ?: $this->getElement(); + $this->descriptionId = 'desc_' . $element->getId(); + } + + return $this->descriptionId; + } + + /** + * Return the current view + * + * @return View + */ + protected function getView() + { + return Icinga::app()->getViewRenderer()->view; + } + + /** + * Add a help icon to the left of an element + * + * @param string $content The html rendered so far + * + * @return string The updated html + */ + public function render($content = '') + { + $element = $this->getElement(); + $description = $element->getDescription(); + $requirement = $element->getAttrib('requirement'); + unset($element->requirement); + + $helpContent = ''; + if ($description || $requirement) { + if ($this->accessible) { + $helpContent = '' + . $description + . ($description && $requirement ? ' ' : '') + . $requirement + . ''; + } + + $helpContent = $this->getView()->icon( + 'help', + $description . ($description && $requirement ? ' ' : '') . $requirement, + array('aria-hidden' => $this->accessible ? 'true' : 'false') + ) . $helpContent; + } + + switch ($this->getPlacement()) { + case self::APPEND: + return $content . $helpContent; + case self::PREPEND: + return $helpContent . $content; + } + } +} diff --git a/library/Icinga/Web/Form/Decorator/NoScriptApply.php b/library/Icinga/Web/Form/Decorator/NoScriptApply.php deleted file mode 100644 index 509c3531e..000000000 --- a/library/Icinga/Web/Form/Decorator/NoScriptApply.php +++ /dev/null @@ -1,35 +0,0 @@ -'; - } - - return $content; - } -} diff --git a/library/Icinga/Web/Form/Element/Button.php b/library/Icinga/Web/Form/Element/Button.php index 1c5499373..f62cfde21 100644 --- a/library/Icinga/Web/Form/Element/Button.php +++ b/library/Icinga/Web/Form/Element/Button.php @@ -1,6 +1,5 @@ getFrontController()->getRequest(); + return Icinga::app()->getRequest(); } } diff --git a/library/Icinga/Web/Form/Element/CsrfCounterMeasure.php b/library/Icinga/Web/Form/Element/CsrfCounterMeasure.php index e9bc37edd..13e4e737c 100644 --- a/library/Icinga/Web/Form/Element/CsrfCounterMeasure.php +++ b/library/Icinga/Web/Form/Element/CsrfCounterMeasure.php @@ -1,6 +1,5 @@ local === true ? 'Y-m-d\TH:i:s' : DateTime::RFC3339; - $this->setValue(DateTime::createFromFormat($format, $value)); + $dateTime = DateTime::createFromFormat($format, $value); + if ($dateTime === false) { + $dateTime = DateTime::createFromFormat(substr($format, 0, strrpos($format, ':')), $value); + } + + $this->setValue($dateTime); } + return true; } } diff --git a/library/Icinga/Web/Form/Element/Note.php b/library/Icinga/Web/Form/Element/Note.php index 37bc127e9..eb8cfbbda 100644 --- a/library/Icinga/Web/Form/Element/Note.php +++ b/library/Icinga/Web/Form/Element/Note.php @@ -1,6 +1,5 @@ messages = $this->createMessages($options['element']); + } + + public function isTranslated($messageId, $original = false, $locale = null) + { + return array_key_exists($messageId, $this->messages); + } + + public function translate($messageId, $locale = null) + { + if (array_key_exists($messageId, $this->messages)) { + return $this->messages[$messageId]; + } + + return $messageId; + } + + protected function createMessages($element) + { + $label = $element->getLabel() ?: $element->getName(); + + return array( + Zend_Validate_NotEmpty::IS_EMPTY => sprintf(t('%s is required and must not be empty'), $label), + Zend_Validate_File_MimeType::FALSE_TYPE => sprintf( + t('%s (%%value%%) has a false MIME type of "%%type%%"'), + $label + ), + Zend_Validate_File_MimeType::NOT_DETECTED => sprintf(t('%s (%%value%%) has no MIME type'), $label), + WritablePathValidator::NOT_WRITABLE => sprintf(t('%s is not writable', 'config.path'), $label), + WritablePathValidator::DOES_NOT_EXIST => sprintf(t('%s does not exist', 'config.path'), $label), + ReadablePathValidator::NOT_READABLE => sprintf(t('%s is not readable', 'config.path'), $label), + DateTimeValidator::INVALID_DATETIME_FORMAT => sprintf( + t('%s not in the expected format: %%value%%'), + $label + ) + ); + } + + protected function _loadTranslationData($data, $locale, array $options = array()) + { + // nonsense, required as being abstract otherwise... + } + + public function toString() + { + return 'ErrorLabeller'; // nonsense, required as being abstract otherwise... + } +} diff --git a/library/Icinga/Web/Form/FormElement.php b/library/Icinga/Web/Form/FormElement.php index 690be1acf..14e36cb12 100644 --- a/library/Icinga/Web/Form/FormElement.php +++ b/library/Icinga/Web/Form/FormElement.php @@ -1,6 +1,5 @@ 'Invalid type given. Instance of DateTime or date/time string expected', + self::INVALID_DATETIME_FORMAT => 'Date/time string not in the expected format: %value%' + ); + protected $local; /** @@ -39,17 +53,25 @@ class DateTimeValidator extends Zend_Validate_Abstract public function isValid($value, $context = null) { if (! $value instanceof DateTime && ! is_string($value)) { - $this->_error(t('Invalid type given. Instance of DateTime or date/time string expected')); + $this->_error(self::INVALID_DATETIME_TYPE); return false; } - if (is_string($value)) { - $format = $this->local === true ? 'Y-m-d\TH:i:s' : DateTime::RFC3339; + + if (! $value instanceof DateTime) { + $format = $baseFormat = $this->local === true ? 'Y-m-d\TH:i:s' : DateTime::RFC3339; $dateTime = DateTime::createFromFormat($format, $value); + + if ($dateTime === false) { + $format = substr($format, 0, strrpos($format, ':')); + $dateTime = DateTime::createFromFormat($format, $value); + } + if ($dateTime === false || $dateTime->format($format) !== $value) { - $this->_error(sprintf(t('Date/time string not in the expected format %s'), $format)); + $this->_error(self::INVALID_DATETIME_FORMAT, $baseFormat); return false; } } + return true; } } diff --git a/library/Icinga/Web/Form/Validator/InArray.php b/library/Icinga/Web/Form/Validator/InArray.php new file mode 100644 index 000000000..d9cbc481f --- /dev/null +++ b/library/Icinga/Web/Form/Validator/InArray.php @@ -0,0 +1,28 @@ +_value, $this->_haystack); + if (empty($matches)) { + $this->_messages[$messageKey] = sprintf(t('"%s" is not in the list of allowed values.'), $this->_value); + } else { + $this->_messages[$messageKey] = sprintf( + t('"%s" is not in the list of allowed values. Did you mean one of the following?: %s'), + $this->_value, + implode(', ', $matches) + ); + } + } else { + parent::_error($messageKey, $value); + } + } +} diff --git a/library/Icinga/Web/Form/Validator/ReadablePathValidator.php b/library/Icinga/Web/Form/Validator/ReadablePathValidator.php index 4060f6ecd..87b844fde 100644 --- a/library/Icinga/Web/Form/Validator/ReadablePathValidator.php +++ b/library/Icinga/Web/Form/Validator/ReadablePathValidator.php @@ -1,6 +1,5 @@ _messageTemplates = array( - 'NOT_READABLE' => t('Path is not readable'), - 'DOES_NOT_EXIST' => t('Path does not exist') - ); - } + protected $_messageTemplates = array( + self::NOT_READABLE => 'Path is not readable', + self::DOES_NOT_EXIST => 'Path does not exist' + ); /** * Check whether the given value is a readable filepath @@ -45,12 +39,13 @@ class ReadablePathValidator extends Zend_Validate_Abstract public function isValid($value, $context = null) { if (false === file_exists($value)) { - $this->_error('DOES_NOT_EXIST'); + $this->_error(self::DOES_NOT_EXIST); return false; } if (false === is_readable($value)) { - $this->_error('NOT_READABLE'); + $this->_error(self::NOT_READABLE); + return false; } return true; diff --git a/library/Icinga/Web/Form/Validator/TimeFormatValidator.php b/library/Icinga/Web/Form/Validator/TimeFormatValidator.php index a02581cfa..acebcd919 100644 --- a/library/Icinga/Web/Form/Validator/TimeFormatValidator.php +++ b/library/Icinga/Web/Form/Validator/TimeFormatValidator.php @@ -1,6 +1,5 @@ 'Path is not writable', - 'DOES_NOT_EXIST' => 'Path does not exist' + self::NOT_WRITABLE => 'Path is not writable', + self::DOES_NOT_EXIST => 'Path does not exist' ); /** @@ -54,7 +56,7 @@ class WritablePathValidator extends Zend_Validate_Abstract $this->_setValue($value); if ($this->requireExistence && !file_exists($value)) { - $this->_error('DOES_NOT_EXIST'); + $this->_error(self::DOES_NOT_EXIST); return false; } @@ -63,7 +65,8 @@ class WritablePathValidator extends Zend_Validate_Abstract ) { return true; } - $this->_error('NOT_WRITABLE'); + + $this->_error(self::NOT_WRITABLE); return false; } } diff --git a/library/Icinga/Web/Hook.php b/library/Icinga/Web/Hook.php index 01d1e0e2c..3b9700f27 100644 --- a/library/Icinga/Web/Hook.php +++ b/library/Icinga/Web/Hook.php @@ -1,6 +1,5 @@ - * @author Icinga-Web Team - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License + * Extend this class if you want to integrate your ticketing solution Icinga Web 2 */ abstract class TicketHook { /** - * Constructor must live without arguments right now + * Last error, if any * - * Therefore the constructor is final, we might change our opinion about - * this one far day + * @var string|null + */ + protected $lastError; + + /** + * Create a new ticket hook + * + * @see init() For hook initialization. */ final public function __construct() { @@ -28,16 +33,92 @@ abstract class TicketHook } /** - * Overwrite this function if you want to do some initialization stuff - * - * @return void + * Overwrite this function for hook initialization, e.g. loading the hook's config */ protected function init() { } + /** + * Set the hook as failed w/ the given message + * + * @param string $message Error message or error format string + * @param mixed ...$arg Format string argument + */ + private function fail($message) + { + $args = array_slice(func_get_args(), 1); + $lastError = vsprintf($message, $args); + Logger::debug($lastError); + $this->lastError = $lastError; + } + + /** + * Get the last error, if any + * + * @return string|null + */ + public function getLastError() + { + return $this->lastError; + } + + /** + * Get the pattern + * + * @return string + */ abstract public function getPattern(); + /** + * Create a link for each matched element in the subject text + * + * @param array $match Array of matched elements according to {@link getPattern()} + * + * @return string Replacement string + */ abstract public function createLink($match); + /** + * Create links w/ {@link createLink()} in the given text that matches to the subject from {@link getPattern()} + * + * In case of errors a debug message is recorded to the log and any subsequent call to {@link createLinks()} will + * be a no-op. + * + * @param string $text + * + * @return string + */ + final public function createLinks($text) + { + if ($this->lastError !== null) { + return $text; + } + + try { + $pattern = $this->getPattern(); + } catch (Exception $e) { + $this->fail('Can\'t create ticket links: Retrieving the pattern failed: %s', IcingaException::describe($e)); + return $text; + } + if (empty($pattern)) { + $this->fail('Can\'t create ticket links: Pattern is empty'); + return $text; + } + try { + $text = preg_replace_callback( + $pattern, + array($this, 'createLink'), + $text + ); + } catch (ErrorException $e) { + $this->fail('Can\'t create ticket links: Pattern is invalid: %s', IcingaException::describe($e)); + return $text; + } catch (Exception $e) { + $this->fail('Can\'t create ticket links: %s', IcingaException::describe($e)); + return $text; + } + + return $text; + } } diff --git a/library/Icinga/Web/Hook/TopBarHook.php b/library/Icinga/Web/Hook/TopBarHook.php deleted file mode 100644 index 183a0ca07..000000000 --- a/library/Icinga/Web/Hook/TopBarHook.php +++ /dev/null @@ -1,23 +0,0 @@ -view; } -} \ No newline at end of file +} diff --git a/library/Icinga/Web/JavaScript.php b/library/Icinga/Web/JavaScript.php index 9fa687042..8cce31bb0 100644 --- a/library/Icinga/Web/JavaScript.php +++ b/library/Icinga/Web/JavaScript.php @@ -1,6 +1,5 @@ getModuleManager()->getLoadedModules() as $name => $module) { - if ($module->hasJs()) { - $list[] = 'js/' . $name . '/module.js'; - } - } - return $list; - } - public static function sendMinified() { return self::send(true); @@ -86,7 +75,11 @@ class JavaScript foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) { if ($module->hasJs()) { - $jsFiles[] = $module->getJsFilename(); + foreach ($module->getJsFiles() as $path) { + if (file_exists($path)) { + $jsFiles[] = $path; + } + } } } $files = array_merge($vendorFiles, $jsFiles); @@ -114,7 +107,7 @@ class JavaScript } foreach ($jsFiles as $file) { - $js .= file_get_contents($file); + $js .= file_get_contents($file) . "\n\n\n"; } if ($minified) { diff --git a/library/Icinga/Web/LessCompiler.php b/library/Icinga/Web/LessCompiler.php index 791fdbab0..eae3f565c 100644 --- a/library/Icinga/Web/LessCompiler.php +++ b/library/Icinga/Web/LessCompiler.php @@ -1,6 +1,5 @@ lessc = new lessc(); + } - $this->lessc->setVariables( - array( - 'baseurl' => '\'' . Zend_Controller_Front::getInstance()->getBaseUrl(). '\'' - ) - ); + /** + * Disable the extendend import functionality + * + * @return $this + */ + public function disableExtendedImport() + { + $this->lessc->importDisabled = true; + return $this; } public function compress() @@ -92,14 +93,21 @@ class LessCompiler public function addModule($name, $module) { if ($module->hasCss()) { - $this->source .= "\n/* CSS: modules/$name/module.less */\n" + $contents = array(); + foreach ($module->getCssFiles() as $path) { + if (file_exists($path)) { + $contents[] = "/* CSS: modules/$name/$path */\n" . file_get_contents($path); + } + } + + $this->source .= '' . '.icinga-module.module-' . $name . " {\n" - . file_get_contents($module->getCssFilename()) - . "}\n\n" - ; + . join("\n\n", $contents) + . "}\n\n"; } + return $this; } diff --git a/library/Icinga/Web/Menu.php b/library/Icinga/Web/Menu.php index 18a17ea67..7992364b9 100644 --- a/library/Icinga/Web/Menu.php +++ b/library/Icinga/Web/Menu.php @@ -1,6 +1,5 @@ $value) { $method = 'set' . implode('', array_map('ucfirst', explode('_', strtolower($key)))); if ($key === 'renderer') { - $class = '\Icinga\Web\Menu\\' . $value; - if (!class_exists($class)) { - throw new ConfigurationError( - sprintf('ItemRenderer with class "%s" does not exist', $class) - ); + $value = '\\' . ltrim($value, '\\'); + if (class_exists($value)) { + $value = new $value; + } else { + $class = '\Icinga\Web\Menu' . $value; + if (!class_exists($class)) { + throw new ConfigurationError( + sprintf('ItemRenderer with class "%s" does not exist', $class) + ); + } + $value = new $class; } - $value = new $class; } if (method_exists($this, $method)) { $this->{$method}($value); @@ -191,13 +206,14 @@ class Menu implements RecursiveIterator */ public static function load() { - /** @var $menu \Icinga\Web\Menu */ $menu = new static('menu'); $menu->addMainMenuItems(); + $auth = Auth::getInstance(); $manager = Icinga::app()->getModuleManager(); foreach ($manager->getLoadedModules() as $module) { - /** @var $module \Icinga\Application\Modules\Module */ - $menu->mergeSubMenus($module->getMenuItems()); + if ($auth->hasPermission($manager::MODULE_PERMISSION_NS . $module->getName())) { + $menu->mergeSubMenus($module->getMenuItems()); + } } return $menu->order(); } @@ -207,7 +223,7 @@ class Menu implements RecursiveIterator */ protected function addMainMenuItems() { - $auth = Manager::getInstance(); + $auth = Auth::getInstance(); if ($auth->isAuthenticated()) { @@ -218,37 +234,70 @@ class Menu implements RecursiveIterator )); $section = $this->add(t('System'), array( - 'icon' => 'wrench', - 'priority' => 200 + 'icon' => 'services', + 'priority' => 700, + 'renderer' => 'ProblemMenuItemRenderer' )); - $section->add(t('Configuration'), array( - 'url' => 'config', - 'priority' => 300 + $section->add(t('About'), array( + 'url' => 'about', + 'priority' => 701 )); - $section->add(t('Modules'), array( - 'url' => 'config/modules', - 'priority' => 400 - )); - if (Logger::writesToFile()) { $section->add(t('Application Log'), array( 'url' => 'list/applicationlog', - 'priority' => 500 + 'priority' => 710 )); } + $section = $this->add(t('Configuration'), array( + 'icon' => 'wrench', + 'permission' => 'config/*', + 'priority' => 800 + )); + $section->add(t('Application'), array( + 'url' => 'config', + 'permission' => 'config/application/*', + 'priority' => 810 + )); + $section->add(t('Authentication'), array( + 'url' => 'config/userbackend', + 'permission' => 'config/authentication/*', + 'priority' => 820 + )); + $section->add(t('Roles'), array( + 'url' => 'role/list', + 'permission' => 'config/authentication/roles/show', + 'priority' => 830 + )); + $section->add(t('Users'), array( + 'url' => 'user/list', + 'permission' => 'config/authentication/users/show', + 'priority' => 840 + )); + $section->add(t('Usergroups'), array( + 'url' => 'group/list', + 'permission' => 'config/authentication/groups/show', + 'priority' => 850 + )); + $section->add(t('Modules'), array( + 'url' => 'config/modules', + 'permission' => 'config/modules', + 'priority' => 890 + )); + $section = $this->add($auth->getUser()->getUsername(), array( 'icon' => 'user', - 'priority' => 600 + 'priority' => 900 )); $section->add(t('Preferences'), array( 'url' => 'preference', - 'priority' => 601 + 'priority' => 910 )); $section->add(t('Logout'), array( 'url' => 'authentication/logout', - 'priority' => 700 + 'priority' => 990, + 'renderer' => 'ForeignMenuItemRenderer' )); } } @@ -355,21 +404,20 @@ class Menu implements RecursiveIterator */ public function setUrl($url) { - if ($url instanceof Url) { - $this->url = $url; - } else { - $this->url = Url::fromPath($url); - } + $this->url = $url; return $this; } /** * Return the url of this menu * - * @return string + * @return Url|null */ public function getUrl() { + if ($this->url !== null && ! $this->url instanceof Url) { + $this->url = Url::fromPath($this->url); + } return $this->url; } @@ -442,15 +490,47 @@ class Menu implements RecursiveIterator } /** - * Set required Permissions + * Get the permission a user is required to have granted to display the menu item * - * @param $permission + * @return string|null + */ + public function getPermission() + { + return $this->permission; + } + + /** + * Get parent menu + * + * @return \Icinga\Web\Menu + */ + public function getParent() + { + return $this->parent; + } + + /** + * Get submenus + * + * @return array + */ + public function getSubMenus() + { + return $this->subMenus; + } + + /** + * Set permission a user is required to have granted to display the menu item + * + * If a permission is set, authentication is of course required. + * + * @param string $permission * * @return $this */ - public function requirePermission($permission) + public function setPermission($permission) { - // Not implemented yet + $this->permission = (string) $permission; return $this; } @@ -614,7 +694,7 @@ class Menu implements RecursiveIterator * * @param array $menus The menus to load, as key-value array * - * @return static + * @return $this */ protected function loadSubMenus(array $menus) { diff --git a/library/Icinga/Web/Menu/ForeignMenuItemRenderer.php b/library/Icinga/Web/Menu/ForeignMenuItemRenderer.php new file mode 100644 index 000000000..b898b4d08 --- /dev/null +++ b/library/Icinga/Web/Menu/ForeignMenuItemRenderer.php @@ -0,0 +1,17 @@ + '_self' + ); +} diff --git a/library/Icinga/Web/Menu/MenuItemRenderer.php b/library/Icinga/Web/Menu/MenuItemRenderer.php index da09d7808..d40650d91 100644 --- a/library/Icinga/Web/Menu/MenuItemRenderer.php +++ b/library/Icinga/Web/Menu/MenuItemRenderer.php @@ -1,14 +1,110 @@ element specific attributes + * + * @var array + */ + protected $attributes = array(); + + /** + * View + * + * @var View|null + */ + protected $view; + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = Icinga::app()->getViewRenderer()->view; + } + return $this->view; + } + + /** + * Renders the html content of a single menu item + * + * @param Menu $menu + * + * @return string + */ + public function render(Menu $menu) + { + return $this->createLink($menu); + } + + /** + * Creates a menu item link element + * + * @param Menu $menu + * + * @return string + */ + public function createLink(Menu $menu) + { + if ($menu->getIcon() && strpos($menu->getIcon(), '.') === false) { + return sprintf( + '%s', + $menu->getUrl() ? : '#', + $this->getAttributes(), + $menu->getIcon(), + $this->getView()->escape($menu->getTitle()) + ); + } + + return sprintf( + '%s%s', + $menu->getUrl() ? : '#', + $this->getAttributes(), + $menu->getIcon() ? ' ' : '', + $this->getView()->escape($menu->getTitle()) + ); + } + + /** + * Returns element specific attributes if present + * + * @return string + */ + protected function getAttributes() + { + $attributes = ''; + $view = $this->getView(); + foreach ($this->attributes as $attribute => $value) { + $attributes .= ' ' . $view->escape($attribute) . '="' . $view->escape($value) . '"'; + } + return $attributes; + } } diff --git a/library/Icinga/Web/Menu/PermittedMenuItemFilter.php b/library/Icinga/Web/Menu/PermittedMenuItemFilter.php new file mode 100644 index 000000000..829506c0b --- /dev/null +++ b/library/Icinga/Web/Menu/PermittedMenuItemFilter.php @@ -0,0 +1,33 @@ +current(); + /** @var Menu $item */ + if (($permission = $item->getPermission()) !== null) { + $auth = Auth::getInstance(); + if (! $auth->isAuthenticated()) { + // Don't accept menu item because user is not authenticated and the menu item requires a permission + return false; + } + if (! $auth->getUser()->can($permission)) { + return false; + } + } + // Accept menu item if it does not require a permission + return true; + } +} diff --git a/library/Icinga/Web/Menu/ProblemMenuItemRenderer.php b/library/Icinga/Web/Menu/ProblemMenuItemRenderer.php index 58689547f..010bdc034 100644 --- a/library/Icinga/Web/Menu/ProblemMenuItemRenderer.php +++ b/library/Icinga/Web/Menu/ProblemMenuItemRenderer.php @@ -1,11 +1,64 @@ getParent() !== null && $menu->hasSubMenus()) { + /** @var $submenu Menu */ + foreach ($menu->getSubMenus() as $submenu) { + $renderer = $submenu->getRenderer(); + if (method_exists($renderer, 'getSummary')) { + if ($renderer->getSummary() !== null) { + $this->summary[] = $renderer->getSummary(); + } + } + } + } + return $this->getBadge() . $this->createLink($menu); + } + + /** + * Get the problem badge + * + * @return string + */ + protected function getBadge() + { + if (count($this->summary) > 0) { + $problems = 0; + $titles = array(); + + foreach ($this->summary as $summary) { + $problems += $summary['problems']; + $titles[] = $summary['title']; + } + + return sprintf( + '
    %s
    ', + implode(', ', $titles), + $problems + ); + } + return ''; + } } diff --git a/library/Icinga/Web/MenuRenderer.php b/library/Icinga/Web/MenuRenderer.php index 8502e5215..7b6ad9856 100644 --- a/library/Icinga/Web/MenuRenderer.php +++ b/library/Icinga/Web/MenuRenderer.php @@ -1,12 +1,13 @@ url = Url::fromPath($url); } - parent::__construct($menu, RecursiveIteratorIterator::CHILD_FIRST); + $this->defaultRenderer = new MenuItemRenderer(); + parent::__construct(new PermittedMenuItemFilter($menu), RecursiveIteratorIterator::CHILD_FIRST); } /** @@ -62,7 +69,7 @@ class MenuRenderer extends RecursiveIteratorIterator */ public function beginIteration() { - $this->tags[] = '
      '; + $this->tags[] = '
        '; } /** @@ -111,23 +118,17 @@ class MenuRenderer extends RecursiveIteratorIterator try { return $child->getRenderer()->render($child); } catch (Exception $e) { - Logger::error('Could not invoke custom renderer. Exception: '. $e->getMessage()); + Logger::error( + 'Could not invoke custom menu renderer. %s in %s:%d with message: %s', + get_class($e), + $e->getFile(), + $e->getLine(), + $e->getMessage() + ); } } - if ($child->getIcon() && strpos($child->getIcon(), '.') === false) { - return sprintf( - '%s', - $child->getUrl() ?: '#', - $child->getIcon(), - htmlspecialchars($child->getTitle()) - ); - } - return sprintf( - '%s%s', - $child->getUrl() ?: '#', - $child->getIcon() ? ' ' : '', - htmlspecialchars($child->getTitle()) - ); + + return $this->defaultRenderer->render($child); } /** diff --git a/library/Icinga/Web/Notification.php b/library/Icinga/Web/Notification.php index b046476b3..c4ab724da 100644 --- a/library/Icinga/Web/Notification.php +++ b/library/Icinga/Web/Notification.php @@ -1,6 +1,5 @@ addMessage($msg, 'info'); - } + /** + * Notification messages + * + * @var array + */ + protected $messages = array(); - public static function success($msg) - { - self::getInstance()->addMessage($msg, 'success'); - } + /** + * Session + * + * @var Session + */ + protected $session; - public static function warning($msg) + /** + * Create the notification instance + */ + final private function __construct() { - self::getInstance()->addMessage($msg, 'warning'); - } - - public static function error($msg) - { - self::getInstance()->addMessage($msg, 'error'); - } - - protected function addMessage($message, $type = 'info') - { - if (! in_array( - $type, - array( - 'info', - 'error', - 'warning', - 'success' - ) - )) { - throw new ProgrammingError( - '"%s" is not a valid notification type', - $type - ); - } - - if ($this->isCli) { - $msg = sprintf('[%s] %s', $type, $message); - switch ($type) { - case 'info': - case 'success': - Logger::info($msg); - break; - case 'warning': - Logger::warn($msg); - break; - case 'error': - Logger::error($msg); - break; - } + if (Platform::isCli()) { + $this->isCli = true; return; } - $messages = & Session::getSession()->getByRef('messages'); - $messages[] = (object) array( - 'type' => $type, - 'message' => $message, - ); - } - - public function hasMessages() - { - $session = Session::getSession(); - return false === empty($session->messages); - } - - public function getMessages() - { - $session = Session::getSession(); - $messages = $session->messages; - if (false === empty($messages)) { - $session->messages = array(); - } - - return $messages; - } - - final private function __construct() - { - $session = Session::getSession(); - if (!is_array($session->get('messages'))) { - $session->messages = array(); - } - - if (Platform::isCli()) { - $this->is_cli = true; + $this->session = Session::getSession(); + $messages = $this->session->get(self::SESSION_KEY); + if (is_array($messages)) { + $this->messages = $messages; + $this->session->delete(self::SESSION_KEY); + $this->session->write(); } } + /** + * Get the Notification instance + * + * @return Notification + */ public static function getInstance() { if (self::$instance === null) { - self::$instance = new Notification(); + self::$instance = new self(); } return self::$instance; } + + /** + * Add info notification + * + * @param string $msg + */ + public static function info($msg) + { + self::getInstance()->addMessage($msg, self::INFO); + } + + /** + * Add error notification + * + * @param string $msg + */ + public static function error($msg) + { + self::getInstance()->addMessage($msg, self::ERROR); + } + + /** + * Add success notification + * + * @param string $msg + */ + public static function success($msg) + { + self::getInstance()->addMessage($msg, self::SUCCESS); + } + + /** + * Add warning notification + * + * @param string $msg + */ + public static function warning($msg) + { + self::getInstance()->addMessage($msg, self::WARNING); + } + + /** + * Add a notification message + * + * @param string $message + * @param string $type + */ + protected function addMessage($message, $type = self::INFO) + { + if ($this->isCli) { + $msg = sprintf('[%s] %s', $type, $message); + switch ($type) { + case self::INFO: + case self::SUCCESS: + Logger::info($msg); + break; + case self::ERROR: + Logger::error($msg); + break; + case self::WARNING: + Logger::warning($msg); + break; + } + } else { + $this->messages[] = (object) array( + 'type' => $type, + 'message' => $message, + ); + } + } + + /** + * Pop the notification messages + * + * @return array + */ + public function popMessages() + { + $messages = $this->messages; + $this->messages = array(); + return $messages; + } + + /** + * Get whether notification messages have been added + * + * @return bool + */ + public function hasMessages() + { + return ! empty($this->messages); + } + + /** + * Destroy the notification instance + */ + final public function __destruct() + { + if ($this->isCli) { + return; + } + if ($this->hasMessages() && $this->session->get('messages') !== $this->messages) { + $this->session->set(self::SESSION_KEY, $this->messages); + $this->session->write(); + } + } } diff --git a/library/Icinga/Web/Paginator/Adapter/QueryAdapter.php b/library/Icinga/Web/Paginator/Adapter/QueryAdapter.php index 03f8f1cc5..3f2b71cc3 100644 --- a/library/Icinga/Web/Paginator/Adapter/QueryAdapter.php +++ b/library/Icinga/Web/Paginator/Adapter/QueryAdapter.php @@ -1,48 +1,67 @@ query = $query; + $this->setQuery($query); } /** - * Returns an array of items for a page. + * Set the query to paginate * - * @param integer $offset Page offset - * @param integer $itemCountPerPage Number of items per page - * @return array + * @param QueryInterface $query + * + * @return $this + */ + public function setQuery(QueryInterface $query) + { + $this->query = $query; + return $this; + } + + /** + * Return the query being paginated + * + * @return QueryInterface + */ + public function getQuery() + { + return $this->query; + } + + /** + * Fetch and return the rows in the given range of the query result + * + * @param int $offset Page offset + * @param int $itemCountPerPage Number of items per page + * + * @return array */ public function getItems($offset, $itemCountPerPage) { @@ -50,15 +69,16 @@ class QueryAdapter implements Zend_Paginator_Adapter_Interface } /** - * Returns the total number of items in the query result. + * Return the total number of items in the query result * - * @return integer + * @return int */ public function count() { if ($this->count === null) { $this->count = $this->query->count(); } + return $this->count; } } diff --git a/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php b/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php index d861dbe29..ff05d9d6c 100644 --- a/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php +++ b/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php @@ -1,6 +1,5 @@ getHeader('Accept') === 'application/json'; + } + + /** + * Get the request URL + * + * @return Url + */ public function getUrl() { if ($this->url === null) { @@ -30,22 +56,44 @@ class Request extends Zend_Controller_Request_Http } /** - * Setter for user + * Get the user if authenticated * - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - - /** - * Getter for user - * - * @return User + * @return User|null */ public function getUser() { return $this->user; } + + /** + * Set the authenticated user + * + * @param User $user + * + * @return $this + */ + public function setUser(User $user) + { + $this->user = $user; + return $this; + } + + /** + * Makes an ID unique to this request, to prevent id collisions in different containers + * + * Call this whenever an ID might show up multiple times in different containers. This function is useful + * for ensuring unique ids on sites, even if we combine the HTML of different requests into one site, + * while still being able to reference elements uniquely in the same request. + * + * @param string $id + * + * @return string The id suffixed w/ an identifier unique to this request + */ + public function protectId($id) + { + if (! isset($this->uniqueId)) { + $this->uniqueId = Window::generateId(); + } + return $id . '-' . $this->uniqueId; + } } diff --git a/library/Icinga/Web/Response.php b/library/Icinga/Web/Response.php index d0d8912e6..b11ecd78e 100644 --- a/library/Icinga/Web/Response.php +++ b/library/Icinga/Web/Response.php @@ -1,6 +1,5 @@ redirectUrl; + } + + /** + * Set the redirect URL + * + * Unlike {@link setRedirect()} this method only sets a redirect URL on the response for later usage. + * {@link prepare()} will take care of the correct redirect handling and HTTP headers on XHR and "normal" browser + * requests. + * + * @param string|Url $redirectUrl + * + * @return $this + */ + protected function setRedirectUrl($redirectUrl) + { + if (! $redirectUrl instanceof Url) { + $redirectUrl = Url::fromPath((string) $redirectUrl); + } + $redirectUrl->getParams()->setSeparator('&'); + $this->redirectUrl = $redirectUrl; + return $this; + } + + /** + * Get the request + * + * @return Request + */ + public function getRequest() + { + if ($this->request === null) { + $this->request = Icinga::app()->getRequest(); + } + return $this->request; + } + + /** + * Get whether to send the rerender layout header on XHR + * + * @return bool + */ + public function getRerenderLayout() + { + return $this->rerenderLayout; + } + + /** + * Get whether to send the rerender layout header on XHR + * + * @param bool $rerenderLayout + * + * @return $this + */ + public function setRerenderLayout($rerenderLayout = true) + { + $this->rerenderLayout = (bool) $rerenderLayout; + return $this; + } + + /** + * Prepare the request before sending + */ + protected function prepare() + { + $redirectUrl = $this->getRedirectUrl(); + if ($this->getRequest()->isXmlHttpRequest()) { + if ($redirectUrl !== null) { + $this->setHeader('X-Icinga-Redirect', rawurlencode($redirectUrl->getAbsoluteUrl()), true); + if ($this->getRerenderLayout()) { + $this->setHeader('X-Icinga-Rerender-Layout', 'yes', true); + } + } + if ($this->getRerenderLayout()) { + $this->setHeader('X-Icinga-Container', 'layout', true); + } + } else { + if ($redirectUrl !== null) { + $this->setRedirect($redirectUrl->getAbsoluteUrl()); + } + } + } + + /** + * Redirect to the given URL and exit immediately + * + * @param string|Url $url + */ public function redirectAndExit($url) { - if (! $url instanceof Url) { - $url = Url::fromPath($url); - } - $url->getParams()->setSeparator('&'); - - if (Icinga::app()->getFrontController()->getRequest()->isXmlHttpRequest()) { - $this->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl())); - } else { - $this->setRedirect($url->getAbsoluteUrl()); - } + $this->setRedirectUrl($url); $session = Session::getSession(); if ($session->hasChanged()) { @@ -30,4 +136,13 @@ class Response extends Zend_Controller_Response_Http $this->sendHeaders(); exit; } + + /** + * {@inheritdoc} + */ + public function sendHeaders() + { + $this->prepare(); + return parent::sendHeaders(); + } } diff --git a/library/Icinga/Web/Session.php b/library/Icinga/Web/Session.php index 4723903e9..fec14aaf4 100644 --- a/library/Icinga/Web/Session.php +++ b/library/Icinga/Web/Session.php @@ -1,6 +1,5 @@ exists()) { diff --git a/library/Icinga/Web/Session/Session.php b/library/Icinga/Web/Session/Session.php index ab73dcac7..48c065743 100644 --- a/library/Icinga/Web/Session/Session.php +++ b/library/Icinga/Web/Session/Session.php @@ -1,6 +1,5 @@ namespaces[$identifier])) { - if (in_array($identifier, $this->removedNamespaces)) { - unset($this->removedNamespaces[array_search($identifier, $this->removedNamespaces)]); + if (in_array($identifier, $this->removedNamespaces, true)) { + unset($this->removedNamespaces[array_search($identifier, $this->removedNamespaces, true)]); } $this->namespaces[$identifier] = new SessionNamespace(); diff --git a/library/Icinga/Web/Session/SessionNamespace.php b/library/Icinga/Web/Session/SessionNamespace.php index c72d69c36..bda22fd80 100644 --- a/library/Icinga/Web/Session/SessionNamespace.php +++ b/library/Icinga/Web/Session/SessionNamespace.php @@ -1,6 +1,5 @@ values[$key] = $value; - if (in_array($key, $this->removed)) { - unset($this->removed[array_search($key, $this->removed)]); + if (in_array($key, $this->removed, true)) { + unset($this->removed[array_search($key, $this->removed, true)]); } return $this; @@ -113,8 +112,8 @@ class SessionNamespace implements IteratorAggregate { $this->values[$key] = & $value; - if (in_array($key, $this->removed)) { - unset($this->removed[array_search($key, $this->removed)]); + if (in_array($key, $this->removed, true)) { + unset($this->removed[array_search($key, $this->removed, true)]); } return $this; diff --git a/library/Icinga/Web/StyleSheet.php b/library/Icinga/Web/StyleSheet.php index 719d1c829..af807c6e4 100644 --- a/library/Icinga/Web/StyleSheet.php +++ b/library/Icinga/Web/StyleSheet.php @@ -1,6 +1,5 @@ getModuleManager()->getLoadedModules() as $name => $module) { if ($module->hasCss()) { - $files[] = $module->getCssFilename(); + foreach ($module->getCssFiles() as $path) { + if (file_exists($path)) { + $files[] = $path; + } + } } } @@ -96,7 +101,9 @@ class StyleSheet $cache->send($cacheFile); return; } + $less = new LessCompiler(); + $less->disableExtendedImport(); foreach ($lessFiles as $file) { $less->addFile($file); } diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index a3af4a338..7c3829537 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -1,6 +1,5 @@ isCli()) { return new FakeRequest(); } else { - return $app->getFrontController()->getRequest(); + return $app->getRequest(); } } @@ -164,7 +163,7 @@ class Url * * @param string $baseUrl The url path to use as the Url Base * - * @return self + * @return $this */ public function setBaseUrl($baseUrl) { @@ -191,7 +190,7 @@ class Url * * @param string $path The path to set * - * @return self + * @return $this */ public function setPath($path) { @@ -251,7 +250,7 @@ class Url * * @param array $params The parameters to add * - * @return self + * @return $this */ public function addParams(array $params) { @@ -267,7 +266,7 @@ class Url * * @param array $params The parameters to set * - * @return self + * @return $this */ public function overwriteParams(array $params) { @@ -283,7 +282,7 @@ class Url * * @param UrlParams|array $params The new parameters to use for the query part * - * @return self + * @return $this */ public function setParams($params) { @@ -344,7 +343,7 @@ class Url * @param string $param The query parameter name * @param array|string $value An array or string to set as the parameter value * - * @return self + * @return $this */ public function setParam($param, $value = true) { @@ -357,7 +356,7 @@ class Url * * @param string $anchor The site's anchor string without the '#' * - * @return self + * @return $this */ public function setAnchor($anchor) { @@ -370,7 +369,7 @@ class Url * * @param string|array $keyOrArrayOfKeys An array of strings or a string representing the key(s) * of the parameters to be removed - * @return self + * @return $this */ public function remove($keyOrArrayOfKeys) { diff --git a/library/Icinga/Web/UrlParams.php b/library/Icinga/Web/UrlParams.php index 1c839960d..504c076be 100644 --- a/library/Icinga/Web/UrlParams.php +++ b/library/Icinga/Web/UrlParams.php @@ -1,9 +1,10 @@ params[ end($this->index[$param]) ][ 1 ]); } + /** + * Require a parameter + * + * @param string $name Name of the parameter + * @param bool $strict Whether the parameter's value must not be the empty string + * + * @return mixed + * + * @throws MissingParameterException If the parameter was not given + */ + public function getRequired($name, $strict = true) + { + if ($this->has($name)) { + $value = $this->get($name); + if (! $strict || strlen($value) > 0) { + return $value; + } + } + $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name); + $e->setParameter($name); + throw $e; + } + /** * Get all instances of the given parameter * @@ -114,6 +138,30 @@ class UrlParams return $ret; } + /** + * Require and remove a parameter + * + * @param string $name Name of the parameter + * @param bool $strict Whether the parameter's value must not be the empty string + * + * @return mixed + * + * @throws MissingParameterException If the parameter was not given + */ + public function shiftRequired($name, $strict = true) + { + if ($this->has($name)) { + $value = $this->get($name); + if (! $strict || strlen($value) > 0) { + $this->shift($name); + return $value; + } + } + $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name); + $e->setParameter($name); + throw $e; + } + public function addEncoded($param, $value = true) { $this->params[] = array($param, $this->cleanupValue($value)); @@ -135,7 +183,7 @@ class UrlParams * @param string $param The parameter you're interested in * @param string $value The value to be stored * - * @return self + * @return $this */ public function add($param, $value = true) { @@ -151,7 +199,7 @@ class UrlParams * @param string $param Parameter name or param/value list * @param string $value The value to be stored * - * @return self + * @return $this */ public function addValues($param, $values = null) { @@ -207,7 +255,7 @@ class UrlParams * @param string $param The parameter you're interested in * @param string $value The value to be stored * - * @return self + * @return $this */ public function unshift($param, $value) { @@ -224,7 +272,7 @@ class UrlParams * @param string $param The parameter you want to set * @param string $value The value to be stored * - * @return self + * @return $this */ public function set($param, $value) { @@ -243,7 +291,7 @@ class UrlParams ); $this->reIndexAll(); - return $this; + return $this; } public function remove($param) @@ -326,9 +374,29 @@ class UrlParams } } - public function toArray() + /** + * Return the parameters of this url as sequenced or associative array + * + * @param bool $sequenced + * + * @return array + */ + public function toArray($sequenced = true) { - return $this->params; + if ($sequenced) { + return $this->params; + } + + $params = array(); + foreach ($this->params as $param) { + if ($param[1] === true) { + $params[] = $param[0]; + } else { + $params[$param[0]] = $param[1]; + } + } + + return $params; } public function toString($separator = null) diff --git a/library/Icinga/Web/View.php b/library/Icinga/Web/View.php index 7a954934a..766516768 100644 --- a/library/Icinga/Web/View.php +++ b/library/Icinga/Web/View.php @@ -1,13 +1,13 @@ auth === null) { + $this->auth = Auth::getInstance(); + } + return $this->auth; + } + + /** + * Whether the current user has the given permission + * + * @param string $permission Name of the permission + * + * @return bool + */ + public function hasPermission($permission) + { + return $this->Auth()->hasPermission($permission); + } + /** * Use to include the view script in a scope that only allows public * members. diff --git a/library/Icinga/Web/View/DateTimeRenderer.php b/library/Icinga/Web/View/DateTimeRenderer.php deleted file mode 100644 index 639befb7f..000000000 --- a/library/Icinga/Web/View/DateTimeRenderer.php +++ /dev/null @@ -1,205 +0,0 @@ -future = $future; - $this->now = new DateTime(); - $this->dateTime = $this->normalize($dateTime); - $this->detectType(); - } - - /** - * Creates a new DateTimeRenderer - * - * @param DateTime|int $dateTime - * @param bool $future - * - * @return DateTimeRenderer - */ - public static function create($dateTime, $future = false) - { - return new static($dateTime, $future); - } - - /** - * Detects the DateTime context - */ - protected function detectType() - { - if ($this->now->format('Y-m-d') !== $this->dateTime->format('Y-m-d')) { - $this->type = self::TYPE_DATETIME; - return; - } - - if ( - $this->now->format('Y-m-d') === $this->dateTime->format('Y-m-d') && - (abs($this->now->getTimestamp() - $this->dateTime->getTimestamp()) >= self::HOUR) - ) { - $this->type = self::TYPE_TIME; - return; - } - - if ( - $this->now->format('Y-m-d') === $this->dateTime->format('Y-m-d') && - (abs($this->now->getTimestamp() - $this->dateTime->getTimestamp()) < self::HOUR) - ) { - $this->type = self::TYPE_TIMESPAN; - return; - } - } - - /** - * Normalizes the given DateTime - * - * @param DateTime|int $dateTime - * - * @return DateTime - */ - public static function normalize($dateTime) - { - if (! ($dt = $dateTime) instanceof DateTime) { - $dt = new DateTime(); - $dt->setTimestamp($dateTime); - } - return $dt; - } - - /** - * Checks whether DateTime is a date with time - * - * @return bool - */ - public function isDateTime() - { - return $this->type === self::TYPE_DATETIME; - } - - /** - * Checks whether DateTime is a time of the current day - * - * @return bool - */ - public function isTime() - { - return $this->type === self::TYPE_TIME; - } - - /** - * Checks whether DateTime is in a defined interval - * - * @return bool - */ - public function isTimespan() - { - return $this->type === self::TYPE_TIMESPAN; - } - - /** - * Returns the type of the DateTime - * - * @return mixed - */ - public function getType() - { - return $this->type; - } - - /** - * Renders the DateTime on the basis of the type and returns suited text - * - * @param string $dateTimeText - * @param string $timeText - * @param string $timespanText - * - * @return string - */ - public function render($dateTimeText, $timeText, $timespanText) - { - if ($this->isDateTime()) { - return sprintf($dateTimeText, $this); - } elseif ($this->isTime()) { - return sprintf($timeText, $this); - } elseif ($this->isTimespan()) { - return sprintf($timespanText, $this); - } - - return $dateTimeText; - } - - /** - * Returns a rendered html wrapped text - * - * @return string - */ - public function __toString() - { - switch ($this->type) { - case self::TYPE_DATETIME: - $format = $this->dateTime->format('d.m.Y - H:i:s'); - break; - case self::TYPE_TIME: - $format = $this->dateTime->format('H:i:s'); - break; - case self::TYPE_TIMESPAN: - $format = $this->dateTime->diff($this->now)->format(t('%im %ss', 'timespan')); - break; - default: - $format = $this->dateTime->format('d.m.Y - H:i:s'); - break; - } - - $css = ''; - if ($this->type === self::TYPE_TIMESPAN) { - $css = $this->future === true ? 'timeuntil' : 'timesince'; - } - - return sprintf( - '%s', - $css, - $this->dateTime->format('d.m.Y - H:i:s'), - $format - ); - } -} diff --git a/library/Icinga/Web/View/helpers/format.php b/library/Icinga/Web/View/helpers/format.php index a203f2fb3..73257d079 100644 --- a/library/Icinga/Web/View/helpers/format.php +++ b/library/Icinga/Web/View/helpers/format.php @@ -1,50 +1,72 @@ addHelperFunction('format', function () { return Format::getInstance(); }); -$this->addHelperFunction('timeSince', function ($timestamp) { +$this->addHelperFunction('formatDate', function ($date) { + if (! $date) { + return ''; + } + return DateFormatter::formatDate($date); +}); + +$this->addHelperFunction('formatDateTime', function ($dateTime) { + if (! $dateTime) { + return ''; + } + return DateFormatter::formatDateTime($dateTime); +}); + +$this->addHelperFunction('formatDuration', function ($seconds) { + if (! $seconds) { + return ''; + } + return DateFormatter::formatDuration($seconds); +}); + +$this->addHelperFunction('formatTime', function ($time) { + if (! $time) { + return ''; + } + return DateFormatter::formatTime($time); +}); + +$this->addHelperFunction('timeAgo', function ($time, $timeOnly = false) { + if (! $time) { + return ''; + } return sprintf( - '%s', - date('Y-m-d H:i:s', $timestamp), // TODO: internationalized format - Format::timeSince($timestamp) + '%s', + DateFormatter::formatDateTime($time), + DateFormatter::timeAgo($time, $timeOnly) ); }); -$this->addHelperFunction('prefixedTimeSince', function ($timestamp, $ucfirst = false) { +$this->addHelperFunction('timeSince', function ($time, $timeOnly = false) { + if (! $time) { + return ''; + } return sprintf( - '%s', - date('Y-m-d H:i:s', $timestamp), // TODO: internationalized format - Format::prefixedTimeSince($timestamp, $ucfirst) + '%s', + DateFormatter::formatDateTime($time), + DateFormatter::timeSince($time, $timeOnly) ); }); -$this->addHelperFunction('timeUntil', function ($timestamp) { - if (! $timestamp) return ''; +$this->addHelperFunction('timeUntil', function ($time, $timeOnly = false) { + if (! $time) { + return ''; + } return sprintf( - '%s', - date('Y-m-d H:i:s', $timestamp), // TODO: internationalized format - Format::timeUntil($timestamp) + '%s', + DateFormatter::formatDateTime($time), + DateFormatter::timeUntil($time, $timeOnly) ); }); - -$this->addHelperFunction('prefixedTimeUntil', function ($timestamp, $ucfirst = false) { - if (! $timestamp) return ''; - return sprintf( - '%s', - date('Y-m-d H:i:s', $timestamp), // TODO: internationalized format - Format::prefixedTimeUntil($timestamp, $ucfirst) - ); -}); - -$this->addHelperFunction('dateTimeRenderer', function ($dateTimeOrTimestamp, $future = false) { - return DateTimeRenderer::create($dateTimeOrTimestamp, $future); -}); diff --git a/library/Icinga/Web/View/helpers/generic.php b/library/Icinga/Web/View/helpers/generic.php index 8ab934e0e..59fa90443 100644 --- a/library/Icinga/Web/View/helpers/generic.php +++ b/library/Icinga/Web/View/helpers/generic.php @@ -1,14 +1,13 @@ addHelperFunction('auth', function () { - return Manager::getInstance(); + return Auth::getInstance(); }); $this->addHelperFunction('widget', function ($name, $options = null) { diff --git a/library/Icinga/Web/View/helpers/string.php b/library/Icinga/Web/View/helpers/string.php new file mode 100644 index 000000000..1d79d885a --- /dev/null +++ b/library/Icinga/Web/View/helpers/string.php @@ -0,0 +1,8 @@ +addHelperFunction('ellipsis', function ($string, $maxLength, $ellipsis = '...') { + return String::ellipsis($string, $maxLength, $ellipsis); +}); diff --git a/library/Icinga/Web/View/helpers/url.php b/library/Icinga/Web/View/helpers/url.php index 901e68803..06ace7f19 100644 --- a/library/Icinga/Web/View/helpers/url.php +++ b/library/Icinga/Web/View/helpers/url.php @@ -1,6 +1,5 @@ addHelperFunction('url', function ($path = null, $params = null) { } else { $url = Url::fromPath($path); } + if ($params !== null) { + if ($url === $path) { + $url = clone $url; + } + $url->overwriteParams($params); } return $url; }); +$this->addHelperFunction('qlink', function ($title, $url, $params = null, $properties = null, $escape = true) use ($view) { + $icon = ''; + if ($properties) { + if (array_key_exists('title', $properties) && !array_key_exists('aria-label', $properties)) { + $properties['aria-label'] = $properties['title']; + } + + if (array_key_exists('icon', $properties)) { + $icon = $view->icon($properties['icon']); + unset($properties['icon']); + } + } -$this->addHelperFunction('qlink', function ($title, $url, $params = null, $properties = array()) use ($view) { return sprintf( '%s', $view->url($url, $params), $view->propertiesToString($properties), - $view->escape($title) + $icon . ($escape ? $view->escape($title) : $title) ); }); -$this->addHelperFunction('img', function ($url, array $properties = array()) use ($view) { +$this->addHelperFunction('img', function ($url, $params = null, array $properties = array()) use ($view) { if (! array_key_exists('alt', $properties)) { $properties['alt'] = ''; } + $ariaHidden = array_key_exists('aria-hidden', $properties) ? $properties['aria-hidden'] : null; + if (array_key_exists('title', $properties)) { + if (! array_key_exists('aria-label', $properties) && $ariaHidden !== 'true') { + $properties['aria-label'] = $properties['title']; + } + } elseif ($ariaHidden === null) { + $properties['aria-hidden'] = 'true'; + } + return sprintf( '', - $view->url($url), + $view->url($url, $params), $view->propertiesToString($properties) ); }); $this->addHelperFunction('icon', function ($img, $title = null, array $properties = array()) use ($view) { - $isClass = strpos($img, '.') === false; - $class = null; - - if ($isClass) { - $class = 'icon-' . $img; - } else { - $class = 'icon'; - } - if ($title !== null) { - $properties['alt'] = $title; - $properties['title'] = $title; - } - - if ($class !== null) { - if (isset($props['class'])) { - $properties['class'] .= ' ' . $class; + if (strpos($img, '.') !== false) { + if (array_key_exists('class', $properties)) { + $properties['class'] .= ' icon'; } else { - $properties['class'] = $class; + $properties['class'] = 'icon'; } - } - if ($isClass) { - return sprintf('', $view->propertiesToString($properties)); - } else { return $view->img('img/icons/' . $img, $properties); } + + $ariaHidden = array_key_exists('aria-hidden', $properties) ? $properties['aria-hidden'] : null; + if ($title !== null) { + $properties['role'] = 'img'; + $properties['title'] = $title; + + if (! array_key_exists('aria-label', $properties) && $ariaHidden !== 'true') { + $properties['aria-label'] = $title; + } + } elseif ($ariaHidden === null) { + $properties['aria-hidden'] = 'true'; + } + + if (isset($properties['class'])) { + $properties['class'] .= ' icon-' . $img; + } else { + $properties['class'] = 'icon-' . $img; + } + + return sprintf('', $view->propertiesToString($properties)); }); $this->addHelperFunction('propertiesToString', function ($properties) use ($view) { diff --git a/library/Icinga/Web/ViewStream.php b/library/Icinga/Web/ViewStream.php index e5dacd879..f481f2844 100644 --- a/library/Icinga/Web/ViewStream.php +++ b/library/Icinga/Web/ViewStream.php @@ -1,6 +1,5 @@ data) && $this->data[$day]['value'] > 0) { $entry = $this->data[$day]; - return' '; - } else { return ''; + } else { + return ''; } } @@ -268,7 +269,7 @@ class HistoryColorGrid extends AbstractWidget { } $week++; } - if ($day > cal_days_in_month(CAL_GREGORIAN, $month, $year)) { + if ($day > date('t', mktime(0, 0, 0, $month, 1, $year))) { $month++; if ($month > 12) { $year++; @@ -312,7 +313,7 @@ class HistoryColorGrid extends AbstractWidget { private function monthName($month, $year) { // TODO: find a way to render years without messing up the layout - $dt = DateTimeFactory::create($year . '-' . $month . '-01'); + $dt = new DateTime($year . '-' . $month . '-01'); return $dt->format('M'); } @@ -323,7 +324,7 @@ class HistoryColorGrid extends AbstractWidget { */ private function weekdayName($weekday) { - $sun = DateTimeFactory::create('last Sunday'); + $sun = new DateTime('last Sunday'); $interval = new DateInterval('P' . $weekday . 'D'); $sun->add($interval); return substr($sun->format('D'), 0, 2); diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php index 3ff97b2df..70aadf666 100644 --- a/library/Icinga/Web/Widget/Chart/InlinePie.php +++ b/library/Icinga/Web/Widget/Chart/InlinePie.php @@ -1,13 +1,17 @@ - + + +{noscript} +EOD; + + private $noscript =<<<'EOD' + EOD; /** @@ -66,35 +95,7 @@ EOD; * * @var array */ - private $colors = array('#44bb77', '#ffaa44', '#ff5566', '#ddccdd'); - - /** - * The width of the rendered chart - * - * @var int The value in px - */ - private $width = 16; - - /** - * The height of the rendered chart - * - * @var int The value in px - */ - private $height = 16; - - /** - * PieChart border width - * - * @var float - */ - private $borderWidth = 1; - - /** - * The color of the border - * - * @var string - */ - private $borderColor = '#fff'; + private $colors = array('#049BAF', '#ffaa44', '#ff5566', '#ddccdd'); /** * The title of the chart @@ -104,11 +105,9 @@ EOD; private $title; /** - * The style for the HtmlElement - * - * @var string + * @var */ - private $style = ''; + private $size; /** * The data displayed by the pie-chart @@ -118,45 +117,9 @@ EOD; private $data; /** - * The labels to display for each data set - * - * @var array + * @var */ - private $labels = array(); - - /** - * If the tooltip for the "empty" area should be hidden - * - * @var bool - */ - private $hideEmptyLabel = false; - - /** - * The format string used to display tooltips - * - * @var string - */ - private $tooltipFormat = '{{title}}
        {{label}}: {{formatted}} ({{percent}}%)'; - - /** - * The number format used to render numeric values in tooltips - * - * @var array - */ - private $format = self::NUMBER_FORMAT_NONE; - - /** - * Set if the tooltip for the empty area should be hidden - * - * @param bool $hide Whether to hide the empty area - * - * @return $this - */ - public function setHideEmptyLabel($hide = true) - { - $this->hideEmptyLabel = $hide; - return $this; - } + private $class = ''; /** * Set the data to be displayed. @@ -173,24 +136,36 @@ EOD; } /** - * The labels to be displayed in the pie-chart + * Set the size of the inline pie * - * @param mixed $label The label of the displayed value, or null for no labels + * @param int $size Sets both, the height and width * - * @return $this + * @return $this */ - public function setLabel($label) + public function setSize($size = null) { - if (is_array($label)) { - $this->url->setParam('labels', implode(',', array_keys($label))); - } elseif ($label != null) { - $labelArr = array($label, $label, $label, ''); - $this->url->setParam('labels', implode(',', $labelArr)); - $label = $labelArr; - } else { - $this->url->removeKey('labels'); - } - $this->labels = $label; + $this->size = $size; + return $this; + } + + /** + * Do not display the NoScript fallback html + */ + public function disableNoScript() + { + $this->noscript = ''; + } + + /** + * Set the class to define the + * + * @param $class + * + * @return $this + */ + public function setSparklineClass($class) + { + $this->class = $class; return $this; } @@ -212,105 +187,6 @@ EOD; return $this; } - /** - * Set the used number format - * - * @param $format string 'bytes' or 'time' - * - * @return $this - */ - public function setNumberFormat($format) - { - $this->format = $format; - return $this; - } - - /** - * A format string used to render the content of the piechart tooltips - * - * Placeholders using curly braces '{FOO}' are replace with their specific values. The format - * String may contain HTML-Markup. The available replaceable values are: - *
          - *
        • label: The description for the current value
        • - *
        • formatted: A string representing the formatted value
        • - *
        • value: The raw (non-formatted) value used to render the piechart
        • - *
        • percent: The percentage of the current value
        • - *
        - * Note: Changes will only affect JavaScript sparklines and not the SVG charts used for fallback - * - * @param $format - * - * @return $this - */ - public function setTooltipFormat($format) - { - $this->tooltipFormat = $format; - return $this; - } - - /** - * Set the height - * - * @param $height - * - * @return $this - */ - public function setHeight($height) - { - $this->height = $height; - return $this; - } - - /** - * Set the border width of the pie chart - * - * @param float $width Width in px - * - * @return $this - */ - public function setBorderWidth($width) - { - $this->borderWidth = $width; - return $this; - } - - /** - * Set the color of the pie chart border - * - * @param string $col The color string - * - * @return $this - */ - public function setBorderColor($col) - { - $this->borderColor = $col; - } - - /** - * Set the width - * - * @param $width - * - * @return $this - */ - public function setWidth($width) - { - $this->width = $width; - return $this; - } - - /** - * Set the styling of the created HtmlElement - * - * @param string $style - * - * @return $this - */ - public function setStyle($style) - { - $this->style = $style; - } - /** * Set the title of the displayed Data * @@ -320,7 +196,7 @@ EOD; */ public function setTitle($title) { - $this->title = $title; + $this->title = 'title="' . htmlspecialchars($title) . '"'; return $this; } @@ -333,13 +209,10 @@ EOD; */ public function __construct(array $data, $title, $colors = null) { - $this->title = $title; + $this->setTitle($title); $this->url = Url::fromPath('svg/chart.php'); if (array_key_exists('data', $data)) { $this->data = $data['data']; - if (array_key_exists('labels', $data)) { - $this->labels = $data['labels']; - } if (array_key_exists('colors', $data)) { $this->colors = $data['colors']; } @@ -352,21 +225,6 @@ EOD; $this->setColors($this->colors); } } - - /** - * Create a serialization containing the current label array - * - * @return string A serialized array of labels - */ - private function createLabelString () - { - $labels = $this->labels; - foreach ($labels as $key => $label) { - $labels[$key] = str_replace('|', '', $label); - } - return isset($this->labels) && is_array($this->labels) ? implode('|', $this->labels) : ''; - } - /** * Renders this widget via the given view and returns the * HTML as a string @@ -375,18 +233,33 @@ EOD; */ public function render() { + if ($this->view()->layout()->getLayout() === 'pdf') { + $pie = new PieChart(); + $pie->alignTopLeft(); + $pie->disableLegend(); + $pie->drawPie(array( + 'data' => $this->data, 'colors' => $this->colors + )); + + try { + $png = $pie->toPng($this->size, $this->size); + return ''; + } catch (IcingaException $_) { + return ''; + } + } + $template = $this->template; + // TODO: Check whether we are XHR and don't send + $template = str_replace('{noscript}', $this->noscript, $template); $template = str_replace('{url}', $this->url, $template); + $template = str_replace('{class}', $this->class, $template); // style - $template = str_replace('{width}', $this->width, $template); - $template = str_replace('{height}', $this->height, $template); - $template = str_replace('{title}', htmlspecialchars($this->title), $template); - $template = str_replace('{style}', $this->style, $template); + $template = str_replace('{size}', isset($this->size) ? $this->size : 16, $template); + $template = str_replace('{title}', $this->title, $template); + $template = str_replace('{colors}', implode(',', $this->colors), $template); - $template = str_replace('{borderWidth}', $this->borderWidth, $template); - $template = str_replace('{borderColor}', $this->borderColor, $template); - $template = str_replace('{hideEmptyLabel}', $this->hideEmptyLabel ? 'true' : 'false', $template); // Locale-ignorant string cast. Please. Do. NOT. Remove. This. Again. // Problem is that implode respects locales when casting floats. This means @@ -396,38 +269,22 @@ EOD; $data[] = sprintf('%F', $dat); } - // values - $formatted = array(); - foreach ($this->data as $key => $value) { - $formatted[$key] = $this->formatValue($value); - } $template = str_replace('{data}', htmlspecialchars(implode(',', $data)), $template); - $template = str_replace('{formatted}', htmlspecialchars(implode('|', $formatted)), $template); - $template = str_replace('{labels}', htmlspecialchars($this->createLabelString()), $template); - $template = str_replace('{tooltipFormat}', $this->tooltipFormat, $template); return $template; } - /** - * Format the given value depending on the current value of numberFormat - * - * @param float $value The value to format - * - * @return string The formatted value - */ - private function formatValue($value) + public static function createFromStateSummary(stdClass $states, $title, array $colors) { - if ($this->format === self::NUMBER_FORMAT_NONE) { - return (string)$value; - } elseif ($this->format === self::NUMBER_FORMAT_BYTES) { - return Format::bytes($value); - } elseif ($this->format === self::NUMBER_FORMAT_TIME) { - return Format::duration($value); - } elseif ($this->format === self::NUMBER_FORMAT_RATIO) { - return $value; - } else { - Logger::warning('Unknown format string "' . $this->format . '" for InlinePie, value not formatted.'); - return $value; + $handledUnhandledStates = array(); + foreach ($states as $key => $value) { + if (String::endsWith($key, '_handled') || String::endsWith($key, '_unhandled')) { + $handledUnhandledStates[$key] = $value; + } } + $chart = new self(array_values($handledUnhandledStates), $title, $colors); + return $chart + ->setSize(50) + ->setTitle('') + ->setSparklineClass('sparkline-multi'); } } diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index 45e1e6830..758ec8267 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -1,17 +1,13 @@ getModuleManager(); foreach ($manager->getLoadedModules() as $module) { - /** @var $module \Icinga\Application\Modules\Module */ - $this->mergePanes($module->getPaneItems()); + if ($this->getUser()->can($manager::MODULE_PERMISSION_NS . $module->getName())) { + $this->mergePanes($module->getPaneItems()); + } } - if ($this->user !== null) { - $this->loadUserDashboards(); - } + + $this->loadUserDashboards(); return $this; } /** - * Create a writer object + * Create and return a Config object for this dashboard * - * @return IniWriter + * @return Config */ - public function createWriter() + public function getConfig() { - $configFile = $this->getConfigFile(); $output = array(); foreach ($this->panes as $pane) { - if ($pane->isUserWidget() === true) { + if ($pane->isUserWidget()) { $output[$pane->getName()] = $pane->toArray(); } foreach ($pane->getDashlets() as $dashlet) { - if ($dashlet->isUserWidget() === true) { + if ($dashlet->isUserWidget()) { $output[$pane->getName() . '.' . $dashlet->getTitle()] = $dashlet->toArray(); } } } - $co = new ConfigObject($output); - $config = new Config($co); - return new IniWriter(array('config' => $config, 'filename' => $configFile)); - } - - /** - * Write user specific dashboards to disk - */ - public function write() - { - $this->createWriter()->write(); + return Config::fromArray($output)->setConfigFile($this->getConfigFile()); } /** @@ -226,7 +211,11 @@ class Dashboard extends AbstractWidget $this->tabs->add( $key, array( - 'title' => $pane->getTitle(), + 'title' => sprintf( + t('Show %s', 'dashboard.pane.tooltip'), + $pane->getTitle() + ), + 'label' => $pane->getTitle(), 'url' => clone($url), 'urlParams' => array($this->tabParam => $key) ) @@ -252,7 +241,7 @@ class Dashboard extends AbstractWidget * * @param string $title * - * @return self + * @return $this */ public function createPane($title) { @@ -289,7 +278,7 @@ class Dashboard extends AbstractWidget * * @param Pane $pane The pane to add * - * @return self + * @return $this */ public function addPane(Pane $pane) { @@ -358,7 +347,7 @@ class Dashboard extends AbstractWidget } /** - * Activates the default pane of this dashboard and returns it's name + * Activates the default pane of this dashboard and returns its name * * @return mixed */ @@ -438,28 +427,8 @@ class Dashboard extends AbstractWidget public function getConfigFile() { if ($this->user === null) { - return ''; + throw new ProgrammingError('Can\'t load dashboards. User is not set'); } - - $baseDir = '/var/lib/icingaweb'; - - if (! file_exists($baseDir)) { - throw new NotReadableError('Could not read: ' . $baseDir); - } - - $userDir = $baseDir . '/' . $this->user->getUsername(); - - if (! file_exists($userDir)) { - $success = @mkdir($userDir); - if (!$success) { - throw new SystemPermissionException('Could not create: ' . $userDir); - } - } - - if (! file_exists($userDir)) { - throw new NotReadableError('Could not read: ' . $userDir); - } - - return $userDir . '/dashboard.ini'; + return Config::resolvePath('dashboards/' . $this->user->getUsername() . '/dashboard.ini'); } } diff --git a/library/Icinga/Web/Widget/Dashboard/Dashlet.php b/library/Icinga/Web/Widget/Dashboard/Dashlet.php index 1b7a7ef95..06d6851e2 100644 --- a/library/Icinga/Web/Widget/Dashboard/Dashlet.php +++ b/library/Icinga/Web/Widget/Dashboard/Dashlet.php @@ -1,13 +1,11 @@ -

        {TITLE}

        +

        {TITLE}

        EOD; @@ -71,16 +74,13 @@ EOD; { $this->title = $title; $this->pane = $pane; - if ($url instanceof Url) { - $this->url = $url; - } elseif ($url) { - $this->url = Url::fromPath($url); - } else { + if (! $url) { throw new IcingaException( 'Cannot create dashboard dashlet "%s" without valid URL', $title ); } + $this->url = $url; } /** @@ -104,10 +104,13 @@ EOD; /** * Retrieve the dashlets url * - * @return Url + * @return Url|null */ public function getUrl() { + if ($this->url !== null && ! $this->url instanceof Url) { + $this->url = Url::fromPath($this->url); + } return $this->url; } @@ -116,15 +119,11 @@ EOD; * * @param string|Url $url The url to use, either as an Url object or as a path * - * @return self + * @return $this */ public function setUrl($url) { - if ($url instanceof Url) { - $this->url = $url; - } else { - $this->url = Url::fromPath($url); - } + $this->url = $url; return $this; } @@ -156,7 +155,7 @@ EOD; public function toArray() { $array = array( - 'url' => $this->url->getRelativeUrl(), + 'url' => $this->getUrl()->getRelativeUrl(), 'title' => $this->getTitle() ); if ($this->getDisabled() === true) { @@ -175,47 +174,32 @@ EOD; } $view = $this->view(); - $url = clone($this->url); + $url = $this->getUrl(); $url->setParam('view', 'compact'); - $iframeUrl = clone($url); + $iframeUrl = clone $url; $iframeUrl->setParam('isIframe'); $searchTokens = array( '{URL}', '{IFRAME_URL}', '{FULL_URL}', + '{TOOLTIP}', '{TITLE}', - '{REMOVE}' + '{TITLE_PREFIX}' ); $replaceTokens = array( $url, $iframeUrl, $url->getUrlWithout(array('view', 'limit')), + sprintf($view->translate('Show %s', 'dashboard.dashlet.tooltip'), $view->escape($this->getTitle())), $view->escape($this->getTitle()), - $this->getRemoveLink() + $view->translate('Dashlet') . ': ' ); return str_replace($searchTokens, $replaceTokens, $this->template); } - /** - * Render the form for removing a dashboard elemetn - * - * @return string The html representation of the form - */ - protected function getRemoveLink() - { - return sprintf( - '%s', - Url::fromPath('dashboard/remove-dashlet', array( - 'dashlet' => $this->getTitle(), - 'pane' => $this->pane->getTitle() - )), - t('Remove') - ); - } - /** * Create a @see Dashlet instance from the given Zend config, using the provided title * diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php index 52d9fa524..5f973885f 100644 --- a/library/Icinga/Web/Widget/Dashboard/Pane.php +++ b/library/Icinga/Web/Widget/Dashboard/Pane.php @@ -1,6 +1,5 @@ filter === null) { - $this->filter = Filter::fromQueryString((string) $this->url()->getParams()); + $this->filter = Filter::fromQueryString((string) $this->url()->getParams()); } return $this->filter; } + /** + * Set columns to search in + * + * @param array $searchColumns + * + * @return $this + */ + public function setSearchColumns(array $searchColumns) + { + $this->searchColumns = $searchColumns; + return $this; + } + public function setUrl($url) { $this->url = $url; @@ -84,6 +104,14 @@ class FilterEditor extends AbstractWidget return $this->url; } + protected function preservedUrl() + { + if ($this->preservedUrl === null) { + $this->preservedUrl = $this->url()->with($this->preservedParams); + } + return $this->preservedUrl; + } + public function setQuery($query) { $this->query = $query; @@ -136,6 +164,26 @@ class FilterEditor extends AbstractWidget return $filter; } + protected function resetSearchColumns(Filter &$filter) + { + if ($filter->isChain()) { + $filters = &$filter->filters(); + if (!($empty = empty($filters))) { + foreach ($filters as $k => &$f) { + if (false === $this->resetSearchColumns($f)) { + unset($filters[$k]); + } + } + } + return $empty || !empty($filters); + } + return $filter->isExpression() ? !( + in_array($filter->getColumn(), $this->searchColumns) + && + $filter->getSign() === '=' + ) : true; + } + public function handleRequest($request) { $this->setUrl($request->getUrl()->without($this->ignoreParams)); @@ -147,6 +195,7 @@ class FilterEditor extends AbstractWidget $preserve[$key] = $value; } } + $this->preservedParams = $preserve; $add = $params->shift('addFilter'); $remove = $params->shift('removeFilter'); @@ -167,33 +216,22 @@ class FilterEditor extends AbstractWidget $filter = $this->getFilter(); if ($search !== null) { - if (strpos($search, '=') === false) { - // TODO: Ask the view for (multiple) search columns - switch($request->getActionName()) { - case 'services': - $searchCol = 'service_description'; - break; - case 'hosts': - $searchCol = 'host_name'; - break; - case 'hostgroups': - $searchCol = 'hostgroup'; - break; - case 'servicegroups': - $searchCol = 'servicegroup'; - break; - default: - $searchCol = null; - } - - if ($searchCol === null) { - throw new Exception('Cannot search here'); - } - $filter = $this->mergeRootExpression($filter, $searchCol, '=', "*$search*"); - - } else { + if (strpos($search, '=') !== false) { list($k, $v) = preg_split('/=/', $search); - $filter = $this->mergeRootExpression($filter, $k, '=', $v); + $filter = $this->mergeRootExpression($filter, trim($k), '=', ltrim($v)); + } elseif (! empty($this->searchColumns)) { + if (! $this->resetSearchColumns($filter)) { + $filter = Filter::matchAll(); + } + $filters = array(); + $search = ltrim($search); + foreach ($this->searchColumns as $searchColumn) { + $filters[] = Filter::expression($searchColumn, '=', "*$search*"); + } + $filter = $filter->andFilter(new FilterOr($filters)); + } else { + Notification::error(mt('monitoring', 'Cannot search here')); + return $this; } $url = $this->url()->setQueryString( @@ -232,7 +270,7 @@ class FilterEditor extends AbstractWidget if ($modify) { if ($request->isPost()) { if ($request->get('cancel') === 'Cancel') { - $this->redirectNow($this->url()->without('modifyFilter')); + $this->redirectNow($this->preservedUrl()->without('modifyFilter')); } $filter = $this->applyChanges($request->getPost()); @@ -295,11 +333,11 @@ class FilterEditor extends AbstractWidget { return $this->view()->qlink( '', - $this->url()->with('removeFilter', $filter->getId()), + $this->preservedUrl()->with('removeFilter', $filter->getId()), null, array( - 'title' => t('Click to remove this part of your filter'), - 'class' => 'icon-cancel' + 'icon' => 'trash', + 'title' => t('Remove this part of your filter') ) ); } @@ -308,11 +346,11 @@ class FilterEditor extends AbstractWidget { return $this->view()->qlink( '', - $this->url()->with('addFilter', $filter->getId()), + $this->preservedUrl()->with('addFilter', $filter->getId()), null, array( - 'title' => t('Click to add another filter'), - 'class' => 'icon-plus' + 'icon' => 'plus', + 'title' => t('Add another filter') ) ); } @@ -321,11 +359,11 @@ class FilterEditor extends AbstractWidget { return $this->view()->qlink( '', - $this->url()->with('stripFilter', $filter->getId()), + $this->preservedUrl()->with('stripFilter', $filter->getId()), null, array( - 'title' => t('Strip this filter'), - 'class' => 'icon-minus' + 'icon' => 'minus', + 'title' => t('Strip this filter') ) ); } @@ -334,11 +372,11 @@ class FilterEditor extends AbstractWidget { return $this->view()->qlink( '', - $this->url()->without('addFilter'), + $this->preservedUrl()->without('addFilter'), null, array( - 'title' => t('Cancel this operation'), - 'class' => 'icon-cancel' + 'icon' => 'cancel', + 'title' => t('Cancel this operation') ) ); } @@ -391,12 +429,12 @@ class FilterEditor extends AbstractWidget protected function renderFilterExpression(FilterExpression $filter) { if ($this->addTo && $this->addTo === $filter->getId()) { - return + return preg_replace( - '/ class="autosubmit"/', - ' class="autofocus"', - $this->selectOperator() - ) + '/ class="autosubmit"/', + ' class="autofocus"', + $this->selectOperator() + ) . '
        • ' . $this->selectColumn($filter) . $this->selectSign($filter) @@ -502,11 +540,17 @@ class FilterEditor extends AbstractWidget ); } + public function setColumns(array $columns) + { + $this->cachedColumnSelect = $this->arrayForSelect($columns); + return $this; + } + protected function selectColumn(Filter $filter = null) { $active = $filter === null ? null : $filter->getColumn(); - if ($this->query === null) { + if ($this->cachedColumnSelect === null && $this->query === null) { return sprintf( '', $this->elementId('column', $filter), @@ -531,7 +575,7 @@ class FilterEditor extends AbstractWidget $cols[$active] = str_replace('_', ' ', ucfirst(ltrim($active, '_'))); } - return $this->select($this->elementId('column', $filter), $cols, $active); + return $this->select($this->elementId('column', $filter), $cols, $active); } protected function applyChanges($changes) @@ -637,13 +681,13 @@ class FilterEditor extends AbstractWidget public function renderSearch() { - $html = '
          '; - if ($this->filter->isEmpty()) { + if ($this->filter->isEmpty()) { $title = t('Filter this list'); } else { $title = t('Modify this filter'); @@ -653,31 +697,35 @@ class FilterEditor extends AbstractWidget } return $html . '' - . '' + . '' . ''; } public function render() { - if (! $this->url()->getParam('modifyFilter')) { - return $this->renderSearch() . $this->shorten($this->filter, 50); + if (! $this->preservedUrl()->getParam('modifyFilter')) { + return '
          ' . $this->renderSearch() . $this->shorten($this->filter, 50) . '
          '; } - return $this->renderSearch() - . '
          ' - . '
          • ' - . $this->renderFilter($this->filter) - . '
          ' - . '
          ' - . '' - . '' - . '
          ' - . '
          '; + return '
          ' + . $this->renderSearch() + . '
          ' + . '
          • ' + . $this->renderFilter($this->filter) + . '
          ' + . '
          ' + . '' + . '' + . '
          ' + . '
          ' + . '
          '; } protected function shorten($string, $length) diff --git a/library/Icinga/Web/Widget/FilterWidget.php b/library/Icinga/Web/Widget/FilterWidget.php index cf47ef581..bf3652c5e 100644 --- a/library/Icinga/Web/Widget/FilterWidget.php +++ b/library/Icinga/Web/Widget/FilterWidget.php @@ -1,6 +1,5 @@ renderFilter($this->filter); - + $editorUrl = clone $url; $editorUrl->setParam('modifyFilter', true); if ($this->filter->isEmpty()) { diff --git a/library/Icinga/Web/Widget/Limiter.php b/library/Icinga/Web/Widget/Limiter.php index 5afa7e4b8..1c6c82f62 100644 --- a/library/Icinga/Web/Widget/Limiter.php +++ b/library/Icinga/Web/Widget/Limiter.php @@ -1,6 +1,5 @@ url = $url; @@ -40,13 +41,19 @@ class Limiter extends AbstractWidget return $this; } + public function setDefaultLimit($limit) + { + $this->default = $limit; + return $this; + } + public function render() { if ($this->url === null) { $this->url = Url::fromRequest(); } - $currentLimit = (int) $this->url->getParam('limit', 25); // Default?? + $currentLimit = (int) $this->url->getParam('limit', $this->default); $availableLimits = array( 10 => '10', 25 => '25', @@ -81,7 +88,7 @@ class Limiter extends AbstractWidget $this->url->setParam('limit', $limit), null, array( - 'title' => sprintf(t('Show %s rows on one page'), $caption) + 'title' => sprintf($view->translate('Limit each page to a maximum of %u rows'), $caption) ) ); } diff --git a/library/Icinga/Web/Widget/Paginator.php b/library/Icinga/Web/Widget/Paginator.php new file mode 100644 index 000000000..b0c5b6c15 --- /dev/null +++ b/library/Icinga/Web/Widget/Paginator.php @@ -0,0 +1,168 @@ +query = $query; + return $this; + } + + /** + * Set the view script to use + * + * @param string|array $script + * + * @return $this + */ + public function setViewScript($script) + { + $this->viewScript = $script; + return $this; + } + + /** + * Render this paginator + */ + public function render() + { + if ($this->query === null) { + throw new ProgrammingError('Need a query to create the paginator widget for'); + } + + $itemCountPerPage = $this->query->getLimit(); + if (! $itemCountPerPage) { + return ''; // No pagination required + } + + $totalItemCount = count($this->query); + $pageCount = (int) ceil($totalItemCount / $itemCountPerPage); + $currentPage = $this->query->hasOffset() ? ($this->query->getOffset() / $itemCountPerPage) + 1 : 1; + $pagesInRange = $this->getPages($pageCount, $currentPage); + $variables = array( + 'totalItemCount' => $totalItemCount, + 'pageCount' => $pageCount, + 'itemCountPerPage' => $itemCountPerPage, + 'first' => 1, + 'current' => $currentPage, + 'last' => $pageCount, + 'pagesInRange' => $pagesInRange, + 'firstPageInRange' => min($pagesInRange), + 'lastPageInRange' => max($pagesInRange) + ); + + if ($currentPage > 1) { + $variables['previous'] = $currentPage - 1; + } + + if ($currentPage < $pageCount) { + $variables['next'] = $currentPage + 1; + } + + if (is_array($this->viewScript)) { + if ($this->viewScript[1] !== null) { + return $this->view()->partial($this->viewScript[0], $this->viewScript[1], $variables); + } + + return $this->view()->partial($this->viewScript[0], $variables); + } + + return $this->view()->partial($this->viewScript, $variables); + } + + /** + * Returns an array of "local" pages given the page count and current page number + * + * @return array + */ + protected function getPages($pageCount, $currentPage) + { + $range = array(); + + if ($pageCount < 10) { + // Show all pages if we have less than 10 + for ($i = 1; $i < 10; $i++) { + if ($i > $pageCount) { + break; + } + + $range[$i] = $i; + } + } else { + // More than 10 pages: + foreach (array(1, 2) as $i) { + $range[$i] = $i; + } + + if ($currentPage < 6 ) { + // We are on page 1-5 from + for ($i = 1; $i <= 7; $i++) { + $range[$i] = $i; + } + } else { + // Current page > 5 + $range[] = '...'; + + if (($pageCount - $currentPage) < 5) { + // Less than 5 pages left + $start = 5 - ($pageCount - $currentPage); + } else { + $start = 1; + } + + for ($i = $currentPage - $start; $i < ($currentPage + (4 - $start)); $i++) { + if ($i > $pageCount) { + break; + } + + $range[$i] = $i; + } + } + + if ($currentPage < ($pageCount - 2)) { + $range[] = '...'; + } + + foreach (array($pageCount - 1, $pageCount) as $i) { + $range[$i] = $i; + } + + } + + if (empty($range)) { + $range[] = 1; + } + + return $range; + } +} diff --git a/library/Icinga/Web/Widget/SearchDashboard.php b/library/Icinga/Web/Widget/SearchDashboard.php index 9e02f3373..0ca759919 100644 --- a/library/Icinga/Web/Widget/SearchDashboard.php +++ b/library/Icinga/Web/Widget/SearchDashboard.php @@ -1,101 +1,111 @@ tabs === null) { + $this->tabs = new Tabs(); + $this->tabs->add( + 'search', + array( + 'title' => t('Show Search', 'dashboard.pane.tooltip'), + 'label' => t('Search'), + 'url' => Url::fromRequest() + ) + ); + } + return $this->tabs; + } /** * Load all available search dashlets from modules * - * @param string $searchString - * @return Dashboard|SearchDashboard - */ - public static function search($searchString = '') - { - /** @var $dashboard SearchDashboard */ - $dashboard = new static('searchDashboard'); - $dashboard->loadSearchDashlets($searchString); - return $dashboard; - } - - /** - * Renders the output + * @param string $searchString * - * @return string - * @throws \Zend_Controller_Action_Exception + * @return $this */ - public function render() - { - if (! $this->getPane(self::SEARCH_PANE)->hasDashlets()) { - throw new ActionError('Site not found', 404); - } - return parent::render(); - } - - /** - * Loads search dashlets - * - * @param string $searchString - */ - protected function loadSearchDashlets($searchString) + public function search($searchString = '') { $pane = $this->createPane(self::SEARCH_PANE)->getPane(self::SEARCH_PANE)->setTitle(t('Search')); $this->activate(self::SEARCH_PANE); $manager = Icinga::app()->getModuleManager(); + $searchUrls = array(); foreach ($manager->getLoadedModules() as $module) { - $this->addSearchDashletsFromModule($searchString, $module, $pane); + if ($this->getUser()->can($manager::MODULE_PERMISSION_NS . $module->getName())) { + $moduleSearchUrls = $module->getSearchUrls(); + if (! empty($moduleSearchUrls)) { + if ($searchString === '') { + $pane->add(t('Ready to search'), 'search/hint'); + return $this; + } + $searchUrls = array_merge($searchUrls, $moduleSearchUrls); + } + } } - if ($searchString === '' && $pane->hasDashlets()) { - $pane->removeDashlets(); - $pane->add('Ready to search', 'search/hint'); - return; + usort($searchUrls, array($this, 'compareSearchUrls')); + + foreach (array_reverse($searchUrls) as $searchUrl) { + $pane->addDashlet( + $searchUrl->title . ': ' . $searchString, + Url::fromPath($searchUrl->url, array('q' => $searchString)) + ); } + + return $this; } /** - * Add available search dashlets to the pane + * Renders the output * - * @param string $searchString - * @param Module $module - * @param Pane $pane + * @return string + * + * @throws Zend_Controller_Action_Exception */ - protected function addSearchDashletsFromModule($searchString, $module, $pane) + public function render() { - $searchUrls = $module->getSearchUrls(); - - if (! empty($searchUrls)) { - $this->searchUrls[] = $module->getSearchUrls(); - foreach ($searchUrls as $search) { - $pane->addDashlet( - $search->title . ': ' . $searchString, - Url::fromPath($search->url, array('q' => $searchString)) - ); - } + if (! $this->getPane(self::SEARCH_PANE)->hasDashlets()) { + throw new Zend_Controller_Action_Exception(t('Page not found'), 404); } + return parent::render(); + } + + /** + * Compare search URLs based on their priority + * + * @param object $a + * @param object $b + * + * @return int + */ + private function compareSearchUrls($a, $b) + { + if ($a->priority === $b->priority) { + return 0; + } + return ($a->priority < $b->priority) ? -1 : 1; } } diff --git a/library/Icinga/Web/Widget/SortBox.php b/library/Icinga/Web/Widget/SortBox.php index 42e7110cc..c9d38f2b7 100644 --- a/library/Icinga/Web/Widget/SortBox.php +++ b/library/Icinga/Web/Widget/SortBox.php @@ -1,67 +1,64 @@ - * $this->view->sortControl = new SortBox( - * $this->getRequest()->getActionName(), - * $columns - * ); - * $this->view->sortControl->applyRequest($this->getRequest()); - *
    - * By default the sortBox uses the GET parameter 'sort' for the sorting key and 'dir' for the sorting direction + * The constructor takes a string for the component name and an array containing the select options, where the key is + * the value to be submitted and the value is the label that will be shown. You then should call setRequest in order + * to make sure the form is correctly populated when a request with a sort parameter is being made. * + * Call setQuery in case you'll do not want to handle URL parameters manually, but to automatically apply the user's + * chosen sort rules on the given sortable query. This will also allow the SortBox to display the user the correct + * default sort rules if the given query provides already some sort rules. */ class SortBox extends AbstractWidget { - /** * An array containing all sort columns with their associated labels * * @var array */ - private $sortFields; + protected $sortFields; /** - * The name of the form that will be created + * The name used to uniquely identfy the forms being created * * @var string */ - private $name; + protected $name; /** - * A request object used for initial form population + * The request to fetch sort rules from * - * @var \Icinga\Web\Request + * @var Request */ - private $request; + protected $request; + + /** + * The query to apply sort rules on + * + * @var Sortable + */ + protected $query; /** * Create a SortBox with the entries from $sortFields * - * @param string $name The name of the sort form - * @param array $sortFields An array containing the columns and their labels to be displayed - * in the sort select box + * @param string $name The name for the SortBox + * @param array $sortFields An array containing the columns and their labels to be displayed in the SortBox */ public function __construct($name, array $sortFields) { @@ -70,70 +67,161 @@ class SortBox extends AbstractWidget } /** - * Apply the parameters from the given request on this SortBox + * Create a SortBox * - * @param Request $request The request to use for populating the form + * @param string $name The name for the SortBox + * @param array $sortFields An array containing the columns and their labels to be displayed in the SortBox + * + * @return SortBox */ - public function applyRequest($request) + public static function create($name, array $sortFields) + { + return new static($name, $sortFields); + } + + /** + * Set the request to fetch sort rules from + * + * @param Request $request + * + * @return $this + */ + public function setRequest($request) { $this->request = $request; + return $this; } /** - * Create a submit button that is hidden via the ConditionalDecorator - * in order to allow sorting changes to be submitted in a JavaScript-less environment + * Set the query to apply sort rules on * - * @return Zend_Form_Element_Submit The submit button that is hidden by default - * @see ConditionalDecorator + * @param Sortable $query + * + * @return $this */ - private function createFallbackSubmitButton() + public function setQuery(Sortable $query) { - $manualSubmitButton = new Zend_Form_Element_Submit( - array( - 'name' => 'submit_' . $this->name, - 'label' => 'Sort', - 'class' => '', - 'condition' => 0, - 'value' => '{{SUBMIT_ICON}}' - ) - ); - $manualSubmitButton->addDecorator(new ConditionalHidden()); - $manualSubmitButton->setAttrib('addLabelPlaceholder', true); - return $manualSubmitButton; + $this->query = $query; + return $this; } /** - * Renders this widget via the given view and returns the - * HTML as a string + * Apply the sort rules from the given or current request on the query + * + * @param Request $request + * + * @return $this + */ + public function handleRequest(Request $request = null) + { + if ($this->query !== null) { + if ($request === null) { + $request = Icinga::app()->getRequest(); + } + + if (($sort = $request->getParam('sort'))) { + $this->query->order($sort, $request->getParam('dir')); + } elseif (($dir = $request->getParam('dir'))) { + $this->query->order(null, $dir); + } + } + + return $this; + } + + /** + * Return the default sort rule for the query + * + * @param string $column An optional column + * + * @return array An array of two values: $column, $direction + */ + protected function getSortDefaults($column = null) + { + $direction = null; + if ($this->query !== null && $this->query instanceof SortRules) { + $sortRules = $this->query->getSortRules(); + if ($column === null) { + $column = key($sortRules); + } + + if ($column !== null && isset($sortRules[$column]['order'])) { + $direction = strtoupper($sortRules[$column]['order']) === Sortable::SORT_DESC ? 'desc' : 'asc'; + } + } + + return array($column, $direction); + } + + /** + * Render this SortBox as HTML * * @return string */ public function render() { - $form = new Form(); - $form->setAttrib('class', 'inline'); - $form->setMethod('POST'); - $form->setTokenDisabled(); - $form->setName($this->name); - $form->addElement('select', 'sort', array( - 'label' => 'Sort By', - 'multiOptions' => $this->sortFields, - 'style' => 'width: 12em', - 'autosubmit' => true - )); - $form->addElement('select', 'dir', array( - 'multiOptions' => array( - 'asc' => 'Asc', - 'desc' => 'Desc', - ), - 'style' => 'width: 5em', - 'autosubmit' => true - )); - $sort = $form->getElement('sort')->setDecorators(array('ViewHelper')); - $dir = $form->getElement('dir')->setDecorators(array('ViewHelper')); + $columnForm = new Form(); + $columnForm->setTokenDisabled(); + $columnForm->setName($this->name . '-column'); + $columnForm->setAttrib('class', 'inline'); + $columnForm->addElement( + 'select', + 'sort', + array( + 'autosubmit' => true, + 'label' => $this->view()->translate('Sort by'), + 'multiOptions' => $this->sortFields, + 'decorators' => array( + array('ViewHelper'), + array('Label') + ) + ) + ); + + $orderForm = new Form(); + $orderForm->setTokenDisabled(); + $orderForm->setName($this->name . '-order'); + $orderForm->setAttrib('class', 'inline'); + $orderForm->addElement( + 'select', + 'dir', + array( + 'autosubmit' => true, + 'label' => $this->view()->translate('Direction', 'sort direction'), + 'multiOptions' => array( + 'asc' => $this->view()->translate('Ascending', 'sort direction'), + 'desc' => $this->view()->translate('Descending', 'sort direction') + ), + 'decorators' => array( + array('ViewHelper'), + array('Label', array('class' => 'no-js')) + ) + ) + ); + + $column = null; if ($this->request) { - $form->populate($this->request->getParams()); + $url = $this->request->getUrl(); + if ($url->hasParam('sort')) { + $column = $url->getParam('sort'); + + if ($url->hasParam('dir')) { + $direction = $url->getParam('dir'); + } else { + list($_, $direction) = $this->getSortDefaults($column); + } + } elseif ($url->hasParam('dir')) { + $direction = $url->getParam('dir'); + list($column, $_) = $this->getSortDefaults(); + } } - return $form; + + if ($column === null) { + list($column, $direction) = $this->getSortDefaults(); + } + + $columnForm->populate(array('sort' => $column)); + $orderForm->populate(array('dir' => $direction)); + return '
    ' . $columnForm . $orderForm . '
    '; } } diff --git a/library/Icinga/Web/Widget/Tab.php b/library/Icinga/Web/Widget/Tab.php index a06389b98..890928ef0 100644 --- a/library/Icinga/Web/Widget/Tab.php +++ b/library/Icinga/Web/Widget/Tab.php @@ -1,6 +1,5 @@ name; } + /** + * Set the tab label + * + * @param string $label + */ + public function setLabel($label) + { + $this->label = $label; + } + + /** + * Get the tab label + * + * @return string + */ + public function getLabel() + { + if (! $this->label) { + return $this->title; + } + + return $this->label; + } + /** * @param mixed $title */ @@ -138,6 +182,16 @@ class Tab extends AbstractWidget $this->url = $url; } + /** + * Get the tab's target URL + * + * @return Url + */ + public function getUrl() + { + return $this->url; + } + /** * Set the parameters to be set for this tabs Url * @@ -158,6 +212,16 @@ class Tab extends AbstractWidget $this->tagParams = $tagParams; } + public function setTargetBlank($value = true) + { + $this->targetBlank = $value; + } + + public function setBaseTarget($value) + { + $this->baseTarget = $value; + } + /** * Create a new Tab with the given properties * @@ -183,7 +247,7 @@ class Tab extends AbstractWidget * * @param bool $active Whether the tab should be active * - * @return self + * @return $this */ public function setActive($active = true) { @@ -201,27 +265,48 @@ class Tab extends AbstractWidget if ($this->active) { $classes[] = 'active'; } - $caption = $view->escape($this->title); + + $caption = $view->escape($this->getLabel()); $tagParams = $this->tagParams; + if ($this->targetBlank) { + // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201 + $caption .= ' opens in new window '; + $tagParams['target'] ='_blank'; + } + + if ($this->title) { + if ($tagParams !== null) { + $tagParams['title'] = $this->title; + $tagParams['aria-label'] = $this->title; + } else { + $tagParams = array( + 'title' => $this->title, + 'aria-label' => $this->title + ); + } + } + + if ($this->baseTarget !== null) { + $tagParams['data-base-target'] = $this->baseTarget; + } if ($this->icon !== null) { if (strpos($this->icon, '.') === false) { - if ($tagParams && array_key_exists('class', $tagParams)) { - $tagParams['class'] .= ' icon-' . $this->icon; - } else { - $tagParams['class'] = 'icon-' . $this->icon; - } + $caption = $view->icon($this->icon) . $caption; } else { - $caption = $view->img($this->icon, array('class' => 'icon')) . $caption; + $caption = $view->img($this->icon, null, array('class' => 'icon')) . $caption; } } + if ($this->url !== null) { $this->url->overwriteParams($this->urlParams); + if ($tagParams !== null) { $params = $view->propertiesToString($tagParams); } else { $params = ''; } + $tab = sprintf( '%s', $this->url, @@ -231,6 +316,7 @@ class Tab extends AbstractWidget } else { $tab = $caption; } + $class = empty($classes) ? '' : sprintf(' class="%s"', implode(' ', $classes)); return '
  • ' . $tab . "
  • \n"; } diff --git a/library/Icinga/Web/Widget/Tabextension/BasketAction.php b/library/Icinga/Web/Widget/Tabextension/BasketAction.php deleted file mode 100644 index 34514f834..000000000 --- a/library/Icinga/Web/Widget/Tabextension/BasketAction.php +++ /dev/null @@ -1,35 +0,0 @@ -addAsDropdown( - 'basket', - array( - 'title' => 'URL Basket', - 'url' => Url::fromPath('basket/add'), - 'urlParams' => array( - 'url' => Url::fromRequest()->getRelativeUrl() - ) - ) - ); - } -} diff --git a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php index 280a9b142..569bc6f67 100644 --- a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php +++ b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php @@ -1,6 +1,5 @@ 'dashboard', - 'title' => 'Add To Dashboard', + 'label' => t('Add To Dashboard'), 'url' => Url::fromPath('dashboard/new-dashlet'), 'urlParams' => array( 'url' => rawurlencode(Url::fromRequest()->getRelativeUrl()) diff --git a/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php b/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php index 496fe3495..f6b2cad2c 100644 --- a/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php +++ b/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php @@ -1,6 +1,5 @@ 'img/icons/dashboard.png', - 'title' => t('Add To Dashboard'), + 'label' => t('Add New Pane Or Dashlet'), 'url' => Url::fromPath('dashboard/new-dashlet') ) ); @@ -31,10 +30,10 @@ class DashboardSettings implements Tabextension $tabs->addAsDropdown( 'dashboard_settings', array( - 'icon' => 'img/icons/dashboard.png', - 'title' => t('Settings'), - 'url' => Url::fromPath('dashboard/settings') + 'icon' => 'img/icons/dashboard.png', + 'label' => t('Settings'), + 'url' => Url::fromPath('dashboard/settings') ) ); } -} \ No newline at end of file +} diff --git a/library/Icinga/Web/Widget/Tabextension/OutputFormat.php b/library/Icinga/Web/Widget/Tabextension/OutputFormat.php index b5426ea47..f2da29f22 100644 --- a/library/Icinga/Web/Widget/Tabextension/OutputFormat.php +++ b/library/Icinga/Web/Widget/Tabextension/OutputFormat.php @@ -1,9 +1,9 @@ array( - 'name' => 'pdf', - 'title' => 'PDF', - 'icon' => 'file-pdf', - 'urlParams' => array('format' => 'pdf'), - ), - self::TYPE_CSV => array( - 'name' => 'csv', - 'title' => 'CSV', - 'icon' => 'file-excel', - 'urlParams' => array('format' => 'csv') - ), - self::TYPE_JSON => array( - 'name' => 'json', - 'title' => 'JSON', - 'icon' => 'img/icons/json.png', - 'urlParams' => array('format' => 'json') - ) - ); - /** * An array of tabs to be added to the dropdown area * @@ -74,13 +45,12 @@ class OutputFormat implements Tabextension */ public function __construct(array $disabled = array()) { - foreach ($this->supportedTypes as $type => $tabConfig) { + foreach ($this->getSupportedTypes() as $type => $tabConfig) { if (!in_array($type, $disabled)) { $tabConfig['url'] = Url::fromRequest(); - $tabConfig['tagParams'] = array( - 'target' => '_blank' - ); - $this->tabs[] = new Tab($tabConfig); + $tab = new Tab($tabConfig); + $tab->setTargetBlank(); + $this->tabs[] = $tab; } } } @@ -98,4 +68,44 @@ class OutputFormat implements Tabextension $tabs->addAsDropdown($tab->getName(), $tab); } } + + /** + * Return an array containing the tab definitions for all supported types + * + * Using array_keys on this array or isset allows to check whether a + * requested type is supported + * + * @return array + */ + public function getSupportedTypes() + { + $supportedTypes = array(); + + if (Platform::extensionLoaded('gd')) { + $supportedTypes[self::TYPE_PDF] = array( + 'name' => 'pdf', + 'label' => 'PDF', + 'icon' => 'file-pdf', + 'urlParams' => array('format' => 'pdf'), + ); + } + + $supportedTypes[self::TYPE_CSV] = array( + 'name' => 'csv', + 'label' => 'CSV', + 'icon' => 'file-excel', + 'urlParams' => array('format' => 'csv') + ); + + if (Platform::extensionLoaded('json')) { + $supportedTypes[self::TYPE_JSON] = array( + 'name' => 'json', + 'label' => 'JSON', + 'icon' => 'img/icons/json.png', + 'urlParams' => array('format' => 'json') + ); + } + + return $supportedTypes; + } } diff --git a/library/Icinga/Web/Widget/Tabextension/Tabextension.php b/library/Icinga/Web/Widget/Tabextension/Tabextension.php index 881668d2e..b799818eb 100644 --- a/library/Icinga/Web/Widget/Tabextension/Tabextension.php +++ b/library/Icinga/Web/Widget/Tabextension/Tabextension.php @@ -1,6 +1,5 @@ {TABS} {DROPDOWN} + {REFRESH} {CLOSE} EOT; @@ -34,7 +35,7 @@ EOT; */ private $dropdownTpl = <<< 'EOT' EOT; + /** + * Template used for the refresh icon + * + * @var string + */ + private $refreshTpl = <<< 'EOT' +
  • + + + +
  • +EOT; /** * This is where single tabs added to this container will be stored @@ -103,7 +116,7 @@ EOT; * * @param string $name Name of the tab going to be activated * - * @return self + * @return $this * * @throws ProgrammingError When the given tab name doesn't exist * @@ -135,7 +148,7 @@ EOT; * * @param string $name CSS class name(s) * - * @return self + * @return $this */ public function setClass($name) { @@ -179,9 +192,9 @@ EOT; * with tab properties or an instance of an existing Tab * * @param string $name The new tab name - * @param array|Tab $tab The tab itself of it's properties + * @param array|Tab $tab The tab itself of its properties * - * @return self + * @return $this * * @throws ProgrammingError When the tab name already exists */ @@ -204,9 +217,9 @@ EOT; * of an existing Tab * * @param string $name The new tab name - * @param array|Tab $tab The tab itself of it's properties + * @param array|Tab $tab The tab itself of its properties * - * @return self + * @return $this */ public function set($name, $tab) { @@ -218,6 +231,25 @@ EOT; return $this; } + /** + * Remove a tab + * + * @param string $name + * + * @return $this + */ + public function remove($name) + { + if ($this->has($name)) { + unset($this->tabs[$name]); + if (($dropdownIndex = array_search($name, $this->dropdownTabs, true)) !== false) { + array_splice($this->dropdownTabs, $dropdownIndex, 2); + } + } + + return $this; + } + /** * Add a tab to the dropdown on the right side of the tab-bar. * @@ -232,7 +264,7 @@ EOT; } /** - * Render the dropdown area with it's tabs and return the resulting HTML + * Render the dropdown area with its tabs and return the resulting HTML * * @return mixed|string */ @@ -275,6 +307,43 @@ EOT; return $this->closeTpl; } + private function renderRefreshTab() + { + $url = Icinga::app()->getRequest()->getUrl(); + $tab = $this->get($this->getActiveName()); + + if ($tab !== null) { + $label = $this->view()->escape( + $tab->getLabel() + ); + } + + if (! empty($label)) { + $caption = $label; + } else { + $caption = t('Content'); + } + + $label = t(sprintf('Refresh the %s', $caption)); + $title = $label; + + $tpl = str_replace( + array( + '{URL}', + '{TITLE}', + '{LABEL}' + ), + array( + $url, + $title, + $label + ), + $this->refreshTpl + ); + + return $tpl; + } + /** * Render to HTML * @@ -290,12 +359,23 @@ EOT; $drop = $this->renderDropdownTabs(); } $close = $this->closeTab ? $this->renderCloseTab() : ''; + $refresh = $this->renderRefreshTab(); - $html = $this->baseTpl; - $html = str_replace('{TABS}', $tabs, $html); - $html = str_replace('{DROPDOWN}', $drop, $html); - $html = str_replace('{CLOSE}', $close, $html); - return $html; + return str_replace( + array( + '{TABS}', + '{DROPDOWN}', + '{REFRESH}', + '{CLOSE}' + ), + array( + $tabs, + $drop, + $close, + $refresh + ), + $this->baseTpl + ); } public function __toString() @@ -347,7 +427,7 @@ EOT; * * @param Tabextension $tabextension * - * @return self + * @return $this */ public function extend(Tabextension $tabextension) { diff --git a/library/Icinga/Web/Widget/Widget.php b/library/Icinga/Web/Widget/Widget.php index 3eda668ce..8e7a38dd2 100644 --- a/library/Icinga/Web/Widget/Widget.php +++ b/library/Icinga/Web/Widget/Widget.php @@ -1,6 +1,5 @@ parent; + } + + /** + * Set this wizard's parent + * + * @param Wizard $wizard The parent wizard + * + * @return $this + */ + public function setParent(Wizard $wizard) + { + $this->parent = $wizard; + return $this; + } + /** * Return the pages being part of this wizard * + * In case this is a nested wizard a flattened array of all contained pages is returned. + * * @return array */ public function getPages() { - return $this->pages; + $pages = array(); + foreach ($this->pages as $page) { + if ($page instanceof self) { + $pages = array_merge($pages, $page->getPages()); + } else { + $pages[] = $page; + } + } + + return $pages; } /** * Return the page with the given name * + * Note that it's also possible to retrieve a nested wizard's page by using this method. + * * @param string $name The name of the page to return * * @return null|Form The page or null in case there is no page with the given name @@ -98,24 +140,33 @@ class Wizard } /** - * Add a new page to this wizard + * Add a new page or wizard to this wizard * - * @param Form $page The page to add to the wizard + * @param Form|Wizard $page The page or wizard to add to the wizard * - * @return self + * @return $this */ - public function addPage(Form $page) + public function addPage($page) { + if (! $page instanceof Form && ! $page instanceof self) { + throw InvalidArgumentException( + 'The $page argument must be an instance of Icinga\Web\Form ' + . 'or Icinga\Web\Wizard but is of type: ' . get_class($page) + ); + } elseif ($page instanceof self) { + $page->setParent($this); + } + $this->pages[] = $page; return $this; } /** - * Add multiple pages to this wizard + * Add multiple pages or wizards to this wizard * - * @param array $pages The pages to add to the wizard + * @param array $pages The pages or wizards to add to the wizard * - * @return self + * @return $this */ public function addPages(array $pages) { @@ -148,6 +199,10 @@ class Wizard */ public function getCurrentPage() { + if ($this->parent) { + return $this->parent->getCurrentPage(); + } + if ($this->currentPage === null) { $this->assertHasPages(); $pages = $this->getPages(); @@ -166,7 +221,7 @@ class Wizard * * @param Form $page The page to set as current page * - * @return self + * @return $this */ public function setCurrentPage(Form $page) { @@ -202,6 +257,10 @@ class Wizard { $page = $this->getCurrentPage(); + if (($wizard = $this->findWizard($page)) !== null) { + return $wizard->handleRequest($request); + } + if ($request === null) { $request = $page->getRequest(); } @@ -228,8 +287,10 @@ class Wizard $this->setCurrentPage($this->getNewPage($requestedPage, $page)); $page->getResponse()->redirectAndExit($page->getRedirectUrl()); } - } else { + } elseif ($page->getValidatePartial()) { $page->isValidPartial($requestData); + } else { + $page->populate($requestData); } } elseif (($pageData = $this->getPageData($page->getName())) !== null) { $page->populate($pageData); @@ -238,6 +299,39 @@ class Wizard return $request; } + /** + * Return the wizard for the given page or null if its not part of a wizard + * + * @param Form $page The page to return its wizard for + * + * @return Wizard|null + */ + protected function findWizard(Form $page) + { + foreach ($this->getWizards() as $wizard) { + if ($wizard->getPage($page->getName()) === $page) { + return $wizard; + } + } + } + + /** + * Return this wizard's child wizards + * + * @return array + */ + protected function getWizards() + { + $wizards = array(); + foreach ($this->pages as $pageOrWizard) { + if ($pageOrWizard instanceof self) { + $wizards[] = $pageOrWizard; + } + } + + return $wizards; + } + /** * Return the request data based on given form's request method * @@ -264,6 +358,10 @@ class Wizard */ protected function getRequestedPage(array $requestData) { + if ($this->parent) { + return $this->parent->getRequestedPage($requestData); + } + if (isset($requestData[static::BTN_NEXT])) { return $requestData[static::BTN_NEXT]; } elseif (isset($requestData[static::BTN_PREV])) { @@ -280,6 +378,10 @@ class Wizard */ protected function getDirection(Request $request = null) { + if ($this->parent) { + return $this->parent->getDirection($request); + } + $currentPage = $this->getCurrentPage(); if ($request === null) { @@ -299,7 +401,7 @@ class Wizard /** * Return the new page to set as current page * - * Permission is checked by verifying that the requested page's previous page has page data available. + * Permission is checked by verifying that the requested page or its previous page has page data available. * The requested page is automatically permitted without any checks if the origin page is its previous * page or one that occurs later in order. * @@ -312,11 +414,15 @@ class Wizard */ protected function getNewPage($requestedPage, Form $originPage) { + if ($this->parent) { + return $this->parent->getNewPage($requestedPage, $originPage); + } + if (($page = $this->getPage($requestedPage)) !== null) { $permitted = true; $pages = $this->getPages(); - if (($index = array_search($page, $pages, true)) > 0) { + if (! $this->hasPageData($requestedPage) && ($index = array_search($page, $pages, true)) > 0) { $previousPage = $pages[$index - 1]; if ($originPage === null || ($previousPage->getName() !== $originPage->getName() && array_search($originPage, $pages, true) < $index)) @@ -335,6 +441,36 @@ class Wizard ); } + /** + * Return the next or previous page based on the given one + * + * @param Form $page The page to skip + * + * @return Form + */ + protected function skipPage(Form $page) + { + if ($this->parent) { + return $this->parent->skipPage($page); + } + + if ($this->hasPageData($page->getName())) { + $pageData = & $this->getPageData(); + unset($pageData[$page->getName()]); + } + + $pages = $this->getPages(); + if ($this->getDirection() === static::FORWARD) { + $nextPage = $pages[array_search($page, $pages, true) + 1]; + $newPage = $this->getNewPage($nextPage->getName(), $page); + } else { // $this->getDirection() === static::BACKWARD + $previousPage = $pages[array_search($page, $pages, true) - 1]; + $newPage = $this->getNewPage($previousPage->getName(), $page); + } + + return $newPage; + } + /** * Return whether the given page is this wizard's last page * @@ -344,16 +480,33 @@ class Wizard */ protected function isLastPage(Form $page) { + if ($this->parent) { + return $this->parent->isLastPage($page); + } + $pages = $this->getPages(); return $page->getName() === end($pages)->getName(); } + /** + * Return whether all of this wizard's pages were visited by the user + * + * The base implementation just verifies that the very last page has page data available. + * + * @return bool + */ + public function isComplete() + { + $pages = $this->getPages(); + return $this->hasPageData($pages[count($pages) - 1]->getName()); + } + /** * Set whether this wizard has been completed * * @param bool $state Whether this wizard has been completed * - * @return self + * @return $this */ public function setIsFinished($state = true) { @@ -421,6 +574,10 @@ class Wizard */ public function getSession() { + if ($this->parent) { + return $this->parent->getSession(); + } + return Session::getSession()->getNamespace(get_class($this)); } @@ -457,10 +614,11 @@ class Wizard 'button', static::BTN_PREV, array( - 'type' => 'submit', - 'value' => $pages[$index - 1]->getName(), - 'label' => t('Back'), - 'decorators' => array('ViewHelper') + 'type' => 'submit', + 'value' => $pages[$index - 1]->getName(), + 'label' => t('Back'), + 'decorators' => array('ViewHelper'), + 'formnovalidate' => 'formnovalidate' ) ); $page->addElement( @@ -478,10 +636,11 @@ class Wizard 'button', static::BTN_PREV, array( - 'type' => 'submit', - 'value' => $pages[$index - 1]->getName(), - 'label' => t('Back'), - 'decorators' => array('ViewHelper') + 'type' => 'submit', + 'value' => $pages[$index - 1]->getName(), + 'label' => t('Back'), + 'decorators' => array('ViewHelper'), + 'formnovalidate' => 'formnovalidate' ) ); $page->addElement( diff --git a/library/vendor/HTMLPurifier/DefinitionCache/Serializer/README b/library/vendor/HTMLPurifier/DefinitionCache/Serializer/README old mode 100755 new mode 100644 diff --git a/library/vendor/Parsedown/LICENSE.txt b/library/vendor/Parsedown/LICENSE similarity index 100% rename from library/vendor/Parsedown/LICENSE.txt rename to library/vendor/Parsedown/LICENSE diff --git a/library/vendor/Parsedown/SOURCE b/library/vendor/Parsedown/SOURCE index c5a2c7c28..43ee6c61c 100644 --- a/library/vendor/Parsedown/SOURCE +++ b/library/vendor/Parsedown/SOURCE @@ -4,3 +4,4 @@ DESTINATION=. wget -O ${FILENAME}.tar.gz https://github.com/erusev/parsedown/archive/${RELEASE}.tar.gz tar xfz ${FILENAME}.tar.gz -C $DESTINATION/ --strip-components 1 $FILENAME/Parsedown.php $FILENAME/LICENSE.txt chmod 644 $DESTINATION/Parsedown.php +mv LICENSE.txt LICENSE diff --git a/library/vendor/Zend/Amf/Adobe/Auth.php b/library/vendor/Zend/Amf/Adobe/Auth.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Amf/Adobe/DbInspector.php b/library/vendor/Zend/Amf/Adobe/DbInspector.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Amf/Adobe/Introspector.php b/library/vendor/Zend/Amf/Adobe/Introspector.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Amf/Auth/Abstract.php b/library/vendor/Zend/Amf/Auth/Abstract.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Amf/Parse/Resource/MysqlResult.php b/library/vendor/Zend/Amf/Parse/Resource/MysqlResult.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Amf/Parse/Resource/Stream.php b/library/vendor/Zend/Amf/Parse/Resource/Stream.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Amf/Value/Messaging/ArrayCollection.php b/library/vendor/Zend/Amf/Value/Messaging/ArrayCollection.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cache/Backend/ZendServer.php b/library/vendor/Zend/Cache/Backend/ZendServer.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cache/Backend/ZendServer/Disk.php b/library/vendor/Zend/Cache/Backend/ZendServer/Disk.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cache/Backend/ZendServer/ShMem.php b/library/vendor/Zend/Cache/Backend/ZendServer/ShMem.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cloud/AbstractFactory.php b/library/vendor/Zend/Cloud/AbstractFactory.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cloud/DocumentService/Adapter/WindowsAzure.php b/library/vendor/Zend/Cloud/DocumentService/Adapter/WindowsAzure.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php b/library/vendor/Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cloud/QueueService/Adapter/WindowsAzure.php b/library/vendor/Zend/Cloud/QueueService/Adapter/WindowsAzure.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cloud/QueueService/Adapter/ZendQueue.php b/library/vendor/Zend/Cloud/QueueService/Adapter/ZendQueue.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Cloud/QueueService/Message.php b/library/vendor/Zend/Cloud/QueueService/Message.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Config/Json.php b/library/vendor/Zend/Config/Json.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Config/Writer/Json.php b/library/vendor/Zend/Config/Writer/Json.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Config/Writer/Yaml.php b/library/vendor/Zend/Config/Writer/Yaml.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Config/Yaml.php b/library/vendor/Zend/Config/Yaml.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Http/Client/Adapter/Stream.php b/library/vendor/Zend/Http/Client/Adapter/Stream.php old mode 100755 new mode 100644 diff --git a/library/vendor/Zend/Http/Response/Stream.php b/library/vendor/Zend/Http/Response/Stream.php old mode 100755 new mode 100644 diff --git a/library/vendor/dompdf/LICENSE.LGPL b/library/vendor/dompdf/LICENSE similarity index 100% rename from library/vendor/dompdf/LICENSE.LGPL rename to library/vendor/dompdf/LICENSE diff --git a/library/vendor/dompdf/SOURCE b/library/vendor/dompdf/SOURCE index 9343b58cf..c26253e30 100644 --- a/library/vendor/dompdf/SOURCE +++ b/library/vendor/dompdf/SOURCE @@ -1,6 +1,7 @@ curl https://codeload.github.com/dompdf/dompdf/tar.gz/v0.6.1 -o dompdf-0.6.1.tar.gz tar xzf dompdf-0.6.1.tar.gz --strip-components 1 dompdf-0.6.1/{include/*.php,lib,dompdf*.php,LICENSE.LGPL} rm dompdf-0.6.1.tar.gz +mv LICENSE.LGPL LICENSE curl https://codeload.github.com/PhenX/php-font-lib/tar.gz/0.3.1 -o php-font-lib-0.3.1.tar.gz mkdir lib/php-font-lib/classes diff --git a/library/vendor/dompdf/dompdf.php b/library/vendor/dompdf/dompdf.php old mode 100755 new mode 100644 diff --git a/library/vendor/dompdf/dompdf_config.custom.inc.php b/library/vendor/dompdf/dompdf_config.custom.inc.php index 4e58b4a92..25f2ea335 100644 --- a/library/vendor/dompdf/dompdf_config.custom.inc.php +++ b/library/vendor/dompdf/dompdf_config.custom.inc.php @@ -11,7 +11,7 @@ //define("DOMPDF_DPI", 72); //define("DOMPDF_ENABLE_PHP", true); //define("DOMPDF_ENABLE_REMOTE", true); -//define("DOMPDF_ENABLE_CSS_FLOAT", true); +define("DOMPDF_ENABLE_CSS_FLOAT", true); //define("DOMPDF_ENABLE_JAVASCRIPT", false); //define("DEBUGPNG", true); //define("DEBUGKEEPTEMP", true); diff --git a/modules/doc/application/controllers/IcingawebController.php b/modules/doc/application/controllers/IcingawebController.php index 5740f50a9..9fccd32fa 100644 --- a/modules/doc/application/controllers/IcingawebController.php +++ b/modules/doc/application/controllers/IcingawebController.php @@ -1,39 +1,51 @@ getBaseDir('doc'); + if (is_dir($path)) { + return $path; + } + if (($path = $this->Config()->get('documentation', 'icingaweb2')) !== null) { + if (is_dir($path)) { + return $path; + } + } + $this->httpNotFound($this->translate('Documentation for Icinga Web 2 is not available')); + } + /** * View the toc of Icinga Web 2's documentation */ public function tocAction() { - return $this->renderToc(Icinga::app()->getApplicationDir('/../doc'), 'Icinga Web 2', 'doc/icingaweb/chapter'); + $this->renderToc($this->getPath(), 'Icinga Web 2', 'doc/icingaweb/chapter'); } /** * View a chapter of Icinga Web 2's documentation * - * @throws Zend_Controller_Action_Exception If the required parameter 'chapterId' is missing + * @throws \Icinga\Exception\MissingParameterException If the required parameter 'chapter' is missing */ public function chapterAction() { - $chapterId = $this->getParam('chapterId'); - if ($chapterId === null) { - throw new Zend_Controller_Action_Exception( - $this->translate('Missing parameter \'chapterId\''), - 404 - ); - } - return $this->renderChapter( - Icinga::app()->getApplicationDir('/../doc'), - $chapterId, - 'doc/icingaweb/toc', + $chapter = $this->params->getRequired('chapter'); + $this->renderChapter( + $this->getPath(), + $chapter, 'doc/icingaweb/chapter' ); } @@ -43,6 +55,6 @@ class Doc_IcingawebController extends DocController */ public function pdfAction() { - return $this->renderPdf(Icinga::app()->getApplicationDir('/../doc'), 'Icinga Web 2', 'doc/icingaweb/chapter'); + $this->renderPdf($this->getPath(), 'Icinga Web 2', 'doc/icingaweb/chapter'); } } diff --git a/modules/doc/application/controllers/IndexController.php b/modules/doc/application/controllers/IndexController.php index 63b5e8cdf..67d1aad64 100644 --- a/modules/doc/application/controllers/IndexController.php +++ b/modules/doc/application/controllers/IndexController.php @@ -1,6 +1,5 @@ Config()->get('documentation', 'modules')) !== null) { + $path = str_replace('{module}', $module, $path); + if (is_dir($path)) { + return $path; + } + } + if ($suppressErrors) { + return null; + } + $this->httpNotFound($this->translate('Documentation for module \'%s\' is not available'), $module); + } + /** * List modules which are enabled and having the 'doc' directory */ @@ -16,117 +45,96 @@ class Doc_ModuleController extends DocController { $moduleManager = Icinga::app()->getModuleManager(); $modules = array(); - foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $enabledModule) { - $docDir = $moduleManager->getModuleDir($enabledModule, '/doc'); - if (is_dir($docDir)) { - $modules[] = $enabledModule; + foreach ($moduleManager->listInstalledModules() as $module) { + $path = $this->getPath($module, $moduleManager->getModuleDir($module, '/doc'), true); + if ($path !== null) { + $modules[] = $moduleManager->getModule($module, false); } } $this->view->modules = $modules; } /** - * Assert that the given module is enabled + * Assert that the given module is installed * - * @param $moduleName + * @param string $moduleName * - * @throws Zend_Controller_Action_Exception If the required parameter 'moduleName' is empty or either if the - * given module is neither installed nor enabled + * @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed */ - protected function assertModuleEnabled($moduleName) + protected function assertModuleInstalled($moduleName) { - if (empty($moduleName)) { - throw new Zend_Controller_Action_Exception( - $this->translate('Missing parameter \'moduleName\''), - 404 - ); - } $moduleManager = Icinga::app()->getModuleManager(); if (! $moduleManager->hasInstalled($moduleName)) { - throw new Zend_Controller_Action_Exception( - sprintf($this->translate('Module \'%s\' is not installed'), $moduleName), - 404 - ); - } - if (! $moduleManager->hasEnabled($moduleName)) { - throw new Zend_Controller_Action_Exception( - sprintf($this->translate('Module \'%s\' is not enabled'), $moduleName), - 404 - ); + $this->httpNotFound($this->translate('Module \'%s\' is not installed'), $moduleName); } } /** * View the toc of a module's documentation * - * @see assertModuleEnabled() + * @throws \Icinga\Exception\MissingParameterException If the required parameter 'moduleName' is empty + * @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed + * @see assertModuleInstalled() */ public function tocAction() { - $moduleName = $this->getParam('moduleName'); - $this->assertModuleEnabled($moduleName); - $this->view->moduleName = $moduleName; - $moduleManager = Icinga::app()->getModuleManager(); + $module = $this->params->getRequired('moduleName'); + $this->assertModuleInstalled($module); + $this->view->moduleName = $module; try { - return $this->renderToc( - $moduleManager->getModuleDir($moduleName, '/doc'), - $moduleName, + $this->renderToc( + $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')), + $module, 'doc/module/chapter', - array('moduleName' => $moduleName) + array('moduleName' => $module) ); } catch (DocException $e) { - throw new Zend_Controller_Action_Exception($e->getMessage(), 404); + $this->httpNotFound($e->getMessage()); } } /** * View a chapter of a module's documentation * - * @throws Zend_Controller_Action_Exception If the required parameter 'chapterId' is missing or if an error in - * the documentation module's library occurs - * @see assertModuleEnabled() + * @throws \Icinga\Exception\MissingParameterException If one of the required parameters 'moduleName' and + * 'chapter' is empty + * @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed + * @see assertModuleInstalled() */ public function chapterAction() { - $moduleName = $this->getParam('moduleName'); - $this->assertModuleEnabled($moduleName); - $chapterId = $this->getParam('chapterId'); - if ($chapterId === null) { - throw new Zend_Controller_Action_Exception( - $this->translate('Missing parameter \'chapterId\''), - 404 - ); - } - $this->view->moduleName = $moduleName; - $moduleManager = Icinga::app()->getModuleManager(); + $module = $this->params->getRequired('moduleName'); + $this->assertModuleInstalled($module); + $chapter = $this->params->getRequired('chapter'); + $this->view->moduleName = $module; try { - return $this->renderChapter( - $moduleManager->getModuleDir($moduleName, '/doc'), - $chapterId, - $this->_helper->url->url(array('moduleName' => $moduleName), 'doc/module/toc'), + $this->renderChapter( + $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')), + $chapter, 'doc/module/chapter', - array('moduleName' => $moduleName) + array('moduleName' => $module) ); } catch (DocException $e) { - throw new Zend_Controller_Action_Exception($e->getMessage(), 404); + $this->httpNotFound($e->getMessage()); } } /** * View a module's documentation as PDF * - * @see assertModuleEnabled() + * @throws \Icinga\Exception\MissingParameterException If the required parameter 'moduleName' is empty + * @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed + * @see assertModuleInstalled() */ public function pdfAction() { - $moduleName = $this->getParam('moduleName'); - $this->assertModuleEnabled($moduleName); - $moduleManager = Icinga::app()->getModuleManager(); - return $this->renderPdf( - $moduleManager->getModuleDir($moduleName, '/doc'), - $moduleName, + $module = $this->params->getRequired('moduleName'); + $this->assertModuleInstalled($module); + $this->renderPdf( + $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')), + $module, 'doc/module/chapter', - array('moduleName' => $moduleName) + array('moduleName' => $module) ); } } diff --git a/modules/doc/application/controllers/SearchController.php b/modules/doc/application/controllers/SearchController.php new file mode 100644 index 000000000..5d71681eb --- /dev/null +++ b/modules/doc/application/controllers/SearchController.php @@ -0,0 +1,97 @@ +getWebPath()); + $search = new DocSearchRenderer( + new DocSearchIterator( + $parser->getDocTree()->getIterator(), + new DocSearch($this->params->get('q')) + ) + ); + $search->setUrl('doc/icingaweb/chapter'); + if (strlen($this->params->get('q')) < 3) { + $this->view->searches = array(); + return; + } + $searches = array( + 'Icinga Web 2' => $search + ); + foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $module) { + if (($path = $this->getModulePath($module)) !== null) { + try { + $parser = new DocParser($path); + $search = new DocSearchRenderer( + new DocSearchIterator( + $parser->getDocTree()->getIterator(), + new DocSearch($this->params->get('q')) + ) + ); + } catch (DocException $e) { + continue; + } + $search + ->setUrl('doc/module/chapter') + ->setUrlParams(array('moduleName' => $module)); + $searches[$module] = $search; + } + } + $this->view->searches = $searches; + } + + /** + * Get the path to a module's documentation + * + * @param string $module + * + * @return string|null + */ + protected function getModulePath($module) + { + if (is_dir(($path = Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')))) { + return $path; + } + if (($path = $this->Config()->get('documentation', 'modules')) !== null) { + $path = str_replace('{module}', $module, $path); + if (is_dir($path)) { + return $path; + } + } + return null; + } + + /** + * Get the path to Icinga Web 2's documentation + * + * @return string + * + * @throws Zend_Controller_Action_Exception If Icinga Web 2's documentation is not available + */ + protected function getWebPath() + { + $path = Icinga::app()->getBaseDir('doc'); + if (is_dir($path)) { + return $path; + } + if (($path = $this->Config()->get('documentation', 'icingaweb2')) !== null) { + if (is_dir($path)) { + return $path; + } + } + $this->httpNotFound($this->translate('Documentation for Icinga Web 2 is not available')); + } +} diff --git a/modules/doc/application/controllers/StyleController.php b/modules/doc/application/controllers/StyleController.php index d5440c95a..8661d024a 100644 --- a/modules/doc/application/controllers/StyleController.php +++ b/modules/doc/application/controllers/StyleController.php @@ -1,4 +1,5 @@ add( 'guide', array( - 'title' => $this->translate('Style Guide'), - 'url' => 'doc/style/guide' + 'label' => $this->translate('Style Guide'), + 'url' => 'doc/style/guide' ) )->add( 'font', array( - 'title' => $this->translate('Icons'), - 'url' => 'doc/style/font' + 'label' => $this->translate('Icons'), + 'title' => $this->translate('List all available icons'), + 'url' => 'doc/style/font' ) ); } diff --git a/modules/doc/application/views/scripts/chapter.phtml b/modules/doc/application/views/scripts/chapter.phtml index 7657d69fb..fc5b3073b 100644 --- a/modules/doc/application/views/scripts/chapter.phtml +++ b/modules/doc/application/views/scripts/chapter.phtml @@ -1,3 +1,6 @@ -
    - render($this, $this->getHelper('Url')); ?> +
    + showOnlyCloseButton() ?> +
    +
    +
    diff --git a/modules/doc/application/views/scripts/index/index.phtml b/modules/doc/application/views/scripts/index/index.phtml index e4218bee2..af9ff087a 100644 --- a/modules/doc/application/views/scripts/index/index.phtml +++ b/modules/doc/application/views/scripts/index/index.phtml @@ -1,6 +1,20 @@ -
    -

    translate('Available documentations'); ?>

    - +
    + showOnlyCloseButton(); ?> +

    translate('Available documentations'); ?>

    +
    +
    +
      +
    • qlink( + 'Icinga Web 2', + 'doc/icingaweb/toc', + null, + array('title' => $this->translate('Show the documentation\'s table of contents for Icinga Web 2')) + ); ?>
    • +
    • qlink( + $this->translate('Module documentations'), + 'doc/module/', + null, + array('title' => $this->translate('List all modules for which documentation is available')) + ); ?>
    • +
    +
    diff --git a/modules/doc/application/views/scripts/module/chapter.phtml b/modules/doc/application/views/scripts/module/chapter.phtml deleted file mode 100644 index 7657d69fb..000000000 --- a/modules/doc/application/views/scripts/module/chapter.phtml +++ /dev/null @@ -1,3 +0,0 @@ -
    - render($this, $this->getHelper('Url')); ?> -
    diff --git a/modules/doc/application/views/scripts/module/index.phtml b/modules/doc/application/views/scripts/module/index.phtml index cc184016f..d80e05cb3 100644 --- a/modules/doc/application/views/scripts/module/index.phtml +++ b/modules/doc/application/views/scripts/module/index.phtml @@ -1,10 +1,19 @@ -

    translate('Module documentations'); ?>

    -
      - -
    • - - - -
    • +
      + showOnlyCloseButton(); ?> +

      translate('Module documentations'); ?>

      +
      +
      +
        + +
      • qlink( + $module->getTitle(), + 'doc/module/toc', + array('moduleName' => $module->getName()), + array('title' => sprintf( + $this->translate('Show the documentation\'s table of contents for the %s'), + $module->getTitle() + )) + ); ?>
      • -
      +
    +
    diff --git a/modules/doc/application/views/scripts/module/toc.phtml b/modules/doc/application/views/scripts/module/toc.phtml deleted file mode 100644 index ca6283d67..000000000 --- a/modules/doc/application/views/scripts/module/toc.phtml +++ /dev/null @@ -1,6 +0,0 @@ -
    -

    -
    -
    - render($this, $this->getHelper('Url')); ?> -
    diff --git a/modules/doc/application/views/scripts/pdf.phtml b/modules/doc/application/views/scripts/pdf.phtml index 72d77f3c0..2666efb1c 100644 --- a/modules/doc/application/views/scripts/pdf.phtml +++ b/modules/doc/application/views/scripts/pdf.phtml @@ -1,7 +1,5 @@ -

    translate('Documentation'); ?>

    -
    - render($this, $this->getHelper('Url')); ?> -
    -
    - render($this, $this->getHelper('Url')); ?> +
    +

    + +
    diff --git a/modules/doc/application/views/scripts/search/index.phtml b/modules/doc/application/views/scripts/search/index.phtml new file mode 100644 index 000000000..c613f04df --- /dev/null +++ b/modules/doc/application/views/scripts/search/index.phtml @@ -0,0 +1,8 @@ +
    + $search): ?> +

    escape($title) ?>

    + isEmpty() + ? $this->translate('No documentation found matching the filter') + : $search ?> + +
    diff --git a/modules/doc/application/views/scripts/style/guide.phtml b/modules/doc/application/views/scripts/style/guide.phtml index 0be3d7563..3cfcc1ca9 100644 --- a/modules/doc/application/views/scripts/style/guide.phtml +++ b/modules/doc/application/views/scripts/style/guide.phtml @@ -1,13 +1,37 @@
    tabs ?> -

    Style Guide

    -

    H1 - header

    -

    H2 - header

    -

    H3 - header

    -

    H4 - header

    -
    H5 - header
    -
    H6 - header
    +

    Header h1

    +

    Header h2

    +

    Header h3

    +

    Header h4

    +
    Header h5
    +
    Header h6
    + +

    This is a paragraph. This is a paragraph. This is a paragraph. This is a paragraph. This is a paragraph. This is a paragraph. A link pointing somewhere. This is a paragraph. This is a paragraph. This is a paragraph. This is a paragraph. This is a paragraph.

    + + + + + + + + + + + + + + + + + + + + + +
    Table Head - th in theadtd in thead +
    Tbody - thTbody - td
    Tbody - thTbody - td
    Tbody - thTbody - td
    diff --git a/modules/doc/application/views/scripts/toc.phtml b/modules/doc/application/views/scripts/toc.phtml index ca6283d67..5a57e5bda 100644 --- a/modules/doc/application/views/scripts/toc.phtml +++ b/modules/doc/application/views/scripts/toc.phtml @@ -1,6 +1,7 @@
    -

    + showOnlyCloseButton() ?> +

    -
    - render($this, $this->getHelper('Url')); ?> +
    +
    diff --git a/modules/doc/configuration.php b/modules/doc/configuration.php index 87e5da77a..392009798 100644 --- a/modules/doc/configuration.php +++ b/modules/doc/configuration.php @@ -1,14 +1,13 @@ menuSection($this->translate('Documentation'), array( 'title' => 'Documentation', 'icon' => 'book', 'url' => 'doc', - 'priority' => 190 + 'priority' => 700 )); $section->add('Icinga Web 2', array( @@ -19,5 +18,7 @@ $section->add('Module documentations', array( )); $section->add($this->translate('Developer - Style'), array( 'url' => 'doc/style/guide', - 'priority' => 200, + 'priority' => 790 )); + +$this->provideSearchUrl($this->translate('Doc'), 'doc/search', -10); diff --git a/modules/doc/doc/1-module-documentation.md b/modules/doc/doc/1-module-documentation.md new file mode 100644 index 000000000..edf20aac2 --- /dev/null +++ b/modules/doc/doc/1-module-documentation.md @@ -0,0 +1,67 @@ +# Writing Module Documentation + +![Markdown](/img/doc/doc/markdown.png) + +Icinga Web 2 is capable of viewing your module's documentation, if the documentation is written in +[Markdown](http://en.wikipedia.org/wiki/Markdown). Please refer to +[Markdown Syntax Documentation](http://daringfireball.net/projects/markdown/syntax) for Markdown's formatting syntax. + +## Where to Put Module Documentation? + +By default, your module's Markdown documentation files must be placed in the `doc` directory beneath your module's root +directory, e.g.: + + example-module/doc + +## Chapters + +Each Markdown documentation file represents a chapter of your module's documentation. The first found heading inside +each file is the chapter's title. The order of chapters is based on the case insensitive "Natural Order" of your files' +names. Natural Order means that the file names are ordered in the way which seems natural to humans. +It is best practice to prefix Markdown documentation file names with numbers to ensure that they appear in the correct +order, e.g.: + + 1-about.md + 2-installation.md + 3-configuration.md + +## Table Of Contents + +The table of contents for your module's documentation is auto-generated based on all found headings inside each +Markdown documentation file. + +## Linking Between Headings + +For linking between headings, place an anchor where you want to link to, e.g.: + + # Heading + +Please note that anchors have to be unique across all your Markdown documentation files. + +Now you can reference the anchor either in the same or **in another** Markdown documentation file, e.g.: + + This is a link to [Heading](#heading). + +Other tools support linking between headings by giving the filename plus the anchor to link to, e.g.: + + This is a link to [About/Heading](1-about.md#heading.md) + +This syntax is also supported in Icinga Web 2. + +## Including Images + +Images must placed in the `img` directory beneath your module's `public` directory, e.g.: + + example-module/public/img/doc + +Module images can be accessed using the following URL: + + {baseURL}/img/{moduleName}/{file} e.g. icingaweb/img/example-module/doc/example.png + +Markdown's image syntax is very similar to Markdown's link syntax, but prefixed with an exclamation mark, e.g.: + + ![Alt text](http://path/to/img.png "Optional Title") + +URLs to images inside your Markdown documentation files must be specified without the base URL, e.g.: + + ![Example](/img/example-module/doc/example.png) diff --git a/modules/doc/library/Doc/DocController.php b/modules/doc/library/Doc/DocController.php index bc782953f..10a9b1d40 100644 --- a/modules/doc/library/Doc/DocController.php +++ b/modules/doc/library/Doc/DocController.php @@ -1,52 +1,67 @@ hasParam('chapter')) { + $this->params->set('chapter', $this->getParam('chapter')); + } + if ($this->hasParam('moduleName')) { + $this->params->set('moduleName', $this->getParam('moduleName')); + } + } + /** * Render a chapter * - * @param string $path Path to the documentation - * @param string $chapterId ID of the chapter - * @param string $tocUrl - * @param string $url - * @param array $urlParams + * @param string $path Path to the documentation + * @param string $chapter ID of the chapter + * @param string $url URL to replace links with + * @param array $urlParams Additional URL parameters */ - protected function renderChapter($path, $chapterId, $tocUrl, $url, array $urlParams = array()) + protected function renderChapter($path, $chapter, $url, array $urlParams = array()) { $parser = new DocParser($path); - $this->view->sectionRenderer = new SectionRenderer( - $parser->getDocTree(), - SectionRenderer::decodeUrlParam($chapterId), - $tocUrl, - $url, - $urlParams - ); - $this->view->title = $chapterId; - return $this->render('chapter', null, true); + $section = new DocSectionRenderer($parser->getDocTree(), DocSectionRenderer::decodeUrlParam($chapter)); + $this->view->section = $section + ->setUrl($url) + ->setUrlParams($urlParams) + ->setHighlightSearch($this->params->get('highlight-search')); + $this->view->title = $chapter; + $this->render('chapter', null, true); } /** * Render a toc * - * @param string $path Path to the documentation - * @param string $name Name of the documentation - * @param string $url - * @param array $urlParams + * @param string $path Path to the documentation + * @param string $name Name of the documentation + * @param string $url URL to replace links with + * @param array $urlParams Additional URL parameters */ protected function renderToc($path, $name, $url, array $urlParams = array()) { $parser = new DocParser($path); - $this->view->tocRenderer = new TocRenderer($parser->getDocTree(), $url, $urlParams); + $toc = new DocTocRenderer($parser->getDocTree()->getIterator()); + $this->view->toc = $toc + ->setUrl($url) + ->setUrlParams($urlParams); $name = ucfirst($name); - $this->view->docName = $name; $this->view->title = sprintf($this->translate('%s Documentation'), $name); - return $this->render('toc', null, true); + $this->render('toc', null, true); } /** @@ -60,17 +75,16 @@ class DocController extends ModuleActionController protected function renderPdf($path, $name, $url, array $urlParams = array()) { $parser = new DocParser($path); - $docTree = $parser->getDocTree(); - $this->view->tocRenderer = new TocRenderer($docTree, $url, $urlParams); - $this->view->sectionRenderer = new SectionRenderer( - $docTree, - null, - null, - $url, - $urlParams - ); - $this->view->docName = $name; + $toc = new DocTocRenderer($parser->getDocTree()->getIterator()); + $this->view->toc = $toc + ->setUrl($url) + ->setUrlParams($urlParams); + $section = new DocSectionRenderer($parser->getDocTree()); + $this->view->section = $section + ->setUrl($url) + ->setUrlParams($urlParams); + $this->view->title = sprintf($this->translate('%s Documentation'), $name); $this->_request->setParam('format', 'pdf'); - return $this->render('pdf', null, true); + $this->_helper->viewRenderer->setRender('pdf', null, true); } } diff --git a/modules/doc/library/Doc/DocIterator.php b/modules/doc/library/Doc/DocIterator.php index 43a9c7727..430d25445 100644 --- a/modules/doc/library/Doc/DocIterator.php +++ b/modules/doc/library/Doc/DocIterator.php @@ -1,14 +1,15 @@ docIterator as $fileInfo) { - /* @var $file \SplFileInfo */ + /** @var $fileInfo \SplFileInfo */ $file = $fileInfo->openFile(); - /* @var $file \SplFileObject */ $lastLine = null; - foreach ($file as $line) { - $header = $this->extractHeader($line, $lastLine); + $stack = new SplStack(); + $cachingIterator = new CachingIterator($file, CachingIterator::TOSTRING_USE_CURRENT); + for ($cachingIterator->rewind(); $line = $cachingIterator->valid(); $cachingIterator->next()) { + $fileIterator = $cachingIterator->getInnerIterator(); + $line = $cachingIterator->current(); + $header = $this->extractHeader($line, $fileIterator->valid() ? $fileIterator->current() : null); if ($header !== null) { - list($title, $id, $level) = $header; + list($title, $id, $level, $headerStyle) = $header; while (! $stack->isEmpty() && $stack->top()->getLevel() >= $level) { $stack->pop(); } if ($id === null) { $path = array(); foreach ($stack as $section) { - /* @var $section Section */ + /** @var $section DocSection */ $path[] = $section->getTitle(); } $path[] = $title; @@ -152,21 +172,46 @@ class DocParser } else { $noFollow = false; } + if ($tree->getNode($id) !== null) { + $id = uniqid($id); + } + $section = new DocSection(); + $section + ->setId($id) + ->setTitle($title) + ->setLevel($level) + ->setNoFollow($noFollow); if ($stack->isEmpty()) { - $chapterId = $id; - $section = new Section($id, $title, $level, $noFollow, $chapterId); - $tree->addRoot($section); + $section->setChapter($section); + $tree->addChild($section); } else { - $chapterId = $stack->bottom()->getId(); - $section = new Section($id, $title, $level, $noFollow, $chapterId); + $section->setChapter($stack->bottom()); $tree->addChild($section, $stack->top()); } $stack->push($section); + if ($headerStyle === static::HEADER_SETEXT) { + $cachingIterator->next(); + continue; + } } else { + if ($stack->isEmpty()) { + $title = ucfirst($file->getBasename('.' . pathinfo($file->getFilename(), PATHINFO_EXTENSION))); + $id = $title; + if ($tree->getNode($id) !== null) { + $id = uniqid($id); + } + $section = new DocSection(); + $section + ->setId($id) + ->setTitle($title) + ->setLevel(1) + ->setNoFollow(true); + $section->setChapter($section); + $tree->addChild($section); + $stack->push($section); + } $stack->top()->appendContent($line); } - // Save last line for setext-style headers - $lastLine = $line; } } return $tree; diff --git a/modules/doc/library/Doc/DocSection.php b/modules/doc/library/Doc/DocSection.php new file mode 100644 index 000000000..a8efc6fc3 --- /dev/null +++ b/modules/doc/library/Doc/DocSection.php @@ -0,0 +1,167 @@ +chapter = $section; + return $this; + } + + /** + * Get the chapter the section belongs to + * + * @return DocSection + */ + public function getChapter() + { + return $this->chapter; + } + + /** + * Append content + * + * @param string $content + */ + public function appendContent($content) + { + $this->content[] = $content; + } + + /** + * Get the content of the section + * + * @return array + */ + public function getContent() + { + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + return parent::setId(str_replace(' ', '-', (string) $id)); + } + + /** + * Set the header level + * + * @param int $level Header level + * + * @return $this + */ + public function setLevel($level) + { + $this->level = (int) $level; + return $this; + } + + /** + * Get the header level + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Set whether to instruct search engines to not index the link to the section + * + * @param bool $noFollow Whether to instruct search engines to not index the link to the section + * + * @return $this + */ + public function setNoFollow($noFollow = true) + { + $this->noFollow = (bool) $noFollow; + return $this; + } + + /** + * Get whether to instruct search engines to not index the link to the section + * + * @return bool + */ + public function getNoFollow() + { + return $this->noFollow; + } + + /** + * Set the title of the section + * + * @param string $title Title of the section + * + * @return $this + */ + public function setTitle($title) + { + $this->title = (string) $title; + return $this; + } + + /** + * Get the title of the section + * + * @return string + */ + public function getTitle() + { + return $this->title; + } +} diff --git a/modules/doc/library/Doc/DocSectionFilterIterator.php b/modules/doc/library/Doc/DocSectionFilterIterator.php new file mode 100644 index 000000000..fa92583ca --- /dev/null +++ b/modules/doc/library/Doc/DocSectionFilterIterator.php @@ -0,0 +1,79 @@ +chapter = $chapter; + } + + /** + * Accept sections that are part of the given chapter + * + * @return bool Whether the current element of the iterator is acceptable + * through this filter + */ + public function accept() + { + $section = $this->current(); + /** @var \Icinga\Module\Doc\DocSection $section */ + if ($section->getChapter()->getId() === $this->chapter) { + return true; + } + return false; + } + + /** + * {@inheritdoc} + */ + public function getChildren() + { + return new static($this->getInnerIterator()->getChildren(), $this->chapter); + } + + /** + * {@inheritdoc} + */ + public function count() + { + return iterator_count($this); + } + + /** + * Whether the filter swallowed every section + * + * @return bool + */ + public function isEmpty() + { + return $this->count() === 0; + } +} diff --git a/modules/doc/library/Doc/DocTree.php b/modules/doc/library/Doc/DocTree.php deleted file mode 100644 index 1b112649c..000000000 --- a/modules/doc/library/Doc/DocTree.php +++ /dev/null @@ -1,80 +0,0 @@ -getId(); - if (isset($this->nodes[$rootId])) { - $rootId = uniqid($rootId); -// throw new LogicException( -// sprintf('Can\'t add root node: a root node with the id \'%s\' already exists', $rootId) -// ); - } - $this->nodes[$rootId] = $this->appendChild($root); - } - - /** - * Append a child node to a parent node - * - * @param Identifiable $child - * @param Identifiable $parent - * - * @throws LogicException If the the tree does not contain the parent node - */ - public function addChild(Identifiable $child, Identifiable $parent) - { - $childId = $child->getId(); - $parentId = $parent->getId(); - if (isset($this->nodes[$childId])) { - $childId = uniqid($childId); -// throw new LogicException( -// sprintf('Can\'t add child node: a child node with the id \'%s\' already exists', $childId) -// ); - } - if (! isset($this->nodes[$parentId])) { - throw new LogicException( - sprintf(mt('doc', 'Can\'t add child node: there\'s no parent node having the id \'%s\''), $parentId) - ); - } - $this->nodes[$childId] = $this->nodes[$parentId]->appendChild($child); - } - - /** - * Get a node - * - * @param mixed $id - * - * @return Node|null - */ - public function getNode($id) - { - if (! isset($this->nodes[$id])) { - return null; - } - return $this->nodes[$id]; - } -} diff --git a/modules/doc/library/Doc/Exception/ChapterNotFoundException.php b/modules/doc/library/Doc/Exception/ChapterNotFoundException.php index cd048a162..5a5d41936 100644 --- a/modules/doc/library/Doc/Exception/ChapterNotFoundException.php +++ b/modules/doc/library/Doc/Exception/ChapterNotFoundException.php @@ -1,10 +1,11 @@ getInnerIterator()->current(); - /* @var $current \SplFileInfo */ - if (! $current->isFile()) { - return false; - } - $filename = $current->getFilename(); - $sfx = substr($filename, -3); - return $sfx === false ? false : strtolower($sfx) === '.md'; - } -} diff --git a/modules/doc/library/Doc/NonEmptyFileIterator.php b/modules/doc/library/Doc/NonEmptyFileIterator.php deleted file mode 100644 index 71bf5acfa..000000000 --- a/modules/doc/library/Doc/NonEmptyFileIterator.php +++ /dev/null @@ -1,31 +0,0 @@ -getInnerIterator()->current(); - /* @var $current \SplFileInfo */ - if (! $current->isFile() - || $current->getSize() === 0 - ) { - return false; - } - return true; - } -} diff --git a/modules/doc/library/Doc/Renderer.php b/modules/doc/library/Doc/Renderer.php deleted file mode 100644 index 0aebb89b9..000000000 --- a/modules/doc/library/Doc/Renderer.php +++ /dev/null @@ -1,75 +0,0 @@ -url = (string) $url; + return $this; + } + + /** + * Get the URL to replace links with + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set additional URL parameters + * + * @param array $urlParams + * + * @return $this + */ + public function setUrlParams(array $urlParams) + { + $this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams); + return $this; + } + + /** + * Get additional URL parameters + * + * @return array + */ + public function getUrlParams() + { + return $this->urlParams; + } + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = Icinga::app()->getViewRenderer()->view; + } + return $this->view; + } + + /** + * Encode an anchor identifier + * + * @param string $anchor + * + * @return string + */ + public static function encodeAnchor($anchor) + { + return rawurlencode($anchor); + } + + /** + * Decode an anchor identifier + * + * @param string $anchor + * + * @return string + */ + public static function decodeAnchor($anchor) + { + return rawurldecode($anchor); + } + + /** + * Encode a URL parameter + * + * @param string $param + * + * @return string + */ + public static function encodeUrlParam($param) + { + return str_replace(array('%2F','%5C'), array('%252F','%255C'), rawurlencode($param)); + } + + /** + * Decode a URL parameter + * + * @param string $param + * + * @return string + */ + public static function decodeUrlParam($param) + { + return str_replace(array('%2F', '%5C'), array('/', '\\'), $param); + } + + /** + * Render to HTML + * + * @return string + */ + abstract public function render(); + + /** + * Render to HTML + * + * @return string + * @see \Icinga\Module\Doc\Renderer::render() For the render method. + */ + public function __toString() + { + try { + return $this->render(); + } catch (Exception $e) { + return $e->getMessage() . ': ' . $e->getTraceAsString(); + } + } +} diff --git a/modules/doc/library/Doc/Renderer/DocSearchRenderer.php b/modules/doc/library/Doc/Renderer/DocSearchRenderer.php new file mode 100644 index 000000000..a89e49333 --- /dev/null +++ b/modules/doc/library/Doc/Renderer/DocSearchRenderer.php @@ -0,0 +1,144 @@ +content[] = ''; + } + + /** + * {@inheritdoc} + */ + public function beginChildren() + { + if ($this->getInnerIterator()->getMatches()) { + $this->content[] = '
      '; + } + } + + /** + * {@inheritdoc} + */ + public function endChildren() + { + if ($this->getInnerIterator()->getMatches()) { + $this->content[] = '
    '; + } + } + + /** + * {@inheritdoc} + */ + public function render() + { + foreach ($this as $section) { + if (($matches = $this->getInnerIterator()->getMatches()) === null) { + continue; + } + $title = $this->getView()->escape($section->getTitle()); + $contentMatches = array(); + foreach ($matches as $match) { + if ($match->getMatchType() === DocSearchMatch::MATCH_HEADER) { + $title = $match->highlight(); + } else { + $contentMatches[] = sprintf( + '

    %s

    ', + $match->highlight() + ); + } + } + $path = $this->getView()->getHelper('Url')->url( + array_merge( + $this->getUrlParams(), + array( + 'chapter' => $this->encodeUrlParam($section->getChapter()->getId()) + ) + ), + $this->url, + false, + false + ); + $url = $this->getView()->url( + $path, + array('highlight-search' => $this->getInnerIterator()->getSearch()->getInput()) + ); + /** @var \Icinga\Web\Url $url */ + $url->setAnchor($this->encodeAnchor($section->getId())); + $urlAttributes = array( + 'data-base-target' => '_next', + 'title' => sprintf( + $this->getView()->translate( + 'Show all matches of "%s" in %sthe chapter "%s"', + 'search.render.section.link' + ), + $this->getInnerIterator()->getSearch()->getInput(), + $section->getId() !== $section->getChapter()->getId() ? sprintf( + $this->getView()->translate('the section "%s" of ', 'search.render.section.link'), + $section->getTitle() + ) : '', + $section->getChapter()->getTitle() + ) + ); + if ($section->getNoFollow()) { + $urlAttributes['rel'] = 'nofollow'; + } + $this->content[] = '
  • ' . $this->getView()->qlink( + $title, + $url->getAbsoluteUrl(), + null, + $urlAttributes, + false + ); + if (! empty($contentMatches)) { + $this->content = array_merge($this->content, $contentMatches); + } + if (! $section->hasChildren()) { + $this->content[] = '
  • '; + } + } + return implode("\n", $this->content); + } +} diff --git a/modules/doc/library/Doc/Renderer/DocSectionRenderer.php b/modules/doc/library/Doc/Renderer/DocSectionRenderer.php new file mode 100644 index 000000000..088c5d617 --- /dev/null +++ b/modules/doc/library/Doc/Renderer/DocSectionRenderer.php @@ -0,0 +1,285 @@ +getIterator(), $chapter); + if ($filter->isEmpty()) { + throw new ChapterNotFoundException( + mt('doc', 'Chapter %s not found'), $chapter + ); + } + parent::__construct( + $filter, + RecursiveIteratorIterator::SELF_FIRST + ); + } else { + parent::__construct($tree->getIterator(), RecursiveIteratorIterator::SELF_FIRST); + } + $this->tree = $tree; + $this->parsedown = Parsedown::instance(); + } + + /** + * Set the search criteria to highlight + * + * @param string $highlightSearch + * + * @return $this + */ + public function setHighlightSearch($highlightSearch) + { + $this->highlightSearch = $highlightSearch; + return $this; + } + + /** + * Get the search criteria to highlight + * + * @return string + */ + public function getHighlightSearch() + { + return $this->highlightSearch; + } + + /** + * Syntax highlighting for PHP code + * + * @param array $match + * + * @return string + */ + protected function highlightPhp($match) + { + return '
    ' . highlight_string(htmlspecialchars_decode($match[1]), true) . '
    '; + } + + /** + * Highlight search criteria + * + * @param string $html + * @param DocSearch $search Search criteria + * + * @return string + */ + protected function highlightSearch($html, DocSearch $search) + { + $doc = new DOMDocument(); + @$doc->loadHTML($html); + $iter = new RecursiveIteratorIterator(new DomNodeIterator($doc), RecursiveIteratorIterator::SELF_FIRST); + foreach ($iter as $node) { + if ($node->nodeType !== XML_TEXT_NODE + || ($node->parentNode->nodeType === XML_ELEMENT_NODE && $node->parentNode->tagName === 'code') + ) { + continue; + } + $text = $node->nodeValue; + if (($match = $search->search($text)) === null) { + continue; + } + $matches = $match->getMatches(); + ksort($matches); + $offset = 0; + $fragment = $doc->createDocumentFragment(); + foreach ($matches as $position => $match) { + $fragment->appendChild($doc->createTextNode(substr($text, $offset, $position - $offset))); + $fragment->appendChild($doc->createElement('span', $match)) + ->setAttribute('class', DocSearchMatch::HIGHLIGHT_CSS_CLASS); + $offset = $position + strlen($match); + } + $fragment->appendChild($doc->createTextNode(substr($text, $offset))); + $node->parentNode->replaceChild($fragment, $node); + } + // Remove removeChild($doc->doctype); + // Remove and + return substr($doc->saveHTML(), 12, -15); + } + + /** + * Markup notes + * + * @param array $match + * + * @return string + */ + protected function markupNotes($match) + { + $doc = new DOMDocument(); + $doc->loadHTML($match[0]); + $xpath = new DOMXPath($doc); + $blockquote = $xpath->query('//blockquote[1]')->item(0); + /** @var \DOMElement $blockquote */ + if (strtolower(substr(trim($blockquote->nodeValue), 0, 5)) === 'note:') { + $blockquote->setAttribute('class', 'note'); + } + return $doc->saveXML($blockquote); + } + + /** + * Replace img src tags + * + * @param $match + * + * @return string + */ + protected function replaceImg($match) + { + $doc = new DOMDocument(); + $doc->loadHTML($match[0]); + $xpath = new DOMXPath($doc); + $img = $xpath->query('//img[1]')->item(0); + /** @var \DOMElement $img */ + $img->setAttribute('src', Url::fromPath($img->getAttribute('src'))->getAbsoluteUrl()); + return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>' + } + + /** + * Replace link + * + * @param array $match + * + * @return string + */ + protected function replaceLink($match) + { + if (($section = $this->tree->getNode($this->decodeAnchor($match['fragment']))) === null) { + return $match[0]; + } + /** @var \Icinga\Module\Doc\DocSection $section */ + $path = $this->getView()->getHelper('Url')->url( + array_merge( + $this->urlParams, + array( + 'chapter' => $this->encodeUrlParam($section->getChapter()->getId()) + ) + ), + $this->url, + false, + false + ); + $url = $this->getView()->url($path); + /** @var \Icinga\Web\Url $url */ + $url->setAnchor($this->encodeAnchor($section->getId())); + return sprintf( + 'getNoFollow() ? 'rel="nofollow" ' : '', + $url->getAbsoluteUrl() + ); + } + + /** + * {@inheritdoc} + */ + public function render() + { + $search = null; + if (($highlightSearch = $this->getHighlightSearch()) !== null) { + $search = new DocSearch($highlightSearch); + } + foreach ($this as $section) { + $title = $section->getTitle(); + if ($search !== null && ($match = $search->search($title)) !== null) { + $title = $match->highlight(); + } else { + $title = $this->getView()->escape($title); + } + $this->content[] = sprintf( + '%3$s', + static::encodeAnchor($section->getId()), + $section->getLevel(), + $title + ); + $html = $this->parsedown->text(implode('', $section->getContent())); + if (empty($html)) { + continue; + } + $html = preg_replace_callback( + '#
    (.*?)
    #s', + array($this, 'highlightPhp'), + $html + ); + $html = preg_replace_callback( + '/]+>/', + array($this, 'replaceImg'), + $html + ); + $html = preg_replace_callback( + '#
    .+
    #ms', + array($this, 'markupNotes'), + $html + ); + $html = preg_replace_callback( + '/[^>]*?\s+)?href="(?:(?!http:\/\/)[^"#]*)#(?P[^"]+)"/', + array($this, 'replaceLink'), + $html + ); + if ($search !== null) { + $html = $this->highlightSearch($html, $search); + } + $this->content[] = $html; + } + return implode("\n", $this->content); + } +} diff --git a/modules/doc/library/Doc/Renderer/DocTocRenderer.php b/modules/doc/library/Doc/Renderer/DocTocRenderer.php new file mode 100644 index 000000000..b0f78d3eb --- /dev/null +++ b/modules/doc/library/Doc/Renderer/DocTocRenderer.php @@ -0,0 +1,116 @@ +content[] = ''; + } + + /** + * {@inheritdoc} + */ + public function beginChildren() + { + $this->content[] = '
      '; + } + + /** + * {@inheritdoc} + */ + public function endChildren() + { + $this->content[] = '
    '; + } + + /** + * {@inheritdoc} + */ + public function render() + { + $view = $this->getView(); + $zendUrlHelper = $view->getHelper('Url'); + foreach ($this as $section) { + $path = $zendUrlHelper->url( + array_merge( + $this->urlParams, + array( + 'chapter' => $this->encodeUrlParam($section->getChapter()->getId()) + ) + ), + $this->url, + false, + false + ); + $url = $view->url($path); + /** @var \Icinga\Web\Url $url */ + $url->setAnchor($this->encodeAnchor($section->getId())); + $urlAttributes = array( + 'data-base-target' => '_next', + 'title' => sprintf( + $this->getView()->translate('Show the %schapter "%s"', 'toc.render.section.link'), + $section->getId() !== $section->getChapter()->getId() ? sprintf( + $this->getView()->translate('section "%s" of the ', 'toc.render.section.link'), + $section->getTitle() + ) : '', + $section->getChapter()->getTitle() + ) + ); + if ($section->getNoFollow()) { + $urlAttributes['rel'] = 'nofollow'; + } + $this->content[] = '
  • ' . $this->getView()->qlink( + $section->getTitle(), + $url->getAbsoluteUrl(), + null, + $urlAttributes + ); + if (! $section->hasChildren()) { + $this->content[] = '
  • '; + } + } + return implode("\n", $this->content); + } +} diff --git a/modules/doc/library/Doc/Search/DocSearch.php b/modules/doc/library/Doc/Search/DocSearch.php new file mode 100644 index 000000000..459cef208 --- /dev/null +++ b/modules/doc/library/Doc/Search/DocSearch.php @@ -0,0 +1,95 @@ +input = $search = (string) $search; + $criteria = array(); + if (preg_match_all('/"(?P[^"]*)"/', $search, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $unquoted = array(); + $offset = 0; + foreach ($matches as $match) { + $fullMatch = $match[0]; + $searchMatch = $match['search']; + $unquoted[] = substr($search, $offset, $fullMatch[1] - $offset); + $offset = $fullMatch[1] + strlen($fullMatch[0]); + if (strlen($searchMatch[0]) > 0) { + $criteria[] = $searchMatch[0]; + } + } + $unquoted[] = substr($search, $offset); + $search = implode(' ', $unquoted); + } + $this->search = array_map( + 'strtolower', + array_unique(array_merge($criteria, array_filter(explode(' ', trim($search))))) + ); + } + + /** + * Get the search criteria + * + * @return array + */ + public function getCriteria() + { + return $this->search; + } + + /** + * Get the search string + * + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * Search in the given line + * + * @param string $line + * + * @return DocSearchMatch|null + */ + public function search($line) + { + $match = new DocSearchMatch(); + $match->setLine($line); + foreach ($this->search as $criteria) { + $offset = 0; + while (($position = stripos($line, $criteria, $offset)) !== false) { + $match->appendMatch(substr($line, $position, strlen($criteria)), $position); + $offset = $position + 1; + } + } + return $match->isEmpty() ? null : $match; + } +} diff --git a/modules/doc/library/Doc/Search/DocSearchIterator.php b/modules/doc/library/Doc/Search/DocSearchIterator.php new file mode 100644 index 000000000..b239254aa --- /dev/null +++ b/modules/doc/library/Doc/Search/DocSearchIterator.php @@ -0,0 +1,120 @@ +search = $search; + parent::__construct($iterator); + } + + /** + * Accept sections that match the search + * + * @return bool Whether the current element of the iterator is acceptable + * through this filter + */ + public function accept() + { + $section = $this->current(); + /** @var $section \Icinga\Module\Doc\DocSection */ + $matches = array(); + if (($match = $this->search->search($section->getTitle())) !== null) { + $matches[] = $match->setMatchType(DocSearchMatch::MATCH_HEADER); + } + foreach ($section->getContent() as $lineno => $line) { + if (($match = $this->search->search($line)) !== null) { + $matches[] = $match + ->setMatchType(DocSearchMatch::MATCH_CONTENT) + ->setLineno($lineno); + } + } + if (! empty($matches)) { + $this->matches = $matches; + return $this; + } + if ($section->hasChildren()) { + $this->matches = null; + return true; + } + return false; + } + + /** + * Get the search criteria + * + * @return DocSearch + */ + public function getSearch() + { + return $this->search; + } + + /** + * {@inheritdoc} + */ + public function getChildren() + { + return new static($this->getInnerIterator()->getChildren(), $this->search); + } + + /** + * Whether the search did not yield any match + * + * @return bool + */ + public function isEmpty() + { + $iter = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST); + foreach ($iter as $section) { + if ($iter->getInnerIterator()->getMatches() !== null) { + return false; + } + } + return true; + } + + /** + * Get current matches + * + * @return DocSearchMatch[]|null + */ + public function getMatches() + { + return $this->matches; + } +} diff --git a/modules/doc/library/Doc/Search/DocSearchMatch.php b/modules/doc/library/Doc/Search/DocSearchMatch.php new file mode 100644 index 000000000..52bd528c9 --- /dev/null +++ b/modules/doc/library/Doc/Search/DocSearchMatch.php @@ -0,0 +1,215 @@ +line = (string) $line; + return $this; + } + + /** + * Get the line + * + * @return string + */ + public function getLine() + { + return $this->line; + } + + /** + * Set the line number + * + * @param int $lineno + * + * @return $this + */ + public function setLineno($lineno) + { + $this->lineno = (int) $lineno; + return $this; + } + + /** + * Set the match type + * + * @param int $matchType + * + * @return $this + */ + public function setMatchType($matchType) + { + $matchType = (int) $matchType; + if ($matchType !== static::MATCH_HEADER && $matchType !== static::MATCH_CONTENT) { + throw new UnexpectedValueException(); + } + $this->matchType = $matchType; + return $this; + } + + /** + * Get the match type + * + * @return int + */ + public function getMatchType() + { + return $this->matchType; + } + + /** + * Append a match + * + * @param string $match + * @param int $position + * + * @return $this + */ + public function appendMatch($match, $position) + { + $this->matches[(int) $position] = (string) $match; + return $this; + } + + /** + * Get the matches + * + * @return array + */ + public function getMatches() + { + return $this->matches; + } + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = Icinga::app()->getViewRenderer()->view; + } + return $this->view; + } + + /** + * Get the line having matches highlighted + * + * @return string + */ + public function highlight() + { + $highlighted = ''; + $offset = 0; + $matches = $this->getMatches(); + ksort($matches); + foreach ($matches as $position => $match) { + $highlighted .= $this->getView()->escape(substr($this->line, $offset, $position - $offset)) + . '' + . $this->getView()->escape($match) + . ''; + $offset = $position + strlen($match); + } + $highlighted .= $this->getView()->escape(substr($this->line, $offset)); + return $highlighted; + } + + /** + * Whether the match is empty + * + * @return bool + */ + public function isEmpty() + { + return empty($this->matches); + } +} diff --git a/modules/doc/library/Doc/Section.php b/modules/doc/library/Doc/Section.php deleted file mode 100644 index 5cf3d61e2..000000000 --- a/modules/doc/library/Doc/Section.php +++ /dev/null @@ -1,143 +0,0 @@ -id = $id; - $this->title = $title; - $this->level = $level; - $this->noFollow = $noFollow; - $this->chapterId= $chapterId; - } - - /** - * Get the ID of the section - * - * @return string - */ - public function getId() - { - return $this->id; - } - - /** - * Get the title of the section - * - * @return string - */ - public function getTitle() - { - return $this->title; - } - - /** - * Get the header level - * - * @return int - */ - public function getLevel() - { - return $this->level; - } - - /** - * Whether to instruct search engines to not index the link to the section - * - * @return bool - */ - public function isNoFollow() - { - return $this->noFollow; - } - - /** - * The ID of the chapter the section is part of - * - * @return string - */ - public function getChapterId() - { - return $this->chapterId; - } - - /** - * Append content - * - * @param string $content - */ - public function appendContent($content) - { - $this->content[] = $content; - } - - /** - * Get the content of the section - * - * @return array - */ - public function getContent() - { - return $this->content; - } -} diff --git a/modules/doc/library/Doc/SectionFilterIterator.php b/modules/doc/library/Doc/SectionFilterIterator.php deleted file mode 100644 index e20d80359..000000000 --- a/modules/doc/library/Doc/SectionFilterIterator.php +++ /dev/null @@ -1,68 +0,0 @@ -chapterId = $chapterId; - } - - /** - * Accept sections that are part of the given chapter - * - * @return bool Whether the current element of the iterator is acceptable - * through this filter - */ - public function accept() - { - $section = $this->getInnerIterator()->current()->getValue(); - /* @var $section \Icinga\Module\Doc\Section */ - if ($section->getChapterId() === $this->chapterId) { - return true; - } - return false; - } - - /** - * (non-PHPDoc) - * @see RecursiveFilterIterator::getChildren() - */ - public function getChildren() - { - return new static($this->getInnerIterator()->getChildren(), $this->chapterId); - } - - /** - * (non-PHPDoc) - * @see Countable::count() - */ - public function count() - { - return iterator_count($this); - } -} diff --git a/modules/doc/library/Doc/SectionRenderer.php b/modules/doc/library/Doc/SectionRenderer.php deleted file mode 100644 index 2fdaf8e90..000000000 --- a/modules/doc/library/Doc/SectionRenderer.php +++ /dev/null @@ -1,310 +0,0 @@ -docTree = $docTree; - $this->view = $view; - $this->zendUrlHelper = $zendUrlHelper; - $this->url = $url; - $this->urlParams = $urlParams; - } - - public function render($match) - { - $node = $this->docTree->getNode(Renderer::decodeAnchor($match['fragment'])); - /* @var $node \Icinga\Data\Tree\Node */ - if ($node === null) { - return $match[0]; - } - $section = $node->getValue(); - /* @var $section \Icinga\Module\Doc\Section */ - $path = $this->zendUrlHelper->url( - array_merge( - $this->urlParams, - array( - 'chapterId' => SectionRenderer::encodeUrlParam($section->getChapterId()) - ) - ), - $this->url, - false, - false - ); - $url = $this->view->url($path); - $url->setAnchor(SectionRenderer::encodeAnchor($section->getId())); - return sprintf( - 'isNoFollow() ? 'rel="nofollow" ' : '', - $url->getAbsoluteUrl() - ); - } -} - -/** - * Section renderer - */ -class SectionRenderer extends Renderer -{ - /** - * The documentation tree - * - * @var DocTree - */ - protected $docTree; - - protected $tocUrl; - - /** - * The URL to replace links with - * - * @var string - */ - protected $url; - - /** - * Additional URL parameters - * - * @var array - */ - protected $urlParams; - - /** - * Parsedown instance - * - * @var Parsedown - */ - protected $parsedown; - - /** - * Content - * - * @var array - */ - protected $content = array(); - - /** - * Create a new section renderer - * - * @param DocTree $docTree The documentation tree - * @param string|null $chapterId If not null, the chapter ID to filter for - * @param string $tocUrl - * @param string $url The URL to replace links with - * @param array $urlParams Additional URL parameters - * - * @throws ChapterNotFoundException If the chapter to filter for was not found - */ - public function __construct(DocTree $docTree, $chapterId, $tocUrl, $url, array $urlParams) - { - if ($chapterId !== null) { - $filter = new SectionFilterIterator($docTree, $chapterId); - if ($filter->count() === 0) { - throw new ChapterNotFoundException( - sprintf(mt('doc', 'Chapter \'%s\' not found'), $chapterId) - ); - } - parent::__construct( - $filter, - RecursiveIteratorIterator::SELF_FIRST - ); - } else { - parent::__construct($docTree, RecursiveIteratorIterator::SELF_FIRST); - } - $this->docTree = $docTree; - $this->tocUrl = $tocUrl; - $this->url = $url; - $this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams); - $this->parsedown = Parsedown::instance(); - } - - /** - * Syntax highlighting for PHP code - * - * @param $match - * - * @return string - */ - protected function highlightPhp($match) - { - return '
    ' . highlight_string(htmlspecialchars_decode($match[1]), true) . '
    '; - } - - /** - * Replace img src tags - * - * @param $match - * - * @return string - */ - protected function replaceImg($match) - { - $doc = new DOMDocument(); - $doc->loadHTML($match[0]); - $xpath = new DOMXPath($doc); - $img = $xpath->query('//img[1]')->item(0); - /* @var $img \DOMElement */ - $img->setAttribute('src', Url::fromPath($img->getAttribute('src'))->getAbsoluteUrl()); - return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>' - } - - protected function blubb($match) - { - $doc = new DOMDocument(); - $doc->loadHTML($match[0]); - $xpath = new DOMXPath($doc); - $blockquote = $xpath->query('//blockquote[1]')->item(0); - /* @var $blockquote \DOMElement */ - if (strtolower(substr(trim($blockquote->nodeValue), 0, 5)) === 'note:') { - $blockquote->setAttribute('class', 'note'); - } - return $doc->saveXML($blockquote); - } - - /** - * Render the section - * - * @param View $view - * @param Zend_View_Helper_Url $zendUrlHelper - * @param bool $renderNavigation - * - * @return string - */ - public function render(View $view, Zend_View_Helper_Url $zendUrlHelper, $renderNavigation = true) - { - $callback = new Callback($this->docTree, $view, $zendUrlHelper, $this->url, $this->urlParams); - $content = array(); - foreach ($this as $node) { - $section = $node->getValue(); - /* @var $section \Icinga\Module\Doc\Section */ - $content[] = sprintf( - '
    %3$s', - Renderer::encodeAnchor($section->getId()), - $section->getLevel(), - $view->escape($section->getTitle()) - ); - $html = preg_replace_callback( - '#
    (.*?)
    #s', - array($this, 'highlightPhp'), - $this->parsedown->text(implode('', $section->getContent())) - ); - $html = preg_replace_callback( - '/]+>/', - array($this, 'replaceImg'), - $html - ); - $html = preg_replace_callback( - '#
    .+
    #ms', - array($this, 'blubb'), - $html - ); - $content[] = preg_replace_callback( - '/[^>]*?\s+)?href="#(?P[^"]+)"/', - array($callback, 'render'), - $html - ); - } - if ($renderNavigation) { - foreach ($this->docTree as $chapter) { - if ($chapter->getValue()->getId() === $section->getChapterId()) { - $navigation = array(''; - $content = array_merge($navigation, $content, $navigation); - break; - } - } - } - return implode("\n", $content); - } -} diff --git a/modules/doc/library/Doc/TocRenderer.php b/modules/doc/library/Doc/TocRenderer.php deleted file mode 100644 index 4061e80e3..000000000 --- a/modules/doc/library/Doc/TocRenderer.php +++ /dev/null @@ -1,109 +0,0 @@ -url = $url; - $this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams); - } - - public function beginIteration() - { - $this->content[] = ''; - } - - public function beginChildren() - { - $this->content[] = '
      '; - } - - public function endChildren() - { - $this->content[] = '
    '; - } - - /** - * Render the toc - * - * @param View $view - * @param Zend_View_Helper_Url $zendUrlHelper - * - * @return string - */ - public function render(View $view, Zend_View_Helper_Url $zendUrlHelper) - { - foreach ($this as $node) { - $section = $node->getValue(); - /* @var $section \Icinga\Module\Doc\Section */ - $path = $zendUrlHelper->url( - array_merge( - $this->urlParams, - array( - 'chapterId' => $this->encodeUrlParam($section->getChapterId()) - ) - ), - $this->url, - false, - false - ); - $url = $view->url($path); - $url->setAnchor($this->encodeAnchor($section->getId())); - $this->content[] = sprintf( - '
  • %s', - $section->isNoFollow() ? 'rel="nofollow" ' : '', - $url->getAbsoluteUrl(), - $view->escape($section->getTitle()) - ); - if (! $this->getInnerIterator()->current()->hasChildren()) { - $this->content[] = '
  • '; - } - } - return implode("\n", $this->content); - } -} diff --git a/modules/doc/module.info b/modules/doc/module.info index 2826d72de..32516de5b 100644 --- a/modules/doc/module.info +++ b/modules/doc/module.info @@ -1,4 +1,4 @@ Module: doc -Version: 2.0.0~alpha4 +Version: 2.0.0-rc1 Description: Documentation module - Extracts, shows and exports documentation for Icinga Web 2 and it's modules. + Extracts, shows and exports documentation for Icinga Web 2 and its modules. diff --git a/modules/doc/public/css/module.less b/modules/doc/public/css/module.less index a029517de..2bb5a44ff 100644 --- a/modules/doc/public/css/module.less +++ b/modules/doc/public/css/module.less @@ -1,6 +1,4 @@ -div.chapter { - padding-left: 5px; -} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ .uppercase { text-transform: uppercase; @@ -20,18 +18,6 @@ div.chapter { background: linear-gradient(to bottom, @a 0%, @b 100%); } -.box-shadow(@x: 3px; @y: 3px; @blur: 2px; @spread: 0px; @color: rgba(45, 45, 45, 0.75)) { - -webkit-box-shadow: @arguments; - -moz-box-shadow: @arguments; - box-shadow: @arguments; -} - -.round-corners { - -moz-border-radius: 0.2em; - -webkit-border-radius: 0.2em; - border-radius: 0.2em; -} - table { // Reset border-collapse: collapse; @@ -75,7 +61,6 @@ thead { .uppercase; .bold; } - position: sticky; border-bottom: 0.25rem solid @icinga; } @@ -103,31 +88,12 @@ pre > code { .box-shadow; } -div.chapter > ul.navigation { +ul.toc { margin: 0; - padding: 0.4em; - text-align: center; - background-color: #888; - - li { - list-style: none; - display: inline; - margin: 0.2em; - padding: 0; - - a { - color: #fff; - text-decoration: none; - } - - &.prev { - padding-right: 0.6em; - border-right: 2px solid #fff; - } - - &.next { - padding-left: 0.6em; - border-left: 2px solid #fff; - } - } + padding: 0 0 0 1em; +} + +.search-highlight { + color: #FBE012; + background: @icinga; } diff --git a/modules/doc/public/img/doc/markdown.png b/modules/doc/public/img/doc/markdown.png new file mode 100644 index 000000000..93e729bc7 Binary files /dev/null and b/modules/doc/public/img/doc/markdown.png differ diff --git a/modules/doc/run.php b/modules/doc/run.php index 7392e4c22..d0712a858 100644 --- a/modules/doc/run.php +++ b/modules/doc/run.php @@ -1,6 +1,6 @@ isCli()) { @@ -8,7 +8,7 @@ if (Icinga::app()->isCli()) { } $docModuleChapter = new Zend_Controller_Router_Route( - 'doc/module/:moduleName/chapter/:chapterId', + 'doc/module/:moduleName/chapter/:chapter', array( 'controller' => 'module', 'action' => 'chapter', @@ -17,7 +17,7 @@ $docModuleChapter = new Zend_Controller_Router_Route( ); $docIcingaWebChapter = new Zend_Controller_Router_Route( - 'doc/icingaweb/chapter/:chapterId', + 'doc/icingaweb/chapter/:chapter', array( 'controller' => 'icingaweb', 'action' => 'chapter', @@ -47,4 +47,3 @@ $this->addRoute('doc/module/chapter', $docModuleChapter); $this->addRoute('doc/icingaweb/chapter', $docIcingaWebChapter); $this->addRoute('doc/module/toc', $docModuleToc); $this->addRoute('doc/module/pdf', $docModulePdf); - diff --git a/modules/monitoring/application/clicommands/ConferenceCommand.php b/modules/monitoring/application/clicommands/ConferenceCommand.php index 7c659e8fb..d0062b1a9 100644 --- a/modules/monitoring/application/clicommands/ConferenceCommand.php +++ b/modules/monitoring/application/clicommands/ConferenceCommand.php @@ -1,6 +1,5 @@ app->setupZendAutoloader(); $this->backend = Backend::createBackend($this->params->shift('backend')); $this->dumpSql = $this->params->shift('showsql'); } @@ -124,19 +124,19 @@ class ListCommand extends Command * --verbose Show detailled output * --showsql Dump generated SQL query (DB backend only) * - * --format > + * --format=> * Dump columns in the given format. format allows $column$ - * placeholders, e.g. --format '$host$: $service$' + * placeholders, e.g. --format='$host$: $service$' * - * -- [filter] + * --[=filter] * Filter given column by optional filter. Boolean (1/0) columns are * true if no filter value is given. * * EXAMPLES * * icingacli monitoring list --unhandled - * icingacli monitoring list --host local* --service *disk* - * icingacli monitoring list --format '$host$: $service$' + * icingacli monitoring list --host=local* --service=*disk* + * icingacli monitoring list --format='$host$: $service$' */ public function statusAction() { @@ -156,7 +156,7 @@ class ListCommand extends Command 'service_perfdata', 'service_last_state_change' ); - $query = $this->getQuery('serviceStatus', $columns) + $query = $this->getQuery('servicestatus', $columns) ->order('host_name'); echo $this->renderStatusQuery($query); } @@ -299,7 +299,7 @@ class ListCommand extends Command $leaf, $screen->underline($row->service_description), $screen->colorize($utils->objectStateFlags('service', $row) . $perf, 'lightblue'), - Format::prefixedTimeSince($row->service_last_state_change, true) + ucfirst(DateFormatter::timeSince($row->service_last_state_change)) ); if ($this->isVerbose) { $out .= $emptyLine . preg_replace( diff --git a/modules/monitoring/application/clicommands/NrpeCommand.php b/modules/monitoring/application/clicommands/NrpeCommand.php index 69ee50d9b..abb877553 100644 --- a/modules/monitoring/application/clicommands/NrpeCommand.php +++ b/modules/monitoring/application/clicommands/NrpeCommand.php @@ -1,6 +1,5 @@ getTabs(); - if (in_array($this->_request->getActionName(), array('alertsummary'))) { - $tabs->extend(new OutputFormat())->extend(new DashboardAction()); - } - - $this->url = Url::fromRequest(); - $this->notificationData = $this->createNotificationData(); $this->problemData = $this->createProblemData(); } /** - * @param string $action - * @param bool $title - */ - protected function addTitleTab($action, $title = false) - { - $title = $title ?: ucfirst($action); - $this->getTabs()->add( - $action, - array( - 'title' => $title, - 'url' => $this->url - ) - )->activate($action); - $this->view->title = $title; - } - - /** - * Creat full report + * Create full report */ public function indexAction() { - $this->addTitleTab('alertsummary', t('Alert Summary')); + $this->getTabs()->add( + 'alertsummary', + array( + 'title' => $this->translate( + 'Show recent alerts and visualize notifications and problems' + . ' based on their amount and chronological distribution' + ), + 'label' => $this->translate('Alert Summary'), + 'url' => Url::fromRequest() + ) + )->extend(new DashboardAction())->activate('alertsummary'); + $this->view->title = $this->translate('Alert Summary'); + $this->view->intervalBox = $this->createIntervalBox(); $this->view->recentAlerts = $this->createRecentAlerts(); $this->view->interval = $this->getInterval(); @@ -77,16 +60,21 @@ class Monitoring_AlertsummaryController extends Controller $query = $this->backend->select()->from( 'notification', array( - 'host', - 'service', + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name', 'notification_output', - 'notification_contact', + 'notification_contact_name', 'notification_start_time', 'notification_state' ) ); + $this->applyRestriction('monitoring/filter/objects', $query); + $this->view->notifications = $query; - $this->view->notifications = $query->paginate(); + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->notifications); } /** @@ -101,18 +89,14 @@ class Monitoring_AlertsummaryController extends Controller $query = $this->backend->select()->from( 'notification', array( - 'host', - 'service', - 'notification_output', - 'notification_contact', - 'notification_start_time', - 'notification_state' + 'notification_start_time' ) ); + $this->applyRestriction('monitoring/filter/objects', $query); - $query->setFilter( + $query->addFilter( new Icinga\Data\Filter\FilterExpression( - 'n.start_time', + 'notification_start_time', '>=', $this->getBeginDate($interval)->format('Y-m-d H:i:s') ) @@ -154,18 +138,14 @@ class Monitoring_AlertsummaryController extends Controller $query = $this->backend->select()->from( 'notification', array( - 'host', - 'service', - 'notification_output', - 'notification_contact', - 'notification_start_time', - 'notification_state' + 'notification_start_time' ) ); + $this->applyRestriction('monitoring/filter/objects', $query); - $query->setFilter( + $query->addFilter( new Icinga\Data\Filter\FilterExpression( - 'n.start_time', + 'notification_start_time', '>=', $beginDate->format('Y-m-d H:i:s') ) @@ -193,11 +173,11 @@ class Monitoring_AlertsummaryController extends Controller $out = new stdClass(); if ($yesterday === $today) { - $out->trend = 'unchanged'; + $out->trend = $this->translate('unchanged'); } elseif ($yesterday > $today) { - $out->trend = 'down'; + $out->trend = $this->translate('down'); } else { - $out->trend = 'up'; + $out->trend = $this->translate('up'); } if ($yesterday <= 0) { @@ -226,18 +206,14 @@ class Monitoring_AlertsummaryController extends Controller $query = $this->backend->select()->from( 'notification', array( - 'host', - 'service', - 'notification_output', - 'notification_contact', - 'notification_start_time', - 'notification_state' + 'notification_start_time' ) ); + $this->applyRestriction('monitoring/filter/objects', $query); - $query->setFilter( + $query->addFilter( new Icinga\Data\Filter\FilterExpression( - 'n.start_time', + 'notification_start_time', '>=', $this->getBeginDate($interval)->format('Y-m-d H:i:s') ) @@ -279,22 +255,12 @@ class Monitoring_AlertsummaryController extends Controller $interval = $this->getInterval(); $query = $this->backend->select()->from( - 'EventHistory', + 'eventhistory', array( - 'host_name', - 'service_description', - 'object_type', - 'timestamp', - 'state', - 'attempt', - 'max_attempts', - 'output', - 'type', - 'host', - 'service', - 'service_host_name' + 'timestamp' ) ); + $this->applyRestriction('monitoring/filter/objects', $query); $query->addFilter( new Icinga\Data\Filter\FilterExpression( @@ -340,32 +306,31 @@ class Monitoring_AlertsummaryController extends Controller public function createHealingChart() { $gridChart = new GridChart(); + $gridChart->title = $this->translate('Healing Chart'); + $gridChart->description = $this->translate('Notifications and average reaction time per hour.'); $gridChart->alignTopLeft(); - $gridChart->setAxisLabel('', mt('monitoring', 'Notifications')) + $gridChart->setAxisLabel($this->createPeriodDescription(), $this->translate('Notifications')) ->setXAxis(new StaticAxis()) - ->setAxisMin(null, 0) - ->setYAxis(new LinearUnit(10)); + ->setYAxis(new LinearUnit(10)) + ->setAxisMin(null, 0); $interval = $this->getInterval(); $query = $this->backend->select()->from( 'notification', array( - 'host', - 'service', 'notification_object_id', - 'notification_output', - 'notification_contact', 'notification_start_time', 'notification_state', 'acknowledgement_entry_time' ) ); + $this->applyRestriction('monitoring/filter/objects', $query); - $query->setFilter( + $query->addFilter( new Icinga\Data\Filter\FilterExpression( - 'n.start_time', + 'notification_start_time', '>=', $this->getBeginDate($interval)->format('Y-m-d H:i:s') ) @@ -373,7 +338,7 @@ class Monitoring_AlertsummaryController extends Controller $query->order('notification_start_time', 'asc'); - $records = $query->getQuery()->fetchAll(); + $records = $query->getQuery()->fetchAll(); $interval = $this->getInterval(); $period = $this->createPeriod($interval); @@ -399,6 +364,15 @@ class Monitoring_AlertsummaryController extends Controller $recover = 0; if ($item->acknowledgement_entry_time) { $recover = $item->acknowledgement_entry_time - $item->notification_start_time; + + /* + * Acknowledgements may happen before the actual notification starts, since notifications + * can be configured to start a certain time after the problem. In that case we assume + * a reaction time of 0s. + */ + if ($recover < 0) { + $recover = 0; + } } $rData[$item->notification_object_id] = array( 'id' => $id, @@ -429,13 +403,13 @@ class Monitoring_AlertsummaryController extends Controller $item[1] = $item[1]/60/60; } - $gridChart->drawBars( array( 'label' => $this->translate('Notifications'), - 'color' => '#049baf', + 'color' => '#07C0D9', 'data' => $notifications, - 'showPoints' => true + 'showPoints' => true, + 'tooltip' => '{title}: {value} {label}' ) ); @@ -444,7 +418,8 @@ class Monitoring_AlertsummaryController extends Controller 'label' => $this->translate('Avg (min)'), 'color' => '#ffaa44', 'data' => $dAvg, - 'showPoints' => true + 'showPoints' => true, + 'tooltip' => $this->translate('{title}: {value}m min. reaction time') ) ); @@ -453,7 +428,8 @@ class Monitoring_AlertsummaryController extends Controller 'label' => $this->translate('Max (min)'), 'color' => '#ff5566', 'data' => $dMax, - 'showPoints' => true + 'showPoints' => true, + 'tooltip' => $this->translate('{title}: {value}m max. reaction time') ) ); @@ -468,19 +444,22 @@ class Monitoring_AlertsummaryController extends Controller public function createDefectImage() { $gridChart = new GridChart(); + $gridChart->title = $this->translate('Defect Chart'); + $gridChart->description = $this->translate('Notifications and defects per hour'); $gridChart->alignTopLeft(); - $gridChart->setAxisLabel('', mt('monitoring', 'Notifications')) + $gridChart->setAxisLabel($this->createPeriodDescription(), $this->translate('Notifications')) ->setXAxis(new StaticAxis()) - ->setAxisMin(null, 0) - ->setYAxis(new LinearUnit(10)); + ->setYAxis(new LinearUnit(10)) + ->setAxisMin(null, 0); $gridChart->drawBars( array( 'label' => $this->translate('Notifications'), - 'color' => '#049baf', + 'color' => '#07C0D9', 'data' => $this->notificationData, - 'showPoints' => true + 'showPoints' => true, + 'tooltip' => '{title}: {value} {label}' ) ); @@ -489,7 +468,8 @@ class Monitoring_AlertsummaryController extends Controller 'label' => $this->translate('Defects'), 'color' => '#ff5566', 'data' => $this->problemData, - 'showPoints' => true + 'showPoints' => true, + 'tooltip' => '{title}: {value} {label}' ) ); @@ -506,18 +486,21 @@ class Monitoring_AlertsummaryController extends Controller $query = $this->backend->select()->from( 'notification', array( - 'host', - 'service', + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name', 'notification_output', - 'notification_contact', + 'notification_contact_name', 'notification_start_time', 'notification_state' ) ); + $this->applyRestriction('monitoring/filter/objects', $query); $query->order('notification_start_time', 'desc'); - return $query->paginate(5); + return $query->limit(5); } /** @@ -530,12 +513,12 @@ class Monitoring_AlertsummaryController extends Controller $box = new SelectBox( 'intervalBox', array( - '1d' => mt('monitoring', 'One day'), - '1w' => mt('monitoring', 'One week'), - '1m' => mt('monitoring', 'One month'), - '1y' => mt('monitoring', 'One year') + '1d' => $this->translate('One day'), + '1w' => $this->translate('One week'), + '1m' => $this->translate('One month'), + '1y' => $this->translate('One year') ), - mt('monitoring', 'Report interval'), + $this->translate('Report interval'), 'interval' ); $box->applyRequest($this->getRequest()); @@ -554,7 +537,7 @@ class Monitoring_AlertsummaryController extends Controller { $format = ''; if ($interval === '1d') { - $format = '%H:00:00'; + $format = '%H:00'; } elseif ($interval === '1w') { $format = '%Y-%m-%d'; } elseif ($interval === '1m') { @@ -579,7 +562,7 @@ class Monitoring_AlertsummaryController extends Controller } elseif ($interval === '1w') { return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1D'), 7); } elseif ($interval === '1m') { - return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1D'), 30); + return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1D'), 31); } elseif ($interval === '1y') { return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1M'), 12); } @@ -623,4 +606,28 @@ class Monitoring_AlertsummaryController extends Controller return $interval; } + + /** + * Create a human-readable description of the current interval size + * + * @return string The description of the current interval size + */ + private function createPeriodDescription() + { + $int = $this->getInterval(); + switch ($int) { + case '1d': + return $this->translate('Hour'); + break; + case '1w'; + return $this->translate('Day'); + break; + case '1m': + return $this->translate('Day'); + break; + case '1y': + return $this->translate('Month'); + break; + } + } } diff --git a/modules/monitoring/application/controllers/ChartController.php b/modules/monitoring/application/controllers/ChartController.php index 2024f83f4..d2e9f990c 100644 --- a/modules/monitoring/application/controllers/ChartController.php +++ b/modules/monitoring/application/controllers/ChartController.php @@ -1,11 +1,12 @@ view->compact = $this->_request->getParam('view') === 'compact'; + $chart = new GridChart(); + $chart->alignTopLeft(); + $chart->setAxisLabel('X axis label', 'Y axis label') + ->setYAxis(new LogarithmicUnit()); + + for ($i = -15; $i < 15; $i++) { + $data1[] = array($i, -$i * rand(1, 10) * pow(2, rand(1, 2))); + } + for ($i = -15; $i < 15; $i++) { + $data2[] = array($i, 1000 + $i * rand(1, 35) * pow(2, rand(1, 2))); + } + for ($i = -15; $i < 15; $i++) { + $data3[] = array($i, $i * rand(1, 100) * pow(2, rand(1, 10)) - 1000); + } + + $chart->drawLines( + array( + 'label' => 'Random 1', + 'color' => '#F56', + 'data' => $data1, + 'showPoints' => true + ) + ); + $chart->drawLines( + array( + 'label' => 'Random 2', + 'color' => '#fa4', + 'data' => $data2, + 'showPoints' => true + ) + ); + $chart->drawLines( + array( + 'label' => 'Random 3', + 'color' => '#4b7', + 'data' => $data3, + 'showPoints' => true + ) + ); + return $chart; + } + + private function drawLogChart2() + { + $chart = new GridChart(); + $chart->alignTopLeft(); + $chart->setAxisLabel('X axis label', 'Y axis label') + ->setYAxis(new LogarithmicUnit()); + + for ($i = -10; $i < 10; $i++) { + $sign = $i > 0 ? 1 : + ($i < 0 ? -1 : 0); + $data[] = array($i, $sign * pow(10, abs($i))); + } + $chart->drawLines( + array( + 'label' => 'f(x): sign(x) * 10^|x|', + 'color' => '#F56', + 'data' => $data, + 'showPoints' => true + ) + ); + return $chart; + } + private function drawLogChart3() + { + $chart = new GridChart(); + $chart->alignTopLeft(); + $chart->setAxisLabel('X axis label', 'Y axis label') + ->setYAxis(new LogarithmicUnit()); + + for ($i = -2; $i < 3; $i++) { + $sign = $i > 0 ? 1 : + ($i < 0 ? -1 : 0); + $data[] = array($i, $sign * pow(10, abs($i))); + } + $chart->drawLines( + array( + 'label' => 'f(x): sign(x) * 10^|x|', + 'color' => '#F56', + 'data' => $data, + 'showPoints' => true + ) + ); + return $chart; } public function testAction() @@ -28,7 +113,7 @@ class Monitoring_ChartController extends Controller $data1 = array(); $data2 = array(); $data3 = array(); - for ($i = 0; $i < 25; $i++) { + for ($i = 0; $i < 50; $i++) { $data3[] = array('Label ' . $i, rand(0, 30)); } @@ -36,13 +121,13 @@ class Monitoring_ChartController extends Controller $this->chart->drawLines( array( 'label' => 'Nr of outtakes', - 'color' => 'red', + 'color' => '#F56', 'width' => '5', 'data' => $data ), array( 'label' => 'Some line', - 'color' => 'blue', + 'color' => '#fa4', 'width' => '4', 'data' => $data3, @@ -52,8 +137,8 @@ class Monitoring_ChartController extends Controller */ $this->chart->drawBars( array( - 'label' => 'Some other line', - 'color' => 'green', + 'label' => 'A big amount of data', + 'color' => '#4b7', 'data' => $data3, 'showPoints' => true ) @@ -68,13 +153,17 @@ class Monitoring_ChartController extends Controller ) ); */ - $this->view->svg = $this->chart; + $this->view->svgs = array(); + $this->view->svgs[] = $this->drawLogChart1(); + $this->view->svgs[] = $this->drawLogChart2(); + $this->view->svgs[] = $this->drawLogChart3(); + $this->view->svgs[] = $this->chart; } public function hostgroupAction() { $query = $this->backend->select()->from( - 'groupsummary', + 'hostgroupsummary', array( 'hostgroup', 'hosts_up', @@ -105,7 +194,7 @@ class Monitoring_ChartController extends Controller public function servicegroupAction() { $query = $this->backend->select()->from( - 'groupsummary', + 'servicegroupsummary', array( 'servicegroup', 'services_ok', @@ -142,36 +231,39 @@ class Monitoring_ChartController extends Controller $unknownBars[] = array($servicegroup->servicegroup, $servicegroup->services_unknown_unhandled); } $this->view->chart = new GridChart(); + $this->view->chart->title = $this->translate('Service Group Chart'); + $this->view->chart->description = $this->translate('Contains service states for each service group.'); + $this->view->chart->alignTopLeft(); - $this->view->chart->setAxisLabel('', mt('monitoring', 'Services')) + $this->view->chart->setAxisLabel('', $this->translate('Services')) ->setXAxis(new StaticAxis()) ->setAxisMin(null, 0); - $tooltip = mt('monitoring', '{title}:
    {value} of {sum} services are {label}'); + $tooltip = $this->translate('{title}:
    {value} of {sum} services are {label}'); $this->view->chart->drawBars( array( - 'label' => mt('monitoring', 'Ok'), + 'label' => $this->translate('Ok'), 'color' => '#44bb77', 'stack' => 'stack1', 'data' => $okBars, 'tooltip' => $tooltip ), array( - 'label' => mt('monitoring', 'Warning'), + 'label' => $this->translate('Warning'), 'color' => '#ffaa44', 'stack' => 'stack1', 'data' => $warningBars, 'tooltip' => $tooltip ), array( - 'label' => mt('monitoring', 'Critical'), + 'label' => $this->translate('Critical'), 'color' => '#ff5566', 'stack' => 'stack1', 'data' => $critBars, 'tooltip' => $tooltip ), array( - 'label' => mt('monitoring', 'Unknown'), + 'label' => $this->translate('Unknown'), 'color' => '#dd66ff', 'stack' => 'stack1', 'data' => $unknownBars, @@ -185,43 +277,43 @@ class Monitoring_ChartController extends Controller $upBars = array(); $downBars = array(); $unreachableBars = array(); - foreach ($query as $hostgroup) { + for ($i = 0; $i < 50; $i++) { $upBars[] = array( - $hostgroup->hostgroup, - $hostgroup->hosts_up + (string)$i, rand(1, 200), rand(1, 200) ); $downBars[] = array( - $hostgroup->hostgroup, - $hostgroup->hosts_down_unhandled + (string)$i, rand(1, 200), rand(1, 200) ); $unreachableBars[] = array( - $hostgroup->hostgroup, - $hostgroup->hosts_unreachable_unhandled + (string)$i, rand(1, 200), rand(1, 200) ); } - $tooltip = mt('monitoring', '{title}:
    {value} of {sum} hosts are {label}'); + $tooltip = $this->translate('{title}:
    {value} of {sum} hosts are {label}'); $this->view->chart = new GridChart(); + $this->view->chart->title = $this->translate('Host Group Chart'); + $this->view->chart->description = $this->translate('Contains host states of each service group.'); + $this->view->chart->alignTopLeft(); - $this->view->chart->setAxisLabel('', mt('monitoring', 'Hosts')) + $this->view->chart->setAxisLabel('', $this->translate('Hosts')) ->setXAxis(new StaticAxis()) ->setAxisMin(null, 0); $this->view->chart->drawBars( array( - 'label' => mt('monitoring', 'Up'), + 'label' => $this->translate('Up'), 'color' => '#44bb77', 'stack' => 'stack1', 'data' => $upBars, 'tooltip' => $tooltip ), array( - 'label' => mt('monitoring', 'Down'), + 'label' => $this->translate('Down'), 'color' => '#ff5566', 'stack' => 'stack1', 'data' => $downBars, 'tooltip' => $tooltip ), array( - 'label' => mt('monitoring', 'Unreachable'), + 'label' => $this->translate('Unreachable'), 'color' => '#dd66ff', 'stack' => 'stack1', 'data' => $unreachableBars, @@ -248,13 +340,13 @@ class Monitoring_ChartController extends Controller 'colors' => array('#44bb77', '#ff4444', '#ff0000', '#ffff00', '#ffff33', '#E066FF', '#f099FF', '#fefefe'), 'labels'=> array( $query->services_ok . ' Up Services', - $query->services_warning_handled . mt('monitoring', ' Warning Services (Handled)'), - $query->services_warning_unhandled . mt('monitoring', ' Warning Services (Unhandled)'), - $query->services_critical_handled . mt('monitoring', ' Down Services (Handled)'), - $query->services_critical_unhandled . mt('monitoring', ' Down Services (Unhandled)'), - $query->services_unknown_handled . mt('monitoring', ' Unreachable Services (Handled)'), - $query->services_unknown_unhandled . mt('monitoring', ' Unreachable Services (Unhandled)'), - $query->services_pending . mt('monitoring', ' Pending Services') + $query->services_warning_handled . $this->translate(' Warning Services (Handled)'), + $query->services_warning_unhandled . $this->translate(' Warning Services (Unhandled)'), + $query->services_critical_handled . $this->translate(' Down Services (Handled)'), + $query->services_critical_unhandled . $this->translate(' Down Services (Unhandled)'), + $query->services_unknown_handled . $this->translate(' Unreachable Services (Handled)'), + $query->services_unknown_unhandled . $this->translate(' Unreachable Services (Unhandled)'), + $query->services_pending . $this->translate(' Pending Services') ) )); } @@ -272,14 +364,21 @@ class Monitoring_ChartController extends Controller (int) $query->hosts_unreachable_unhandled, (int) $query->hosts_pending ), - 'colors' => array('#44bb77', '#ff4444', '#ff0000', '#E066FF', '#f099FF', '#fefefe'), + 'colors' => array( + '#44bb77', // 'Ok' + '#ff4444', // 'Warning' + '#ff0000', // 'WarningHandled' + '#E066FF', + '#f099FF', + '#fefefe' + ), 'labels'=> array( - (int) $query->hosts_up . mt('monitoring', ' Up Hosts'), - (int) $query->hosts_down_handled . mt('monitoring', ' Down Hosts (Handled)'), - (int) $query->hosts_down_unhandled . mt('monitoring', ' Down Hosts (Unhandled)'), - (int) $query->hosts_unreachable_handled . mt('monitoring', ' Unreachable Hosts (Handled)'), - (int) $query->hosts_unreachable_unhandled . mt('monitoring', ' Unreachable Hosts (Unhandled)'), - (int) $query->hosts_pending . mt('monitoring', ' Pending Hosts') + (int) $query->hosts_up . $this->translate(' Up Hosts'), + (int) $query->hosts_down_handled . $this->translate(' Down Hosts (Handled)'), + (int) $query->hosts_down_unhandled . $this->translate(' Down Hosts (Unhandled)'), + (int) $query->hosts_unreachable_handled . $this->translate(' Unreachable Hosts (Handled)'), + (int) $query->hosts_unreachable_unhandled . $this->translate(' Unreachable Hosts (Unhandled)'), + (int) $query->hosts_pending . $this->translate(' Pending Hosts') ) ), array( 'data' => array( @@ -294,14 +393,14 @@ class Monitoring_ChartController extends Controller ), 'colors' => array('#44bb77', '#ff4444', '#ff0000', '#ffff00', '#ffff33', '#E066FF', '#f099FF', '#fefefe'), 'labels'=> array( - $query->services_ok . mt('monitoring', ' Up Services'), - $query->services_warning_handled . mt('monitoring', ' Warning Services (Handled)'), - $query->services_warning_unhandled . mt('monitoring', ' Warning Services (Unhandled)'), - $query->services_critical_handled . mt('monitoring', ' Down Services (Handled)'), - $query->services_critical_unhandled . mt('monitoring', ' Down Services (Unhandled)'), - $query->services_unknown_handled . mt('monitoring', ' Unreachable Services (Handled)'), - $query->services_unknown_unhandled . mt('monitoring', ' Unreachable Services (Unhandled)'), - $query->services_pending . mt('monitoring', ' Pending Services') + $query->services_ok . $this->translate(' Up Services'), + $query->services_warning_handled . $this->translate(' Warning Services (Handled)'), + $query->services_warning_unhandled . $this->translate(' Warning Services (Unhandled)'), + $query->services_critical_handled . $this->translate(' Down Services (Handled)'), + $query->services_critical_unhandled . $this->translate(' Down Services (Unhandled)'), + $query->services_unknown_handled . $this->translate(' Unreachable Services (Handled)'), + $query->services_unknown_unhandled . $this->translate(' Unreachable Services (Unhandled)'), + $query->services_pending . $this->translate(' Pending Services') ) )); } diff --git a/modules/monitoring/application/controllers/CommandController.php b/modules/monitoring/application/controllers/CommandController.php deleted file mode 100644 index d2615104a..000000000 --- a/modules/monitoring/application/controllers/CommandController.php +++ /dev/null @@ -1,1098 +0,0 @@ -form = $form; - } - - /** - * Test if we have a valid form object - * - * @return bool - */ - public function issetForm() - { - return $this->form !== null && ($this->form instanceof Form); - } - - protected function addTitleTab($action) - { - $this->getTabs()->add($action, array( - 'title' => ucfirst($action), - 'url' => Url::fromRequest() - ))->activate($action); - } - - /** - * Post dispatch method - * - * When we have a form put it into the view - */ - public function postDispatch() - { - - if ($this->issetForm()) { - if ($this->form->isSubmittedAndValid()) { - $this->_helper->viewRenderer->setNoRender(true); - $this->_helper->layout()->disableLayout(); - $this->ignoreXhrBody(); - if ($this->_request->getHeader('referer') && ! $this->getRequest()->isXmlHttpRequest()) { - $this->redirect($this->_request->getHeader('referer')); - } - } else { - $this->view->form = $this->form; - } - } - parent::postDispatch(); - } - - /** - * Controller configuration - * - * @throws Icinga\Exception\ConfigurationError - */ - public function init() - { - if ($this->_request->isPost()) { - $instance = $this->_request->getPost('instance'); - $targetConfig = Config::module('monitoring', 'instances'); - if ($instance) { - if ($targetConfig->get($instance)) { - $this->target = new CommandPipe($targetConfig->get($instance)); - } else { - throw new ConfigurationError( - $this->translate('Instance is not configured: %s'), - $instance - ); - } - } else { - if ($targetConfig && $targetInfo = $targetConfig->current()) { - // Take the very first section - $this->target = new CommandPipe($targetInfo); - } else { - throw new ConfigurationError($this->translate('No instances are configured yet')); - } - } - } - - if ($this->getRequest()->getActionName() !== 'list') { - $this->_helper->viewRenderer->setRender(self::DEFAULT_VIEW_SCRIPT); - } - - $this->view->objects = array(); - } - - /** - * Retrieve all existing targets for host- and service combination - * - * @param $hostOnly Ignore the service parameters - * (for example when using commands that only make sense for hosts) - * @return array Array of monitoring objects - * @throws Icinga\Exception\MissingParameterException - */ - private function selectCommandTargets($hostOnly = false) - { - $query = null; - - $fields = array( - 'host_name', - 'host_state' - ); - - try { - $hostname = $this->getParam('host', null); - $servicename = $this->getParam('service', null); - - if (!$hostname && !$servicename) { - throw new MissingParameterException('No target given for this command'); - } - - if ($servicename && !$hostOnly) { - $fields[] = 'service_description'; - $query = $this->backend->select() - ->from('serviceStatus', $fields) - ->where('host', $hostname) - ->where('service', $servicename); - } elseif ($hostname) { - $query = $this->backend->select()->from('hostStatus', $fields)->where('host', $hostname); - } else { - throw new MissingParameterException('hostOnly command got no hostname'); - } - return $query->getQuery()->fetchAll(); - - } catch (\Exception $e) { - Logger::error( - "CommandController: SQL Query '%s' failed (message %s) ", - $query ? (string) $query->dump() : '--', $e->getMessage() - ); - return array(); - } - } - - /** - * Convert other params into valid command structure - * - * @param array $supported Array of supported parameter names - * @param array $params Parameters from request - * - * @return array Return - */ - private function selectOtherTargets(array $supported, array $params) - { - $others = array_diff_key($supported, array('host' => true, 'service' => true)); - $otherParams = array_intersect_key($params, $others); - $out = array(); - - foreach ($otherParams as $name => $value) { - $data = new stdClass(); - $data->{$name} = $value; - $out[] = $data; - } - - return $out; - } - - /** - * Displays a list of all commands - * - * This method uses reflection on the sourcecode to determine all *Action classes and return - * a list of them (ignoring the listAction) - */ - public function listAction() - { - $reflection = new ReflectionObject($this); - $commands = array(); - $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); - foreach ($methods as $method) { - $name = $method->getName(); - if ($name !== 'listAction' && preg_match('/Action$/', $name)) { - $commands[] = preg_replace('/Action$/', '', $name); - } - } - $this->view->commands = $commands; - } - - /** - * Tell the controller that at least one of the parameters in $supported is required to be availabe - * - * @param array $supported An array of properties to check for existence in the POST or GET parameter list - * @throws Exception When non of the supported parameters is given - */ - private function setSupportedParameters(array $supported) - { - $objects = array(); - - $supported = array_flip($supported); - - $given = array_intersect_key($supported, $this->getRequest()->getParams()); - - if (empty($given)) { - throw new IcingaException( - 'Missing parameter, supported: %s', - implode(', ', array_flip($supported)) - ); - } - - if (isset($given['host'])) { - $objects = $this->selectCommandTargets(!in_array("service", $supported)); - if (empty($objects)) { - throw new IcingaException('No objects found for your command'); - } - } - - $this->view->objects = $objects; - } - - // ------------------------------------------------------------------------ - // Commands for hosts / services - // ------------------------------------------------------------------------ - - /** - * Handle command disableactivechecks - */ - public function disableactivechecksAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - - $form = new SingleArgumentCommandForm(); - - $form->setCommand( - 'DISABLE_HOST_CHECK', - 'DISABLE_SVC_CHECK' - ); - - $form->setGlobalCommands( - 'STOP_EXECUTING_HOST_CHECKS', - 'STOP_EXECUTING_SVC_CHECKS' - ); - - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Disable Active Checks')); - - if ($form->provideGlobalCommand()) { - $form->addNote($this->translate('Disable active checks on a program-wide basis.')); - } else { - $form->addNote($this->translate('Disable active checks for this object.')); - } - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, active checks will be disabled')); - } - } - - /** - * Handle command enableactivechecks - */ - public function enableactivechecksAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setCommand('ENABLE_HOST_CHECK', 'ENABLE_SVC_CHECK'); - $form->setGlobalCommands('START_EXECUTING_HOST_CHECKS', 'START_EXECUTING_SVC_CHECKS'); - - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Enable Active Checks')); - if ($form->provideGlobalCommand()) { - $form->addNote($this->translate('Enable active checks on a program-wide basis.')); - } else { - $form->addNote($this->translate('Enable active checks for this object.')); - } - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, active checks will be enabled')); - } - } - - /** - * Handle command reschedulenextcheck - */ - public function reschedulenextcheckAction() - { - $this->addTitleTab('Reschedule Next Check'); - $this->setSupportedParameters(array('host', 'service')); - $form = new RescheduleNextCheckForm(); - $form->setRequest($this->getRequest()); - $form->setConfiguration(Config::app()); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, check will be rescheduled')); - } - } - - /** - * Handle command submitpassivecheckresult - */ - public function submitpassivecheckresultAction() - { - $this->setSupportedParameters(array('host', 'service')); - $type = SubmitPassiveCheckResultForm::TYPE_SERVICE; - if ($this->getParam('service', null) === null) { - $type = SubmitPassiveCheckResultForm::TYPE_HOST; - } - - $form = new SubmitPassiveCheckResultForm(); - $form->setRequest($this->getRequest()); - $form->setType($type); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Passive check result has been submitted')); - } - } - - /** - * Handle command stopobsessing - */ - public function stopobsessingAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Stop obsessing')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Disable obsessing on a program-wide basis.')); - } else { - $form->addNote($this->translate('Stop obsessing over this object.')); - } - - $form->setCommand( - 'STOP_OBSESSING_OVER_HOST', - 'STOP_OBSESSING_OVER_SVC' - ); - - $form->setGlobalCommands( - 'STOP_OBSESSING_OVER_HOST_CHECKS', - 'STOP_OBSESSING_OVER_SVC_CHECKS' - ); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, obsessing will be disabled')); - } - } - - /** - * Handle command startobsessing - */ - public function startobsessingAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Start obsessing')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Enable obsessing on a program-wide basis.')); - } else { - $form->addNote($this->translate('Start obsessing over this object.')); - } - - $form->setCommand( - 'START_OBSESSING_OVER_HOST', - 'START_OBSESSING_OVER_SVC' - ); - - $form->setGlobalCommands( - 'START_OBSESSING_OVER_HOST_CHECKS', - 'START_OBSESSING_OVER_SVC_CHECKS' - ); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, obsessing will be enabled')); - } - } - - /** - * Handle command stopacceptingpassivechecks - */ - public function stopacceptingpassivechecksAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Stop Accepting Passive Checks')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Disable passive checks on a program-wide basis.')); - } else { - $form->addNote($this->translate('Passive checks for this object will be omitted.')); - } - - $form->setCommand( - 'DISABLE_PASSIVE_HOST_CHECKS', - 'DISABLE_PASSIVE_SVC_CHECKS' - ); - - $form->setGlobalCommands( - 'STOP_ACCEPTING_PASSIVE_HOST_CHECKS', - 'STOP_ACCEPTING_PASSIVE_SVC_CHECKS' - ); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, passive check results will be refused')); - } - } - - /** - * Handle command startacceptingpassivechecks - */ - public function startacceptingpassivechecksAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Start Accepting Passive Checks')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Enable passive checks on a program-wide basis.')); - } else { - $form->addNote($this->translate('Passive checks for this object will be accepted.')); - } - - $form->setCommand( - 'ENABLE_PASSIVE_HOST_CHECKS', - 'ENABLE_PASSIVE_SVC_CHECKS' - ); - - $form->setGlobalCommands( - 'START_ACCEPTING_PASSIVE_HOST_CHECKS', - 'START_ACCEPTING_PASSIVE_SVC_CHECKS' - ); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, passive check results will be accepted')); - } - } - - /** - * Disable notifications with expiration - * - * This is a global command only - */ - public function disablenotificationswithexpireAction() - { - $this->setParam('global', 1); - $form = new DisableNotificationWithExpireForm(); - $form->setRequest($this->_request); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, notifications will be disabled')); - } - } - - /** - * Handle command disablenotifications - */ - public function disablenotificationsAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - - $form->setSubmitLabel($this->translate('Disable Notifications')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Disable notifications on a program-wide basis.')); - } else { - $form->addNote($this->translate('Notifications for this object will be disabled.')); - } - - $form->setCommand('DISABLE_HOST_NOTIFICATIONS', 'DISABLE_SVC_NOTIFICATIONS'); - $form->setGlobalCommands('DISABLE_NOTIFICATIONS'); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, notifications will be disabled')); - } - - } - - /** - * Handle command enablenotifications - */ - public function enablenotificationsAction() - { - $this->addTitleTab('Enable Notifications'); - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - - $form->setSubmitLabel($this->translate('Enable Notifications')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Enable notifications on a program-wide basis.')); - } else { - $form->addNote($this->translate('Notifications for this object will be enabled.')); - } - - $form->setCommand('ENABLE_HOST_NOTIFICATIONS', 'ENABLE_SVC_NOTIFICATIONS'); - $form->setGlobalCommands('ENABLE_NOTIFICATIONS'); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, notifications will be enabled')); - } - } - - /** - * Handle command sendcustomnotification - */ - public function sendcustomnotificationAction() - { - $this->setSupportedParameters(array('host', 'service')); - $form = new CustomNotificationForm(); - $form->setRequest($this->getRequest()); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Custom notification has been sent')); - } - } - - /** - * Handle command scheduledowntime - */ - public function scheduledowntimeAction() - { - $this->addTitleTab('Schedule Downtime'); - $this->setSupportedParameters(array('host', 'service')); - $form = new ScheduleDowntimeForm(); - $form->setRequest($this->getRequest()); - $form->setConfiguration(Config::app()); - $form->setWithChildren(false); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Downtime scheduling requested')); - } - } - - /** - * Handle command scheduledowntimeswithchildren - */ - public function scheduledowntimeswithchildrenAction() - { - $this->setSupportedParameters(array('host')); - $form = new ScheduleDowntimeForm(); - $form->setRequest($this->getRequest()); - $form->setConfiguration(Config::app()); - $form->setWithChildren(true); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Downtime scheduling requested')); - } - } - - /** - * Handle command removedowntimeswithchildren - */ - public function removedowntimeswithchildrenAction() - { - $this->setSupportedParameters(array('host')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Remove Downtime(s)')); - $form->addNote($this->translate('Remove downtime(s) from this host and its services.')); - $form->setCommand('DEL_DOWNTIME_BY_HOST_NAME', 'DEL_DOWNTIME_BY_HOST_NAME'); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Downtime removal requested')); - } - } - - /** - * Handle command disablenotificationswithchildren - */ - public function disablenotificationswithchildrenAction() - { - $this->setSupportedParameters(array('host')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Disable Notifications')); - $form->addNote($this->translate('Notifications for this host and its services will be disabled.')); - $form->setCommand('DISABLE_ALL_NOTIFICATIONS_BEYOND_HOST'); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - $form->setCommand('DISABLE_HOST_NOTIFICATIONS', 'DISABLE_SVC_NOTIFICATIONS'); - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, notifications will be disabled')); - } - } - - /** - * Handle command enablenotificationswithchildren - */ - public function enablenotificationswithchildrenAction() - { - $this->setSupportedParameters(array('host')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Enable Notifications')); - $form->addNote($this->translate('Notifications for this host and its services will be enabled.')); - $form->setCommand('ENABLE_ALL_NOTIFICATIONS_BEYOND_HOST'); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - $form->setCommand('ENABLE_HOST_NOTIFICATIONS', 'ENABLE_SVC_NOTIFICATIONS'); - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, notifications will be enabled')); - } - } - - /** - * Handle command reschedulenextcheckwithchildren - */ - public function reschedulenextcheckwithchildrenAction() - { - $this->setSupportedParameters(array('host')); - $form = new RescheduleNextCheckForm(); - $form->setRequest($this->getRequest()); - $form->setConfiguration(Config::app()); - $form->setWithChildren(true); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, checks will be rescheduled')); - } - } - - /** - * Handle command disableactivecheckswithchildren - */ - public function disableactivecheckswithchildrenAction() - { - $this->setSupportedParameters(array('host')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Disable Active Checks')); - $form->addNote($this->translate('Disable active checks for this host and its services.')); - $form->setCommand('DISABLE_HOST_CHECK'); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - // @TODO(mh): Missing child command - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, active checks will be disabled')); - } - } - - /** - * Handle command enableactivecheckswithchildren - */ - public function enableactivecheckswithchildrenAction() - { - $this->setSupportedParameters(array('host')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Enable Active Checks')); - $form->addNote($this->translate('Enable active checks for this host and its services.')); - $form->setCommand('ENABLE_HOST_CHECK'); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - // @TODO(mh): Missing child command - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, active checks will be enabled')); - } - } - - /** - * Handle command disableeventhandler - */ - public function disableeventhandlerAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Disable Event Handler')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Disable event handler for the whole system.')); - } else { - $form->addNote($this->translate('Disable event handler for this object.')); - } - - $form->setCommand( - 'DISABLE_HOST_EVENT_HANDLER', - 'DISABLE_SVC_EVENT_HANDLER' - ); - - $form->setGlobalCommands('DISABLE_EVENT_HANDLERS'); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, event handlers will be disabled')); - } - } - - /** - * Handle command enableeventhandler - */ - public function enableeventhandlerAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Enable Event Handler')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Enable event handlers on the whole system.')); - } else { - $form->addNote($this->translate('Enable event handler for this object.')); - } - - $form->setCommand( - 'ENABLE_HOST_EVENT_HANDLER', - 'ENABLE_SVC_EVENT_HANDLER' - ); - - $form->setGlobalCommands('ENABLE_EVENT_HANDLERS'); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, event handlers will be enabled')); - } - } - - /** - * Handle command disableflapdetection - */ - public function disableflapdetectionAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Disable Flapping Detection')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Disable flapping detection on a program-wide basis.')); - } else { - $form->addNote($this->translate('Disable flapping detection for this object.')); - } - - $form->setCommand( - 'DISABLE_HOST_FLAP_DETECTION', - 'DISABLE_SVC_FLAP_DETECTION' - ); - - $form->setGlobalCommands( - 'DISABLE_FLAP_DETECTION' - ); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, flap detection will be disabled')); - } - } - - /** - * Handle command enableflapdetection - */ - public function enableflapdetectionAction() - { - $this->setSupportedParameters(array('host', 'service', 'global')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Enable Flapping Detection')); - - if ($form->provideGlobalCommand() === true) { - $form->addNote($this->translate('Enable flapping detection on a program-wide basis.')); - } else { - $form->addNote($this->translate('Enable flapping detection for this object.')); - } - - $form->setCommand( - 'ENABLE_HOST_FLAP_DETECTION', - 'ENABLE_SVC_FLAP_DETECTION' - ); - - $form->setGlobalCommands( - 'ENABLE_FLAP_DETECTION' - ); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, flap detection will be enabled')); - } - } - - /** - * Handle command addcomment - */ - public function addcommentAction() - { - $this->addTitleTab('Add comment'); - $this->setSupportedParameters(array('host', 'service')); - $form = new CommentForm(); - $form->setRequest($this->_request); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Your new comment has been submitted')); - } - } - - /** - * Remove a single comment - */ - public function removecommentAction() - { - $this->addTitleTab('Remove Comment'); - $this->setSupportedParameters(array('commentid', 'host', 'service')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->_request); - $form->setCommand('DEL_HOST_COMMENT', 'DEL_SVC_COMMENT'); - $form->setParameterName('commentid'); - $form->setSubmitLabel($this->translate('Remove comment')); - $form->setObjectIgnoreFlag(true); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Comment removal has been requested')); - } - } - - /** - * Handle command resetattributes - */ - public function resetattributesAction() - { - $this->setSupportedParameters(array('host', 'service')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Reset Attributes')); - $form->addNote($this->translate('Reset modified attributes to its default.')); - $form->setCommand('CHANGE_HOST_MODATTR', 'CHANGE_SVC_MODATTR'); - $form->setParameterValue(0); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - } - } - - /** - * Handle command acknowledgeproblem - */ - public function acknowledgeproblemAction() - { - $this->addTitleTab('Acknowledge Problem'); - $this->setSupportedParameters(array('host', 'service')); - $form = new AcknowledgeForm(); - $form->setRequest($this->getRequest()); - $form->setConfiguration(Config::app()); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Acknowledgement has been sent')); - } - - $this->setForm($form); - } - - /** - * Handle command removeacknowledgement - */ - public function removeacknowledgementAction() - { - $this->addTitleTab('Remove Acknowledgement'); - $this->setSupportedParameters(array('host', 'service')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - $form->setSubmitLabel($this->translate('Remove Problem Acknowledgement')); - $form->addNote($this->translate('Remove problem acknowledgement for this object.')); - $form->setCommand('REMOVE_HOST_ACKNOWLEDGEMENT', 'REMOVE_SVC_ACKNOWLEDGEMENT'); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Acknowledgement removal has been requested')); - } - } - - /** - * Handle command delaynotification - */ - public function delaynotificationAction() - { - $this->setSupportedParameters(array('host', 'service')); - $form = new DelayNotificationForm(); - $form->setRequest($this->getRequest()); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Notification delay has been requested')); - } - } - - /** - * Handle command removedowntime - */ - public function removedowntimeAction() - { - $this->setSupportedParameters(array('host', 'service', 'downtimeid')); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->getRequest()); - - $form->setSubmitLabel($this->translate('Delete Downtime')); - $form->setParameterName('downtimeid'); - $form->addNote($this->translate('Delete a single downtime with the id shown above')); - $form->setCommand('DEL_HOST_DOWNTIME', 'DEL_SVC_DOWNTIME'); - $form->setObjectIgnoreFlag(true); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Downtime removal has been requested')); - } - } - - /** - * Shutdown the icinga process - */ - public function shutdownprocessAction() - { - $this->setParam('global', '1'); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->_request); - - $form->setSubmitLabel($this->translate('Shutdown monitoring process')); - $form->addNote($this->translate('Stop monitoring instance. You have to start it again from command line.')); - $form->setGlobalCommands('SHUTDOWN_PROCESS'); - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, process will shut down')); - } - } - - /** - * Restart the icinga process - */ - public function restartprocessAction() - { - $this->setParam('global', '1'); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->_request); - - $form->setSubmitLabel($this->translate('Restart monitoring process')); - $form->addNote($this->translate('Restart the monitoring process.')); - $form->setGlobalCommands('RESTART_PROCESS'); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, monitoring process will restart now')); - } - } - - /** - * Disable processing of performance data - */ - public function disableperformancedataAction() - { - $this->setParam('global', 1); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->_request); - - $form->setSubmitLabel($this->translate('Disable Performance Data')); - $form->addNote($this->translate('Disable processing of performance data on a program-wide basis.')); - - $form->setGlobalCommands('DISABLE_PERFORMANCE_DATA'); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, performance data processing will be disabled')); - } - } - - /** - * Enable processing of performance data - */ - public function enableperformancedataAction() - { - $this->setParam('global', 1); - $form = new SingleArgumentCommandForm(); - $form->setRequest($this->_request); - - $form->setSubmitLabel($this->translate('Enable Performance Data')); - $form->addNote($this->translate('Enable processing of performance data on a program-wide basis.')); - - $form->setGlobalCommands('ENABLE_PERFORMANCE_DATA'); - - $this->setForm($form); - - if ($form->IsSubmittedAndValid() === true) { - $this->target->sendCommand($form->createCommand(), $this->view->objects); - Notification::success($this->translate('Command has been sent, performance data processing will be enabled')); - } - } -} diff --git a/modules/monitoring/application/controllers/CommentController.php b/modules/monitoring/application/controllers/CommentController.php new file mode 100644 index 000000000..59ad90fc7 --- /dev/null +++ b/modules/monitoring/application/controllers/CommentController.php @@ -0,0 +1,100 @@ +params->getRequired('comment_id'); + + $query = $this->backend->select()->from('comment', array( + 'id' => 'comment_internal_id', + 'objecttype' => 'object_type', + 'comment' => 'comment_data', + 'author' => 'comment_author_name', + 'timestamp' => 'comment_timestamp', + 'type' => 'comment_type', + 'persistent' => 'comment_is_persistent', + 'expiration' => 'comment_expiration', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + ))->where('comment_internal_id', $commentId); + $this->applyRestriction('monitoring/filter/objects', $query); + + $this->comment = $query->getQuery()->fetchRow(); + if ($this->comment === false) { + $this->httpNotFound($this->translate('Comment not found')); + } + + $this->getTabs()->add( + 'comment', + array( + 'title' => $this->translate( + 'Display detailed information about a comment.' + ), + 'icon' => 'comment', + 'label' => $this->translate('Comment'), + 'url' =>'monitoring/comments/show' + ) + )->activate('comment')->extend(new DashboardAction()); + } + + /** + * Display comment detail view + */ + public function showAction() + { + $listCommentsLink = Url::fromPath('monitoring/list/comments') + ->setQueryString('comment_type=(comment|ack)'); + + $this->view->comment = $this->comment; + if ($this->hasPermission('monitoring/command/comment/delete')) { + $this->view->delCommentForm = $this->createDelCommentForm(); + $this->view->delCommentForm->populate( + array( + 'redirect' => $listCommentsLink, + 'comment_id' => $this->comment->id, + 'comment_is_service' => isset($this->comment->service_description) + ) + ); + } + } + + /** + * Create a command form to delete a single comment + * + * @return DeleteCommentsCommandForm + */ + private function createDelCommentForm() + { + $this->assertPermission('monitoring/command/comment/delete'); + + $delCommentForm = new DeleteCommentCommandForm(); + $delCommentForm->setAction( + Url::fromPath('monitoring/comment/show') + ->setParam('comment_id', $this->comment->id) + ); + $delCommentForm->handleRequest(); + return $delCommentForm; + } +} diff --git a/modules/monitoring/application/controllers/CommentsController.php b/modules/monitoring/application/controllers/CommentsController.php new file mode 100644 index 000000000..a86b30310 --- /dev/null +++ b/modules/monitoring/application/controllers/CommentsController.php @@ -0,0 +1,102 @@ +filter = Filter::fromQueryString(str_replace( + 'comment_id', + 'comment_internal_id', + (string)$this->params + )); + $query = $this->backend->select()->from('comment', array( + 'id' => 'comment_internal_id', + 'objecttype' => 'object_type', + 'comment' => 'comment_data', + 'author' => 'comment_author_name', + 'timestamp' => 'comment_timestamp', + 'type' => 'comment_type', + 'persistent' => 'comment_is_persistent', + 'expiration' => 'comment_expiration', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + ))->addFilter($this->filter); + $this->applyRestriction('monitoring/filter/objects', $query); + + $this->comments = $query->getQuery()->fetchAll(); + if (false === $this->comments) { + throw new Zend_Controller_Action_Exception($this->translate('Comment not found')); + } + + $this->getTabs()->add( + 'comments', + array( + 'title' => $this->translate( + 'Display detailed information about multiple comments.' + ), + 'icon' => 'comment', + 'label' => $this->translate('Comments') . sprintf(' (%d)', count($this->comments)), + 'url' =>'monitoring/comments/show' + ) + )->activate('comments'); + } + + /** + * Display the detail view for a comment list + */ + public function showAction() + { + $this->view->comments = $this->comments; + $this->view->listAllLink = Url::fromPath('monitoring/list/comments') + ->setQueryString($this->filter->toQueryString()); + $this->view->removeAllLink = Url::fromPath('monitoring/comments/delete-all') + ->setParams($this->params); + } + + /** + * Display the form for removing a comment list + */ + public function deleteAllAction() + { + $this->assertPermission('monitoring/command/comment/delete'); + + $listCommentsLink = Url::fromPath('monitoring/list/comments') + ->setQueryString('comment_type=(comment|ack)'); + $delCommentForm = new DeleteCommentsCommandForm(); + $delCommentForm->setTitle($this->view->translate('Remove all Comments')); + $delCommentForm->addDescription(sprintf( + $this->translate('Confirm removal of %d comments.'), + count($this->comments) + )); + $delCommentForm->setComments($this->comments) + ->setRedirectUrl($listCommentsLink) + ->handleRequest(); + $this->view->delCommentForm = $delCommentForm; + $this->view->comments = $this->comments; + $this->view->listAllLink = Url::fromPath('monitoring/list/comments') + ->setQueryString($this->filter->toQueryString()); + } +} diff --git a/modules/monitoring/application/controllers/ConfigController.php b/modules/monitoring/application/controllers/ConfigController.php index ed5d57570..2622b9c54 100644 --- a/modules/monitoring/application/controllers/ConfigController.php +++ b/modules/monitoring/application/controllers/ConfigController.php @@ -1,11 +1,11 @@ params->getRequired('backend'); + $form = new BackendConfigForm(); + $form->setRedirectUrl('monitoring/config'); + $form->setTitle(sprintf($this->translate('Edit Monitoring Backend %s'), $backendName)); $form->setIniConfig($this->Config('backends')); $form->setResourceConfig(ResourceFactory::getResourceConfigs()); - $form->setRedirectUrl('monitoring/config'); - $form->handleRequest(); + $form->setOnSuccess(function (BackendConfigForm $form) use ($backendName) { + try { + $form->edit($backendName, array_map( + function ($v) { + return $v !== '' ? $v : null; + }, + $form->getValues() + )); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(sprintf(t('Monitoring backend "%s" successfully updated'), $backendName)); + return true; + } + + return false; + }); + + try { + $form->load($backendName); + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('Monitoring backend "%s" not found'), $backendName)); + } $this->view->form = $form; + $this->render('form'); } /** - * Display a form to create a new backend + * Create a new monitoring backend */ public function createbackendAction() { $form = new BackendConfigForm(); - $form->setIniConfig($this->Config('backends')); - $form->setResourceConfig(ResourceFactory::getResourceConfigs()); $form->setRedirectUrl('monitoring/config'); + $form->setTitle($this->translate('Create New Monitoring Backend')); + $form->setIniConfig($this->Config('backends')); + + try { + $form->setResourceConfig(ResourceFactory::getResourceConfigs()); + } catch (ConfigurationError $e) { + if ($this->hasPermission('config/application/resources')) { + Notification::error($e->getMessage()); + $this->redirectNow('config/createresource'); + } + + throw $e; // No permission for resource configuration, show the error + } + + $form->setOnSuccess(function (BackendConfigForm $form) { + try { + $form->add(array_filter($form->getValues())); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(t('Monitoring backend successfully created')); + return true; + } + + return false; + }); $form->handleRequest(); $this->view->form = $form; + $this->render('form'); } /** @@ -58,89 +116,142 @@ class Monitoring_ConfigController extends ModuleActionController */ public function removebackendAction() { - $config = $this->Config('backends'); - $form = new ConfirmRemovalForm(array( - 'onSuccess' => function ($form) use ($config) { - $backendName = $form->getRequest()->getQuery('backend'); - $configForm = new BackendConfigForm(); - $configForm->setIniConfig($config); + $backendName = $this->params->getRequired('backend'); - try { - $configForm->remove($backendName); - } catch (InvalidArgumentException $e) { - Notification::error($e->getMessage()); - return; - } - - if ($configForm->save()) { - Notification::success(sprintf(mt('monitoring', 'Backend "%s" successfully removed.'), $backendName)); - } else { - return false; - } - } - )); + $backendForm = new BackendConfigForm(); + $backendForm->setIniConfig($this->Config('backends')); + $form = new ConfirmRemovalForm(); $form->setRedirectUrl('monitoring/config'); + $form->setTitle(sprintf($this->translate('Remove Monitoring Backend %s'), $backendName)); + $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($backendName, $backendForm) { + try { + $backendForm->delete($backendName); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($backendForm->save()) { + Notification::success(sprintf(t('Monitoring backend "%s" successfully removed'), $backendName)); + return true; + } + + return false; + }); $form->handleRequest(); $this->view->form = $form; + $this->render('form'); } /** - * Display a confirmation form to remove the instance identified by the 'instance' parameter + * Remove a monitoring instance */ public function removeinstanceAction() { - $config = $this->Config('instances'); - $form = new ConfirmRemovalForm(array( - 'onSuccess' => function ($form) use ($config) { - $instanceName = $form->getRequest()->getQuery('instance'); - $configForm = new InstanceConfigForm(); - $configForm->setIniConfig($config); + $instanceName = $this->params->getRequired('instance'); - try { - $configForm->remove($instanceName); - } catch (InvalidArgumentException $e) { - Notification::error($e->getMessage()); - return; - } - - if ($configForm->save()) { - Notification::success(sprintf(mt('monitoring', 'Instance "%s" successfully removed.'), $instanceName)); - } else { - return false; - } - } - )); + $instanceForm = new InstanceConfigForm(); + $instanceForm->setIniConfig($this->Config('instances')); + $form = new ConfirmRemovalForm(); $form->setRedirectUrl('monitoring/config'); + $form->setTitle(sprintf($this->translate('Remove Monitoring Instance %s'), $instanceName)); + $form->addDescription($this->translate( + 'If you have still any environments or views referring to this instance, ' + . 'you won\'t be able to send commands anymore after deletion.' + )); + $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($instanceName, $instanceForm) { + try { + $instanceForm->delete($instanceName); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($instanceForm->save()) { + Notification::success(sprintf(t('Monitoring instance "%s" successfully removed'), $instanceName)); + return true; + } + + return false; + }); $form->handleRequest(); $this->view->form = $form; + $this->render('form'); } /** - * Display a form to edit the instance identified by the 'instance' parameter of the request + * Edit a monitoring instance */ public function editinstanceAction() { + $instanceName = $this->params->getRequired('instance'); + $form = new InstanceConfigForm(); - $form->setIniConfig($this->Config('instances')); $form->setRedirectUrl('monitoring/config'); - $form->handleRequest(); + $form->setTitle(sprintf($this->translate('Edit Monitoring Instance %s'), $instanceName)); + $form->setIniConfig($this->Config('instances')); + $form->setOnSuccess(function (InstanceConfigForm $form) use ($instanceName) { + try { + $form->edit($instanceName, array_map( + function ($v) { + return $v !== '' ? $v : null; + }, + $form->getValues() + )); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(sprintf(t('Monitoring instance "%s" successfully updated'), $instanceName)); + return true; + } + + return false; + }); + + try { + $form->load($instanceName); + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('Monitoring instance "%s" not found'), $instanceName)); + } $this->view->form = $form; + $this->render('form'); } /** - * Display a form to create a new instance + * Create a new monitoring instance */ public function createinstanceAction() { $form = new InstanceConfigForm(); - $form->setIniConfig($this->Config('instances')); $form->setRedirectUrl('monitoring/config'); + $form->setTitle($this->translate('Create New Monitoring Instance')); + $form->setIniConfig($this->Config('instances')); + $form->setOnSuccess(function (InstanceConfigForm $form) { + try { + $form->add(array_filter($form->getValues())); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(t('Monitoring instance successfully created')); + return true; + } + + return false; + }); $form->handleRequest(); $this->view->form = $form; + $this->render('form'); } /** @@ -154,5 +265,6 @@ class Monitoring_ConfigController extends ModuleActionController $this->view->form = $form; $this->view->tabs = $this->Module()->getConfigTabs()->activate('security'); + $this->render('form'); } } diff --git a/modules/monitoring/application/controllers/DowntimeController.php b/modules/monitoring/application/controllers/DowntimeController.php new file mode 100644 index 000000000..077f7e264 --- /dev/null +++ b/modules/monitoring/application/controllers/DowntimeController.php @@ -0,0 +1,139 @@ +params->getRequired('downtime_id'); + + $query = $this->backend->select()->from('downtime', array( + 'id' => 'downtime_internal_id', + 'objecttype' => 'object_type', + 'comment' => 'downtime_comment', + 'author_name' => 'downtime_author_name', + 'start' => 'downtime_start', + 'scheduled_start' => 'downtime_scheduled_start', + 'scheduled_end' => 'downtime_scheduled_end', + 'end' => 'downtime_end', + 'duration' => 'downtime_duration', + 'is_flexible' => 'downtime_is_flexible', + 'is_fixed' => 'downtime_is_fixed', + 'is_in_effect' => 'downtime_is_in_effect', + 'entry_time' => 'downtime_entry_time', + 'host_state', + 'service_state', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + ))->where('downtime_internal_id', $downtimeId); + $this->applyRestriction('monitoring/filter/objects', $query); + + $this->downtime = $query->getQuery()->fetchRow(); + if ($this->downtime === false) { + $this->httpNotFound($this->translate('Downtime not found')); + } + + if (isset($this->downtime->service_description)) { + $this->isService = true; + } else { + $this->isService = false; + } + + $this->getTabs() + ->add( + 'downtime', + array( + 'title' => $this->translate( + 'Display detailed information about a downtime.' + ), + 'icon' => 'plug', + 'label' => $this->translate('Downtime'), + 'url' =>'monitoring/downtimes/show' + ) + )->activate('downtime')->extend(new DashboardAction()); + } + + /** + * Display the detail view for a downtime + */ + public function showAction() + { + $this->view->downtime = $this->downtime; + $this->view->isService = $this->isService; + $this->view->stateName = isset($this->downtime->service_description) ? + Service::getStateText($this->downtime->service_state) : + Host::getStateText($this->downtime->host_state); + $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes'); + $this->view->showHostLink = Url::fromPath('monitoring/host/show') + ->setParam('host', $this->downtime->host_name); + $this->view->showServiceLink = Url::fromPath('monitoring/service/show') + ->setParam('host', $this->downtime->host_name) + ->setParam('service', $this->downtime->service_description); + if ($this->hasPermission('monitoring/command/downtime/delete')) { + $this->view->delDowntimeForm = $this->createDelDowntimeForm(); + $this->view->delDowntimeForm->populate( + array( + 'redirect' => Url::fromPath('monitoring/list/downtimes'), + 'downtime_id' => $this->downtime->id, + 'downtime_is_service' => $this->isService + ) + ); + } + } + + /** + * Receive DeleteDowntimeCommandForm post from other controller + */ + public function removeAction() + { + $this->assertHttpMethod('POST'); + $this->createDelDowntimeForm(); + } + + /** + * Create a command form to delete a single comment + * + * @return DeleteDowntimeCommandForm + */ + private function createDelDowntimeForm() + { + $this->assertPermission('monitoring/command/downtime/delete'); + $delDowntimeForm = new DeleteDowntimeCommandForm(); + $delDowntimeForm->setAction( + Url::fromPath('monitoring/downtime/show') + ->setParam('downtime_id', $this->downtime->id) + ); + $delDowntimeForm->handleRequest(); + return $delDowntimeForm; + } +} diff --git a/modules/monitoring/application/controllers/DowntimesController.php b/modules/monitoring/application/controllers/DowntimesController.php new file mode 100644 index 000000000..35950acfe --- /dev/null +++ b/modules/monitoring/application/controllers/DowntimesController.php @@ -0,0 +1,130 @@ +filter = Filter::fromQueryString(str_replace( + 'downtime_id', + 'downtime_internal_id', + (string)$this->params + )); + $query = $this->backend->select()->from('downtime', array( + 'id' => 'downtime_internal_id', + 'objecttype' => 'object_type', + 'comment' => 'downtime_comment', + 'author_name' => 'downtime_author_name', + 'start' => 'downtime_start', + 'scheduled_start' => 'downtime_scheduled_start', + 'scheduled_end' => 'downtime_scheduled_end', + 'end' => 'downtime_end', + 'duration' => 'downtime_duration', + 'is_flexible' => 'downtime_is_flexible', + 'is_fixed' => 'downtime_is_fixed', + 'is_in_effect' => 'downtime_is_in_effect', + 'entry_time' => 'downtime_entry_time', + 'host_state', + 'service_state', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + ))->addFilter($this->filter); + $this->applyRestriction('monitoring/filter/objects', $query); + + $this->downtimes = $query->getQuery()->fetchAll(); + if (false === $this->downtimes) { + throw new Zend_Controller_Action_Exception( + $this->translate('Downtime not found') + ); + } + + $this->getTabs()->add( + 'downtimes', + array( + 'title' => $this->translate( + 'Display detailed information about multiple downtimes.' + ), + 'icon' => 'plug', + 'label' => $this->translate('Downtimes') . sprintf(' (%d)', count($this->downtimes)), + 'url' =>'monitoring/downtimes/show' + ) + )->activate('downtimes'); + + foreach ($this->downtimes as $downtime) { + if (isset($downtime->service_description)) { + $downtime->isService = true; + } else { + $downtime->isService = false; + } + + if ($downtime->isService) { + $downtime->stateText = Service::getStateText($downtime->service_state); + } else { + $downtime->stateText = Host::getStateText($downtime->host_state); + } + } + } + + /** + * Display the detail view for a downtime list + */ + public function showAction() + { + $this->view->downtimes = $this->downtimes; + $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes') + ->setQueryString($this->filter->toQueryString()); + $this->view->removeAllLink = Url::fromPath('monitoring/downtimes/delete-all') + ->setParams($this->params); + } + + /** + * Display the form for removing a downtime list + */ + public function deleteAllAction() + { + $this->assertPermission('monitoring/command/downtime/delete'); + $this->view->downtimes = $this->downtimes; + $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes') + ->setQueryString($this->filter->toQueryString()); + $delDowntimeForm = new DeleteDowntimesCommandForm(); + $delDowntimeForm->setTitle($this->view->translate('Remove all Downtimes')); + $delDowntimeForm->addDescription(sprintf( + $this->translate('Confirm removal of %d downtimes.'), + count($this->downtimes) + )); + $delDowntimeForm->setRedirectUrl(Url::fromPath('monitoring/list/downtimes')); + $delDowntimeForm->setDowntimes($this->downtimes)->handleRequest(); + $this->view->delDowntimeForm = $delDowntimeForm; + } +} diff --git a/modules/monitoring/application/controllers/HostController.php b/modules/monitoring/application/controllers/HostController.php index 0a4a727ed..1016092cf 100644 --- a/modules/monitoring/application/controllers/HostController.php +++ b/modules/monitoring/application/controllers/HostController.php @@ -1,13 +1,15 @@ backend, $this->params->get('host')); + $host = new Host($this->backend, $this->params->getRequired('host')); + + $this->applyRestriction('monitoring/filter/objects', $host); + if ($host->fetch() === false) { - throw new Zend_Controller_Action_Exception($this->translate('Host not found')); + $this->httpNotFound($this->translate('Host not found')); } $this->object = $host; $this->createTabs(); + $this->getTabs()->activate('host'); + } + + /** + * Get host actions from hook + * + * @return array + */ + protected function getHostActions() + { + $urls = array(); + + foreach (Hook::all('Monitoring\\HostActions') as $hook) { + foreach ($hook->getActionsForHost($this->object) as $id => $url) { + $urls[$id] = $url; + } + } + + return $urls; } /** @@ -37,17 +59,65 @@ class Monitoring_HostController extends MonitoredObjectController */ public function showAction() { - $this->getTabs()->activate('host'); + $this->view->actions = $this->getHostActions(); parent::showAction(); } + /** + * List a host's services + */ + public function servicesAction() + { + $this->setAutorefreshInterval(10); + $this->getTabs()->activate('services'); + $query = $this->backend->select()->from('servicestatus', array( + 'host_name', + 'host_display_name', + 'host_state', + 'host_state_type', + 'host_last_state_change', + 'host_address', + 'host_handled', + 'service_description', + 'service_display_name', + 'service_state', + 'service_in_downtime', + 'service_acknowledged', + 'service_handled', + 'service_output', + 'service_perfdata', + 'service_attempt', + 'service_last_state_change', + 'service_icon_image', + 'service_icon_image_alt', + 'service_is_flapping', + 'service_state_type', + 'service_handled', + 'service_severity', + 'service_last_check', + 'service_notifications_enabled', + 'service_action_url', + 'service_notes_url', + 'service_active_checks_enabled', + 'service_passive_checks_enabled', + 'current_check_attempt' => 'service_current_check_attempt', + 'max_check_attempts' => 'service_max_check_attempts' + )); + $this->applyRestriction('monitoring/filter/objects', $query); + $this->view->services = $query->where('host_name', $this->object->getName()); + $this->view->object = $this->object; + } + /** * Acknowledge a host problem */ public function acknowledgeProblemAction() { - $this->view->title = $this->translate('Acknowledge Host Problem'); - $this->handleCommandForm(new AcknowledgeProblemCommandForm()); + $this->assertPermission('monitoring/command/acknowledge-problem'); + + $form = new AcknowledgeProblemCommandForm(); + $form->setTitle($this->translate('Acknowledge Host Problem')); + $this->handleCommandForm($form); } /** @@ -55,8 +125,11 @@ class Monitoring_HostController extends MonitoredObjectController */ public function addCommentAction() { - $this->view->title = $this->translate('Add Host Comment'); - $this->handleCommandForm(new AddCommentCommandForm()); + $this->assertPermission('monitoring/command/comment/add'); + + $form = new AddCommentCommandForm(); + $form->setTitle($this->translate('Add Host Comment')); + $this->handleCommandForm($form); } /** @@ -64,8 +137,11 @@ class Monitoring_HostController extends MonitoredObjectController */ public function rescheduleCheckAction() { - $this->view->title = $this->translate('Reschedule Host Check'); - $this->handleCommandForm(new ScheduleHostCheckCommandForm()); + $this->assertPermission('monitoring/command/schedule-check'); + + $form = new ScheduleHostCheckCommandForm(); + $form->setTitle($this->translate('Reschedule Host Check')); + $this->handleCommandForm($form); } /** @@ -73,7 +149,35 @@ class Monitoring_HostController extends MonitoredObjectController */ public function scheduleDowntimeAction() { - $this->view->title = $this->translate('Schedule Host Downtime'); - $this->handleCommandForm(new ScheduleHostDowntimeCommandForm()); + $this->assertPermission('monitoring/command/downtime/schedule'); + + $form = new ScheduleHostDowntimeCommandForm(); + $form->setTitle($this->translate('Schedule Host Downtime')); + $this->handleCommandForm($form); + } + + /** + * Submit a passive host check result + */ + public function processCheckResultAction() + { + $this->assertPermission('monitoring/command/process-check-result'); + + $form = new ProcessCheckResultCommandForm(); + $form->setBackend($this->backend); + $form->setTitle($this->translate('Submit Passive Host Check Result')); + $this->handleCommandForm($form); + } + + /** + * Send a custom notification for host + */ + public function sendCustomNotificationAction() + { + $this->assertPermission('monitoring/command/send-custom-notification'); + + $form = new SendCustomNotificationCommandForm(); + $form->setTitle($this->translate('Send Custom Host Notification')); + $this->handleCommandForm($form); } } diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php index fe34c1d6f..9d5da9e50 100644 --- a/modules/monitoring/application/controllers/HostsController.php +++ b/modules/monitoring/application/controllers/HostsController.php @@ -1,20 +1,21 @@ backend); - $hostList->setFilter(Filter::fromQueryString((string) $this->params)); + $this->applyRestriction('monitoring/filter/objects', $hostList); + $hostList->addFilter(Filter::fromQueryString((string) $this->params)); $this->hostList = $hostList; + $this->getTabs()->add( + 'show', + array( + 'title' => sprintf( + $this->translate('Show summarized information for %u hosts'), + count($this->hostList) + ), + 'label' => $this->translate('Hosts') . sprintf(' (%d)', count($this->hostList)), + 'url' => Url::fromRequest(), + 'icon' => 'host' + ) + )->extend(new DashboardAction())->activate('show'); + $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts'); } protected function handleCommandForm(ObjectsCommandForm $form) { + $this->hostList->setColumns(array( + 'host_icon_image', + 'host_icon_image_alt', + 'host_name', + 'host_address', + 'host_state', + 'host_problem', + 'host_handled', + 'host_acknowledged', + 'host_in_downtime', + 'host_is_flapping', + 'host_output', + 'host_notifications_enabled', + 'host_active_checks_enabled', + 'host_passive_checks_enabled' + )); + $form ->setObjects($this->hostList) ->setRedirectUrl(Url::fromPath('monitoring/hosts/show')->setParams($this->params)) ->handleRequest(); + $this->view->form = $form; - $this->_helper->viewRenderer('partials/command-form', null, true); + $this->view->objects = $this->hostList; + $this->view->stats = $this->hostList->getStateSummary(); + $this->_helper->viewRenderer('partials/command/objects-command-form', null, true); return $form; } public function showAction() { - $this->getTabs()->add( - 'show', - array( - 'title' => mt('monitoring', 'Hosts'), - 'url' => Url::fromRequest() - ) - )->activate('show'); $this->setAutorefreshInterval(15); $checkNowForm = new CheckNowCommandForm(); $checkNowForm @@ -57,41 +85,26 @@ class Monitoring_HostsController extends Controller ->handleRequest(); $this->view->checkNowForm = $checkNowForm; $this->hostList->setColumns(array( + 'host_icon_image', + 'host_icon_image_alt', 'host_name', + 'host_address', 'host_state', 'host_problem', 'host_handled', 'host_acknowledged', - 'host_in_downtime'/*, - 'host_passive_checks_enabled', + 'host_in_downtime', + 'host_is_flapping', + 'host_output', 'host_notifications_enabled', - 'host_event_handler_enabled', - 'host_flap_detection_enabled', 'host_active_checks_enabled', + 'host_passive_checks_enabled' + /*'host_event_handler_enabled', + 'host_flap_detection_enabled', 'host_obsessing'*/ )); - $unhandledObjects = array(); - $acknowledgedObjects = array(); - $objectsInDowntime = array(); - $hostStates = array( - Host::getStateText(Host::STATE_UP) => 0, - Host::getStateText(Host::STATE_DOWN) => 0, - Host::getStateText(Host::STATE_UNREACHABLE) => 0, - Host::getStateText(Host::STATE_PENDING) => 0, - ); - foreach ($this->hostList as $host) { - /** @var Service $host */ - if ((bool) $host->problem === true && (bool) $host->handled === false) { - $unhandledObjects[] = $host; - } - if ((bool) $host->acknowledged === true) { - $acknowledgedObjects[] = $host; - } - if ((bool) $host->in_downtime === true) { - $objectsInDowntime[] = $host; - } - ++$hostStates[$host::getStateText($host->state)]; - } + + $acknowledgedObjects = $this->hostList->getAcknowledgedObjects(); if (! empty($acknowledgedObjects)) { $removeAckForm = new RemoveAcknowledgementCommandForm(); $removeAckForm @@ -99,40 +112,70 @@ class Monitoring_HostsController extends Controller ->handleRequest(); $this->view->removeAckForm = $removeAckForm; } + + $hostStates = $this->hostList->getStateSummary(); + $this->setAutorefreshInterval(15); - $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts'); $this->view->rescheduleAllLink = Url::fromRequest()->setPath('monitoring/hosts/reschedule-check'); $this->view->downtimeAllLink = Url::fromRequest()->setPath('monitoring/hosts/schedule-downtime'); - $this->view->hostStates = $hostStates; + $this->view->processCheckResultAllLink = Url::fromRequest()->setPath('monitoring/hosts/process-check-result'); + $this->view->addCommentLink = Url::fromRequest()->setPath('monitoring/hosts/add-comment'); + $this->view->stats = $hostStates; $this->view->objects = $this->hostList; - $this->view->unhandledObjects = $unhandledObjects; - $this->view->acknowledgeUnhandledLink = Url::fromRequest() - ->setPath('monitoring/hosts/acknowledge-problem') - ->addParams(array('host_problem' => 1, 'host_handled' => 0)); - $this->view->downtimeUnhandledLink = Url::fromRequest() - ->setPath('monitoring/hosts/schedule-downtime') - ->addParams(array('host_problem' => 1, 'host_handled' => 0)); - $this->view->acknowledgedObjects = $acknowledgedObjects; - $this->view->objectsInDowntime = $objectsInDowntime; - $this->view->inDowntimeLink = Url::fromRequest() - ->setPath('monitoring/list/downtimes'); - $this->view->havingCommentsLink = Url::fromRequest() - ->setPath('monitoring/list/comments'); - $this->view->hostStatesPieChart = $this->createPieChart( - $hostStates, - $this->translate('Host State'), - array('#44bb77', '#FF5566', '#E066FF', '#77AAFF') - ); + $this->view->unhandledObjects = $this->hostList->getUnhandledObjects(); + $this->view->problemObjects = $this->hostList->getProblemObjects(); + $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem') + ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString()); + $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime') + ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString()); + $this->view->downtimeLink = Url::fromPath('monitoring/hosts/schedule-downtime') + ->setQueryString($this->hostList->getProblemObjects()->objectsFilter()->toQueryString()); + $this->view->acknowledgedObjects = $this->hostList->getAcknowledgedObjects(); + $this->view->acknowledgeLink = Url::fromPath('monitoring/hosts/acknowledge-problem') + ->setQueryString($this->hostList->getUnacknowledgedObjects()->objectsFilter()->toQueryString()); + $this->view->unacknowledgedObjects = $this->hostList->getUnacknowledgedObjects(); + $this->view->objectsInDowntime = $this->hostList->getObjectsInDowntime(); + $this->view->inDowntimeLink = Url::fromPath('monitoring/list/hosts') + ->setQueryString( + $this->hostList + ->getObjectsInDowntime() + ->objectsFilter() + ->toQueryString() + ); + $this->view->showDowntimesLink = Url::fromPath('monitoring/list/downtimes') + ->setQueryString( + $this->hostList + ->objectsFilter() + ->andFilter(FilterEqual::where('downtime_objecttype', 'host')) + ->toQueryString() + ); + $this->view->commentsLink = Url::fromRequest()->setPath('monitoring/list/comments'); + $this->view->baseFilter = $this->hostList->getFilter(); + $this->view->sendCustomNotificationLink = Url::fromRequest()->setPath('monitoring/hosts/send-custom-notification'); } - protected function createPieChart(array $states, $title, array $colors) + /** + * Add a host comments + */ + public function addCommentAction() { - $chart = new InlinePie(array_values($states), $title, $colors); - return $chart - ->setLabel(array_map('strtoupper', array_keys($states))) - ->setHeight(100) - ->setWidth(100) - ->setTitle($title); + $this->assertPermission('monitoring/command/comment/add'); + + $form = new AddCommentCommandForm(); + $form->setTitle($this->translate('Add Host Comments')); + $this->handleCommandForm($form); + } + + /** + * Delete a comment + */ + public function deleteCommentAction() + { + $this->assertPermission('monitoring/command/comment/delete'); + + $form = new DeleteCommentCommandForm(); + $form->setTitle($this->translate('Delete Host Comments')); + $this->handleCommandForm($form); } /** @@ -140,8 +183,11 @@ class Monitoring_HostsController extends Controller */ public function acknowledgeProblemAction() { - $this->view->title = $this->translate('Acknowledge Host Problems'); - $this->handleCommandForm(new AcknowledgeProblemCommandForm()); + $this->assertPermission('monitoring/command/acknowledge-problem'); + + $form = new AcknowledgeProblemCommandForm(); + $form->setTitle($this->translate('Acknowledge Host Problems')); + $this->handleCommandForm($form); } /** @@ -149,8 +195,11 @@ class Monitoring_HostsController extends Controller */ public function rescheduleCheckAction() { - $this->view->title = $this->translate('Reschedule Host Checks'); - $this->handleCommandForm(new ScheduleHostCheckCommandForm()); + $this->assertPermission('monitoring/command/schedule-check'); + + $form = new ScheduleHostCheckCommandForm(); + $form->setTitle($this->translate('Reschedule Host Checks')); + $this->handleCommandForm($form); } /** @@ -158,7 +207,35 @@ class Monitoring_HostsController extends Controller */ public function scheduleDowntimeAction() { - $this->view->title = $this->translate('Schedule Host Downtimes'); - $this->handleCommandForm(new ScheduleHostDowntimeCommandForm()); + $this->assertPermission('monitoring/command/downtime/schedule'); + + $form = new ScheduleHostDowntimeCommandForm(); + $form->setTitle($this->translate('Schedule Host Downtimes')); + $this->handleCommandForm($form); + } + + /** + * Submit passive host check results + */ + public function processCheckResultAction() + { + $this->assertPermission('monitoring/command/process-check-result'); + + $form = new ProcessCheckResultCommandForm(); + $form->setBackend($this->backend); + $form->setTitle($this->translate('Submit Passive Host Check Results')); + $this->handleCommandForm($form); + } + + /** + * Send a custom notification for hosts + */ + public function sendCustomNotificationAction() + { + $this->assertPermission('monitoring/command/send-custom-notification'); + + $form = new SendCustomNotificationCommandForm(); + $form->setTitle($this->translate('Send Custom Host Notification')); + $this->handleCommandForm($form); } } diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index c5d7cc906..3cbb9fca6 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -1,43 +1,28 @@ createTabs(); - $this->view->compact = $this->_request->getParam('view') === 'compact'; - if ($this->_request->getParam('view') === 'inline') { - $this->view->compact = true; - $this->view->inline = true; - } - $this->url = Url::fromRequest(); } /** @@ -58,25 +43,6 @@ class Monitoring_ListController extends Controller return $query; } - protected function hasBetterUrl() - { - $request = $this->getRequest(); - $url = clone($this->url); - - if ($this->getRequest()->isPost()) { - if ($request->getPost('sort')) { - $url->setParam('sort', $request->getPost('sort')); - if ($request->getPost('dir')) { - $url->setParam('dir', $request->getPost('dir')); - } else { - $url->removeParam('dir'); - } - return $url; - } - } - return false; - } - /** * Overwrite the backend to use (used for testing) * @@ -92,26 +58,21 @@ class Monitoring_ListController extends Controller */ public function hostsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - // Handle soft and hard states - $stateType = $this->params->shift('stateType', 'soft'); - if ($stateType == 'hard') { + if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') { $stateColumn = 'host_hard_state'; $stateChangeColumn = 'host_last_hard_state_change'; } else { - $stateType = 'soft'; $stateColumn = 'host_state'; $stateChangeColumn = 'host_last_state_change'; } - - $this->addTitleTab('hosts'); + $this->addTitleTab('hosts', $this->translate('Hosts'), $this->translate('List hosts')); $this->setAutorefreshInterval(10); - $query = $this->backend->select()->from('hostStatus', array_merge(array( + $query = $this->backend->select()->from('hoststatus', array_merge(array( 'host_icon_image', + 'host_icon_image_alt', 'host_name', + 'host_display_name', 'host_state' => $stateColumn, 'host_address', 'host_acknowledged', @@ -124,31 +85,17 @@ class Monitoring_ListController extends Controller 'host_last_check', 'host_last_state_change' => $stateChangeColumn, 'host_notifications_enabled', - 'host_unhandled_services', 'host_action_url', 'host_notes_url', - 'host_last_comment', - 'host_last_ack', - 'host_last_downtime', 'host_active_checks_enabled', 'host_passive_checks_enabled', 'host_current_check_attempt', 'host_max_check_attempts' - ), $this->extraColumns())); - + ), $this->addColumns())); $this->filterQuery($query); - - $this->setupSortControl(array( - 'host_last_check' => $this->translate('Last Check'), - 'host_severity' => $this->translate('Severity'), - 'host_name' => $this->translate('Hostname'), - 'host_address' => $this->translate('Address'), - 'host_state' => $this->translate('Current State'), - 'host_state' => $this->translate('Hard State') - )); - $this->view->hosts = $query->paginate(); - - $this->view->stats = $this->backend->select()->from('statusSummary', array( + $this->applyRestriction('monitoring/filter/objects', $query); + $this->view->hosts = $query; + $stats = $this->backend->select()->from('hoststatussummary', array( 'hosts_total', 'hosts_up', 'hosts_down', @@ -158,7 +105,22 @@ class Monitoring_ListController extends Controller 'hosts_unreachable_handled', 'hosts_unreachable_unhandled', 'hosts_pending', - ))->getQuery()->fetchRow(); + )); + $this->applyRestriction('monitoring/filter/objects', $stats); + $this->view->stats = $stats; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->hosts); + $this->setupSortControl(array( + 'host_severity' => $this->translate('Severity'), + 'host_state' => $this->translate('Current State'), + 'host_display_name' => $this->translate('Hostname'), + 'host_address' => $this->translate('Address'), + 'host_last_check' => $this->translate('Last Check') + ), $query); + + $summary = $query->getQuery()->queryServiceProblemSummary(); + $this->applyRestriction('monitoring/filter/objects', $summary); + $this->view->summary = $summary->fetchPairs(); } /** @@ -166,32 +128,25 @@ class Monitoring_ListController extends Controller */ public function servicesAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - // Handle soft and hard states - $stateType = $this->params->shift('stateType', 'soft'); - if ($stateType == 'hard') { + if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') { $stateColumn = 'service_hard_state'; $stateChangeColumn = 'service_last_hard_state_change'; } else { $stateColumn = 'service_state'; $stateChangeColumn = 'service_last_state_change'; - $stateType = 'soft'; } - $this->addTitleTab('services'); + $this->addTitleTab('services', $this->translate('Services'), $this->translate('List services')); $this->view->showHost = true; - if ($host = $this->_getParam('host')) { - if (strpos($host, '*') === false) { - $this->view->showHost = false; - } + if (strpos($this->params->get('host_name', '*'), '*') === false) { + $this->view->showHost = false; } $this->setAutorefreshInterval(10); $columns = array_merge(array( 'host_name', + 'host_display_name', 'host_state', 'host_state_type', 'host_last_state_change', @@ -208,6 +163,7 @@ class Monitoring_ListController extends Controller 'service_attempt', 'service_last_state_change' => $stateChangeColumn, 'service_icon_image', + 'service_icon_image_alt', 'service_is_flapping', 'service_state_type', 'service_handled', @@ -216,56 +172,46 @@ class Monitoring_ListController extends Controller 'service_notifications_enabled', 'service_action_url', 'service_notes_url', - 'service_last_comment', - 'service_last_ack', - 'service_last_downtime', 'service_active_checks_enabled', 'service_passive_checks_enabled', 'current_check_attempt' => 'service_current_check_attempt', 'max_check_attempts' => 'service_max_check_attempts' - ), $this->extraColumns()); - $query = $this->backend->select()->from('serviceStatus', $columns); - + ), $this->addColumns()); + $query = $this->backend->select()->from('servicestatus', $columns); $this->filterQuery($query); + $this->applyRestriction('monitoring/filter/objects', $query); + $this->view->services = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->services); $this->setupSortControl(array( - 'service_last_check' => $this->translate('Last Service Check'), - 'service_severity' => $this->translate('Severity'), + 'service_severity' => $this->translate('Service Severity'), 'service_state' => $this->translate('Current Service State'), - 'service_description' => $this->translate('Service Name'), - 'service_state_type' => $this->translate('Hard State'), + 'service_display_name' => $this->translate('Service Name'), + 'service_last_check' => $this->translate('Last Service Check'), 'host_severity' => $this->translate('Host Severity'), 'host_state' => $this->translate('Current Host State'), - 'host_name' => $this->translate('Host Name'), + 'host_display_name' => $this->translate('Hostname'), 'host_address' => $this->translate('Host Address'), 'host_last_check' => $this->translate('Last Host Check') - )); - $limit = $this->params->get('limit'); - $this->view->limit = $limit; - if ($limit === 0) { - $this->view->services = $query->getQuery()->fetchAll(); - } else { - // TODO: Workaround, paginate should be able to fetch limit from new params - $this->view->services = $query->paginate($this->params->get('limit')); - } + ), $query); - $this->view->stats = $this->backend->select()->from('statusSummary', array( - 'services_total', - 'services_ok', - 'services_problem', - 'services_problem_handled', - 'services_problem_unhandled', + $stats = $this->backend->select()->from('servicestatussummary', array( 'services_critical', - 'services_critical_unhandled', 'services_critical_handled', - 'services_warning', - 'services_warning_unhandled', - 'services_warning_handled', - 'services_unknown', - 'services_unknown_unhandled', - 'services_unknown_handled', + 'services_critical_unhandled', + 'services_ok', 'services_pending', - ))->getQuery()->fetchRow(); - + 'services_total', + 'services_unknown', + 'services_unknown_handled', + 'services_unknown_unhandled', + 'services_warning', + 'services_warning_handled', + 'services_warning_unhandled' + )); + $this->applyRestriction('monitoring/filter/objects', $stats); + $this->view->stats = $stats; } /** @@ -273,16 +219,14 @@ class Monitoring_ListController extends Controller */ public function downtimesAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('downtimes'); + $this->addTitleTab('downtimes', $this->translate('Downtimes'), $this->translate('List downtimes')); $this->setAutorefreshInterval(12); + $query = $this->backend->select()->from('downtime', array( 'id' => 'downtime_internal_id', - 'objecttype' => 'downtime_objecttype', + 'objecttype' => 'object_type', 'comment' => 'downtime_comment', - 'author' => 'downtime_author', + 'author_name' => 'downtime_author_name', 'start' => 'downtime_start', 'scheduled_start' => 'downtime_scheduled_start', 'scheduled_end' => 'downtime_scheduled_end', @@ -292,29 +236,38 @@ class Monitoring_ListController extends Controller 'is_fixed' => 'downtime_is_fixed', 'is_in_effect' => 'downtime_is_in_effect', 'entry_time' => 'downtime_entry_time', - 'host' => 'downtime_host', - 'service' => 'downtime_service', - 'host_state' => 'downtime_host_state', - 'service_state' => 'downtime_service_state' - ))->order('downtime_is_in_effect', 'DESC') - ->order('downtime_scheduled_start', 'DESC'); - + 'host_state', + 'service_state', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + )); $this->filterQuery($query); - $this->setupSortControl(array( - 'downtime_is_in_effect' => $this->translate('Is In Effect'), - 'downtime_host' => $this->translate('Host / Service'), - 'downtime_entry_time' => $this->translate('Entry Time'), - 'downtime_author' => $this->translate('Author'), - 'downtime_start' => $this->translate('Start Time'), - 'downtime_start' => $this->translate('End Time'), - 'downtime_scheduled_start' => $this->translate('Scheduled Start'), - 'downtime_scheduled_end' => $this->translate('Scheduled End'), - 'downtime_duration' => $this->translate('Duration'), - )); + $this->applyRestriction('monitoring/filter/objects', $query); - $this->view->downtimes = $query->paginate(); - $this->view->delDowntimeForm = new DeleteDowntimeCommandForm(); + $this->view->downtimes = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->downtimes); + $this->setupSortControl(array( + 'downtime_is_in_effect' => $this->translate('Is In Effect'), + 'host_display_name' => $this->translate('Host'), + 'service_display_name' => $this->translate('Service'), + 'downtime_entry_time' => $this->translate('Entry Time'), + 'downtime_author' => $this->translate('Author'), + 'downtime_start' => $this->translate('Start Time'), + 'downtime_end' => $this->translate('End Time'), + 'downtime_scheduled_start' => $this->translate('Scheduled Start'), + 'downtime_scheduled_end' => $this->translate('Scheduled End'), + 'downtime_duration' => $this->translate('Duration') + ), $query); + + if ($this->Auth()->hasPermission('monitoring/command/downtime/delete')) { + $this->view->delDowntimeForm = new DeleteDowntimeCommandForm(); + $this->view->delDowntimeForm->handleRequest(); + } } /** @@ -322,55 +275,52 @@ class Monitoring_ListController extends Controller */ public function notificationsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('notifications'); + $this->addTitleTab( + 'notifications', + $this->translate('Notifications'), + $this->translate('List notifications') + ); $this->setAutorefreshInterval(15); + $query = $this->backend->select()->from('notification', array( - 'host', - 'service', + 'host_name', + 'service_description', 'notification_output', - 'notification_contact', + 'notification_contact_name', 'notification_start_time', - 'notification_state' + 'notification_state', + 'host_display_name', + 'service_display_name' )); $this->filterQuery($query); - $this->view->notifications = $query->paginate(); + $this->applyRestriction('monitoring/filter/objects', $query); + $this->view->notifications = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->notifications); $this->setupSortControl(array( 'notification_start_time' => $this->translate('Notification Start') - )); + ), $query); } public function contactsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('contacts'); + $this->addTitleTab('contacts', $this->translate('Contacts'), $this->translate('List contacts')); + $query = $this->backend->select()->from('contact', array( 'contact_name', - 'contact_id', 'contact_alias', 'contact_email', 'contact_pager', 'contact_notify_service_timeperiod', - 'contact_notify_service_recovery', - 'contact_notify_service_warning', - 'contact_notify_service_critical', - 'contact_notify_service_unknown', - 'contact_notify_service_flapping', - 'contact_notify_service_downtime', - 'contact_notify_host_timeperiod', - 'contact_notify_host_recovery', - 'contact_notify_host_down', - 'contact_notify_host_unreachable', - 'contact_notify_host_flapping', - 'contact_notify_host_downtime', + 'contact_notify_host_timeperiod' )); $this->filterQuery($query); - $this->view->contacts = $query->paginate(); + $this->applyRestriction('monitoring/filter/objects', $query); + $this->view->contacts = $query; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->contacts); $this->setupSortControl(array( 'contact_name' => $this->translate('Name'), 'contact_alias' => $this->translate('Alias'), @@ -378,15 +328,12 @@ class Monitoring_ListController extends Controller 'contact_pager' => $this->translate('Pager Address / Number'), 'contact_notify_service_timeperiod' => $this->translate('Service Notification Timeperiod'), 'contact_notify_host_timeperiod' => $this->translate('Host Notification Timeperiod') - )); + ), $query); } public function eventgridAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('eventgrid', t('Event Grid')); + $this->addTitleTab('eventgrid', $this->translate('Event Grid'), $this->translate('Show the Event Grid')); $form = new StatehistoryForm(); $form->setEnctype(Zend_Form::ENCTYPE_URLENCODED); @@ -396,6 +343,7 @@ class Monitoring_ListController extends Controller $form->render(); $this->view->form = $form; + $this->params->remove('view'); $orientation = $this->params->shift('vertical', 0) ? 'vertical' : 'horizontal'; /* $orientationBox = new SelectBox( @@ -416,7 +364,8 @@ class Monitoring_ListController extends Controller $this->params->remove(array('objecttype', 'from', 'to', 'state', 'btn_submit')); $this->view->filter = Filter::fromQuerystring((string) $this->params); $query->applyFilter($this->view->filter); - $this->view->summary = $query->getQuery()->fetchAll(); + $this->applyRestriction('monitoring/filter/objects', $query); + $this->view->summary = $query; $this->view->column = $form->getValue('state'); // $this->view->orientationBox = $orientationBox; $this->view->orientation = $orientation; @@ -424,19 +373,27 @@ class Monitoring_ListController extends Controller public function contactgroupsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('contactgroups'); + $this->addTitleTab( + 'contactgroups', + $this->translate('Contact Groups'), + $this->translate('List contact groups') + ); + $query = $this->backend->select()->from('contactgroup', array( 'contactgroup_name', 'contactgroup_alias', 'contact_name', 'contact_alias', 'contact_email', - 'contact_pager', - ))->order('contactgroup_alias'); + 'contact_pager' + )); $this->filterQuery($query); + $this->applyRestriction('monitoring/filter/objects', $query); + + $this->setupSortControl(array( + 'contactgroup_name' => $this->translate('Contactgroup Name'), + 'contactgroup_alias' => $this->translate('Contactgroup Alias') + ), $query); // Fetch and prepare all contact groups: $contactgroups = $query->getQuery()->fetchAll(); @@ -450,174 +407,183 @@ class Monitoring_ListController extends Controller } $groupData[$c->contactgroup_name]['contacts'][] = $c; } + // TODO: Find a better naming $this->view->groupData = $groupData; } public function commentsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('comments'); + $this->addTitleTab('comments', $this->translate('Comments'), $this->translate('List comments')); $this->setAutorefreshInterval(12); + $query = $this->backend->select()->from('comment', array( 'id' => 'comment_internal_id', - 'objecttype' => 'comment_objecttype', + 'objecttype' => 'object_type', 'comment' => 'comment_data', - 'author' => 'comment_author', + 'author' => 'comment_author_name', 'timestamp' => 'comment_timestamp', 'type' => 'comment_type', 'persistent' => 'comment_is_persistent', 'expiration' => 'comment_expiration', - 'host' => 'comment_host', - 'service' => 'comment_service' + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' )); $this->filterQuery($query); - $this->view->comments = $query->paginate(); + $this->applyRestriction('monitoring/filter/objects', $query); + + $this->view->comments = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->comments); $this->setupSortControl( array( - 'comment_timestamp' => $this->translate('Comment Timestamp'), - 'comment_host' => $this->translate('Host / Service'), - 'comment_type' => $this->translate('Comment Type'), - 'comment_expiration' => $this->translate('Expiration'), - ) + 'comment_timestamp' => $this->translate('Comment Timestamp'), + 'host_display_name' => $this->translate('Host'), + 'service_display_name' => $this->translate('Service'), + 'comment_type' => $this->translate('Comment Type'), + 'comment_expiration' => $this->translate('Expiration') + ), + $query ); - $this->view->delCommentForm = new DeleteCommentCommandForm(); + + if ($this->Auth()->hasPermission('monitoring/command/comment/delete')) { + $this->view->delCommentForm = new DeleteCommentCommandForm(); + $this->view->delCommentForm->handleRequest(); + } } public function servicegroupsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('servicegroups'); + $this->addTitleTab( + 'servicegroups', + $this->translate('Service Groups'), + $this->translate('List service groups') + ); $this->setAutorefreshInterval(12); - $query = $this->backend->select()->from('groupsummary', array( - 'servicegroup', - 'hosts_up', - 'hosts_unreachable_handled', - 'hosts_unreachable_unhandled', - 'hosts_down_handled', - 'hosts_down_unhandled', - 'hosts_pending', - 'services_ok', - 'services_unknown_handled', - 'services_unknown_unhandled', + + $query = $this->backend->select()->from('servicegroupsummary', array( + 'servicegroup_alias', + 'servicegroup_name', 'services_critical_handled', + 'services_critical_last_state_change_handled' => 'services_critical_handled_last_state_change', + 'services_critical_last_state_change_unhandled' => 'services_critical_unhandled_last_state_change', 'services_critical_unhandled', - 'services_warning_handled', - 'services_warning_unhandled', - 'services_pending', + 'services_ok', 'services_ok_last_state_change', + 'services_pending', 'services_pending_last_state_change', - 'services_warning_last_state_change_handled', - 'services_critical_last_state_change_handled', - 'services_unknown_last_state_change_handled', - 'services_warning_last_state_change_unhandled', - 'services_critical_last_state_change_unhandled', - 'services_unknown_last_state_change_unhandled', - 'services_total' + 'services_total', + 'services_unknown_handled', + 'services_unknown_last_state_change_handled' => 'services_unknown_handled_last_state_change', + 'services_unknown_last_state_change_unhandled' => 'services_unknown_unhandled_last_state_change', + 'services_unknown_unhandled', + 'services_warning_handled', + 'services_warning_last_state_change_handled' => 'services_warning_handled_last_state_change', + 'services_warning_last_state_change_unhandled' => 'services_warning_unhandled_last_state_change', + 'services_warning_unhandled' )); $this->filterQuery($query); - $this->view->servicegroups = $query->paginate(); + + $this->applyRestriction('monitoring/filter/objects', $query); + + $this->view->servicegroups = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->servicegroups); $this->setupSortControl(array( - 'services_severity' => $this->translate('Severity'), - 'servicegroup' => $this->translate('Service Group Name'), - 'services_total' => $this->translate('Total Services'), - 'services_ok' => $this->translate('Services OK'), - 'services_unknown' => $this->translate('Services UNKNOWN'), - 'services_critical' => $this->translate('Services CRITICAL'), - 'services_warning' => $this->translate('Services WARNING'), - 'services_pending' => $this->translate('Services PENDING') - )); + 'services_severity' => $this->translate('Severity'), + 'servicegroup_alias' => $this->translate('Service Group Name'), + 'services_total' => $this->translate('Total Services') + ), $query); } public function hostgroupsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('hostgroups'); + $this->addTitleTab('hostgroups', $this->translate('Host Groups'), $this->translate('List host groups')); $this->setAutorefreshInterval(12); - $query = $this->backend->select()->from('groupsummary', array( - 'hostgroup', - 'hosts_up', - 'hosts_unreachable_handled', - 'hosts_unreachable_unhandled', + + $query = $this->backend->select()->from('hostgroupsummary', array( + 'hostgroup_alias', + 'hostgroup_name', 'hosts_down_handled', + 'hosts_down_last_state_change_handled' => 'hosts_down_handled_last_state_change', + 'hosts_down_last_state_change_unhandled' => 'hosts_down_unhandled_last_state_change', 'hosts_down_unhandled', 'hosts_pending', - 'services_ok', - 'services_unknown_handled', - 'services_unknown_unhandled', + 'hosts_pending_last_state_change', + 'hosts_total', + 'hosts_unreachable_handled', + 'hosts_unreachable_last_state_change_handled' => 'hosts_unreachable_handled_last_state_change', + 'hosts_unreachable_last_state_change_unhandled' => 'hosts_unreachable_unhandled_last_state_change', + 'hosts_unreachable_unhandled', + 'hosts_up', + 'hosts_up_last_state_change', 'services_critical_handled', 'services_critical_unhandled', - 'services_warning_handled', - 'services_warning_unhandled', + 'services_ok', 'services_pending', - 'services_ok_last_state_change', - 'services_pending_last_state_change', - 'services_warning_last_state_change_handled', - 'services_critical_last_state_change_handled', - 'services_unknown_last_state_change_handled', - 'services_warning_last_state_change_unhandled', - 'services_critical_last_state_change_unhandled', - 'services_unknown_last_state_change_unhandled', - 'services_total' + 'services_total', + 'services_unknown_handled', + 'services_unknown_unhandled', + 'services_warning_handled', + 'services_warning_unhandled' )); $this->filterQuery($query); - $this->view->hostgroups = $query->paginate(); + + $this->applyRestriction('monitoring/filter/objects', $query); + + $this->view->hostgroups = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->hostgroups); $this->setupSortControl(array( - 'services_severity' => $this->translate('Severity'), - 'hostgroup' => $this->translate('Host Group Name'), - 'services_total' => $this->translate('Total Services'), - 'services_ok' => $this->translate('Services OK'), - 'services_unknown' => $this->translate('Services UNKNOWN'), - 'services_critical' => $this->translate('Services CRITICAL'), - 'services_warning' => $this->translate('Services WARNING'), - 'services_pending' => $this->translate('Services PENDING') - )); + 'hosts_severity' => $this->translate('Severity'), + 'hostgroup_alias' => $this->translate('Host Group Name'), + 'hosts_total' => $this->translate('Total Hosts'), + 'services_total' => $this->translate('Total Services') + ), $query); } public function eventhistoryAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('eventhistory', $this->translate('Event Overview')); + $this->addTitleTab( + 'eventhistory', + $this->translate('Event Overview'), + $this->translate('List event records') + ); - $query = $this->backend->select()->from('eventHistory', array( + $query = $this->backend->select()->from('eventhistory', array( 'host_name', + 'host_display_name', 'service_description', + 'service_display_name', 'object_type', 'timestamp', 'state', - 'attempt', - 'max_attempts', 'output', - 'type', - 'host', - 'service' + 'type' )); - $this->applyFilter($query); + $this->applyRestriction('monitoring/filter/objects', $query); + $this->filterQuery($query); + $this->view->history = $query; + $this->setupLimitControl(); $this->setupSortControl(array( - 'timestamp' => 'Occurence' - )); - $this->view->history = $query->paginate(); + 'timestamp' => $this->translate('Occurence') + ), $query); } public function servicegridAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - $this->addTitleTab('servicegrid', $this->translate('Service Grid')); + $this->addTitleTab('servicegrid', $this->translate('Service Grid'), $this->translate('Show the Service Grid')); $this->setAutorefreshInterval(15); - $query = $this->backend->select()->from('serviceStatus', array( + $problems = (bool) $this->params->shift('problems', 0); + $query = $this->backend->select()->from('servicestatus', array( 'host_name', 'service_description', 'service_state', @@ -625,81 +591,73 @@ class Monitoring_ListController extends Controller 'service_handled' )); $this->filterQuery($query); + $this->applyRestriction('monitoring/filter/objects', $query); $this->setupSortControl(array( 'host_name' => $this->translate('Hostname'), 'service_description' => $this->translate('Service description') - )); - $pivot = $query->pivot('service_description', 'host_name'); + ), $query); + $pivot = $query->pivot( + 'service_description', + 'host_name', + $problems ? Filter::where('service_problem', 1) : null, + $problems ? Filter::where('service_problem', 1) : null + ); $this->view->pivot = $pivot; $this->view->horizontalPaginator = $pivot->paginateXAxis(); $this->view->verticalPaginator = $pivot->paginateYAxis(); } - protected function filterQuery($query) + /** + * Apply filters on a DataView + * + * @param DataView $dataView The DataView to apply filters on + * + * @return DataView $dataView + */ + protected function filterQuery(DataView $dataView) { $editor = Widget::create('filterEditor') - ->setQuery($query) - ->preserveParams('limit', 'sort', 'dir', 'format', 'view', 'backend', 'renderLayout', 'stateType', 'addColumns') - ->ignoreParams('page', 'objecttype', 'from', 'to', 'btn_submit', 'icon') + ->setQuery($dataView) + ->preserveParams( + 'limit', 'sort', 'dir', 'format', 'view', 'backend', + 'stateType', 'addColumns', '_dev', 'problems' + ) + ->ignoreParams('page') + ->setSearchColumns($dataView->getSearchColumns()) ->handleRequest($this->getRequest()); - $query->applyFilter($editor->getFilter()); + $dataView->applyFilter($editor->getFilter()); - $this->view->filterEditor = $editor; + $this->setupFilterControl($editor); $this->view->filter = $editor->getFilter(); - if ($sort = $this->params->get('sort')) { - $query->order($sort, $this->params->get('dir')); - } - $this->applyRestrictions($query); - $this->handleFormatRequest($query); - return $query; + $this->handleFormatRequest($dataView); + return $dataView; } /** - * Apply current user's `monitoring/filter' restrictions on the given data view + * Get columns to be added from URL parameter 'addColumns' + * and assign to $this->view->addColumns (as array) + * + * @return array */ - protected function applyRestrictions($query) - { - foreach ($this->getRestrictions('monitoring/filter') as $restriction) { - // TODO: $query->applyFilter(Filter::fromQueryString()); - } - return $query; - } - - protected function extraColumns() + protected function addColumns() { $columns = preg_split( '~,~', - $this->params->shift('addcolumns', ''), + $this->params->shift('addColumns', ''), -1, PREG_SPLIT_NO_EMPTY ); - $this->view->extraColumns = $columns; + $this->view->addColumns = $columns; return $columns; } - /** - * Create a sort control box at the 'sortControl' view parameter - * - * @param array $columns An array containing the sort columns, with the - * submit value as the key and the value as the label - */ - private function setupSortControl(array $columns) + protected function addTitleTab($action, $title, $tip) { - $this->view->sortControl = new SortBox( - $this->getRequest()->getActionName(), - $columns - ); - $this->view->sortControl->applyRequest($this->getRequest()); - } - - protected function addTitleTab($action, $title = false) - { - $title = $title ?: ucfirst($action); $this->getTabs()->add($action, array( - 'title' => $title, - // 'url' => Url::fromPath('monitoring/list/' . $action) - 'url' => $this->url + 'title' => $tip, + 'label' => $title, + 'url' => Url::fromRequest() ))->activate($action); $this->view->title = $title; } @@ -711,15 +669,6 @@ class Monitoring_ListController extends Controller */ private function createTabs() { - $tabs = $this->getTabs(); - if (in_array($this->_request->getActionName(), array( - 'hosts', - 'services', - 'eventhistory', - 'eventgrid', - 'notifications' - ))) { - $tabs->extend(new OutputFormat())->extend(new DashboardAction()); - } + $this->getTabs()->extend(new OutputFormat())->extend(new DashboardAction()); } } diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php deleted file mode 100644 index 52d927ffb..000000000 --- a/modules/monitoring/application/controllers/MultiController.php +++ /dev/null @@ -1,282 +0,0 @@ -backend->select()->from( - 'hostStatus', - array( - 'host_name', - 'host_in_downtime', - 'host_passive_checks_enabled', - 'host_obsessing', - 'host_state', - 'host_notifications_enabled', - 'host_event_handler_enabled', - 'host_flap_detection_enabled', - 'host_active_checks_enabled', - // columns intended for filter-request - 'host_problem', - 'host_handled' - ) - )->getQuery(); - $this->applyQueryFilter($query); - $hosts = $query->fetchAll(); - - $comments = $this->backend->select()->from('comment', array( - 'comment_internal_id', - 'comment_host', - )); - $this->applyQueryFilter($comments); - $uniqueComments = array_keys($this->getUniqueValues($comments->getQuery()->fetchAll(), 'comment_internal_id')); - - // Populate view - $this->view->objects = $this->view->hosts = $hosts; - $this->view->problems = $this->getProblems($hosts); - $this->view->comments = $uniqueComments; - $this->view->hostnames = $this->getProperties($hosts, 'host_name'); - $this->view->downtimes = $this->getDowntimes($hosts); - $this->view->errors = $errors; - $this->view->states = $this->countStates($hosts, 'host', 'host_name'); - $this->view->pie = $this->createPie( - $this->view->states, - $this->view->getHelper('MonitoringState')->getHostStateColors(), - mt('monitoring', 'Host State') - ); - - // Handle configuration changes - $this->handleConfigurationForm(array( - 'host_passive_checks_enabled' => $this->translate('Passive Checks'), - 'host_active_checks_enabled' => $this->translate('Active Checks'), - 'host_notifications_enabled' => $this->translate('Notifications'), - 'host_event_handler_enabled' => $this->translate('Event Handler'), - 'host_flap_detection_enabled' => $this->translate('Flap Detection'), - 'host_obsessing' => $this->translate('Obsessing') - )); - } - - public function serviceAction() - { - $errors = array(); - $query = $this->backend->select()->from('serviceStatus', array( - 'host_name', - 'host_state', - 'service_description', - 'service_handled', - 'service_state', - 'service_in_downtime', - 'service_passive_checks_enabled', - 'service_notifications_enabled', - 'service_event_handler_enabled', - 'service_flap_detection_enabled', - 'service_active_checks_enabled', - 'service_obsessing', - // also accept all filter-requests from ListView - 'service_problem', - 'service_severity', - 'service_last_check', - 'service_state_type', - 'host_severity', - 'host_address', - 'host_last_check' - )); - - $this->applyQueryFilter($query); - $services = $query->getQuery()->fetchAll(); - - $comments = $this->backend->select()->from('comment', array( - 'comment_internal_id', - 'comment_host', - 'comment_service' - )); - $this->applyQueryFilter($comments); - $uniqueComments = array_keys($this->getUniqueValues($comments->getQuery()->fetchAll(), 'comment_internal_id')); - - // populate the view - $this->view->objects = $this->view->services = $services; - $this->view->problems = $this->getProblems($services); - $this->view->comments = $uniqueComments; - $this->view->hostnames = $this->getProperties($services, 'host_name'); - $this->view->servicenames = $this->getProperties($services, 'service_description'); - $this->view->downtimes = $this->getDowntimes($services); - $this->view->service_states = $this->countStates($services, 'service'); - $this->view->host_states = $this->countStates($services, 'host', 'host_name'); - $this->view->service_pie = $this->createPie( - $this->view->service_states, - $this->view->getHelper('MonitoringState')->getServiceStateColors(), - mt('monitoring', 'Service State') - ); - $this->view->host_pie = $this->createPie( - $this->view->host_states, - $this->view->getHelper('MonitoringState')->getHostStateColors(), - mt('monitoring', 'Host State') - ); - $this->view->errors = $errors; - - $this->handleConfigurationForm(array( - 'service_passive_checks_enabled' => $this->translate('Passive Checks'), - 'service_active_checks_enabled' => $this->translate('Active Checks'), - 'service_notifications_enabled' => $this->translate('Notifications'), - 'service_event_handler_enabled' => $this->translate('Event Handler'), - 'service_flap_detection_enabled' => $this->translate('Flap Detection'), - 'service_obsessing' => $this->translate('Obsessing'), - )); - } - - protected function applyQueryFilter($query) - { - $params = clone $this->params; - $modifyFilter = $params->shift('modifyFilter'); - - $filter = Filter::fromQueryString((string) $params); - if ($modifyFilter) { - $this->view->filterWidget = Widget::create('filterEditor', array( - 'filter' => $filter, - 'query' => $query - )); - } - $this->view->filter = $filter; - $query->applyFilter($filter); - return $query; - } - - /** - * Create an array with all unique values as keys. - * - * @param array $values The array containing the objects - * @param $key The key to access - * - * @return array - */ - private function getUniqueValues($values, $key) - { - $unique = array(); - foreach ($values as $value) { - if (is_array($value)) { - $unique[$value[$key]] = $value[$key]; - } else { - $unique[$value->$key] = $value->$key; - } - } - return $unique; - } - - /** - * Get the numbers of problems of the given objects - * - * @param $objects The objects containing the problems - * - * @return int The problem count - */ - private function getProblems($objects) - { - $problems = 0; - foreach ($objects as $object) { - if (property_exists($object, 'host_unhandled_service_count')) { - $problems += $object->host_unhandled_service_count; - } else if ( - property_exists($object, 'service_handled') && - !$object->service_handled && - $object->service_state > 0 - ) { - $problems++; - } - } - return $problems; - } - - private function countStates($objects, $type = 'host', $unique = null) - { - $known = array(); - if ($type === 'host') { - $states = array_fill_keys($this->view->getHelper('MonitoringState')->getHostStateNames(), 0); - } else { - $states = array_fill_keys($this->view->getHelper('MonitoringState')->getServiceStateNames(), 0); - } - foreach ($objects as $object) { - if (isset($unique)) { - if (array_key_exists($object->$unique, $known)) { - continue; - } - $known[$object->$unique] = true; - } - $states[$this->view->monitoringState($object, $type)]++; - } - return $states; - } - - private function createPie($states, $colors, $title) - { - $chart = new InlinePie(array_values($states), $title, $colors); - $chart->setLabel(array_keys($states))->setHeight(100)->setWidth(100); - $chart->setTitle($title); - return $chart; - } - - - private function getComments($objects) - { - $unique = array(); - foreach ($objects as $object) { - $unique = array_merge($unique, $this->getUniqueValues($object->comments, 'comment_internal_id')); - } - return array_keys($unique); - } - - private function getProperties($objects, $property) - { - $objectnames = array(); - foreach ($objects as $object) { - $objectnames[] = $object->$property; - } - return $objectnames; - } - - private function getDowntimes($objects) - { - $downtimes = array(); - foreach ($objects as $object) - { - if ( - (property_exists($object, 'host_in_downtime') && $object->host_in_downtime) || - (property_exists($object, 'service_in_downtime') && $object->service_in_downtime) - ) { - $downtimes[] = true; - } - } - return $downtimes; - } - - - /** - * Handle the form to edit configuration flags. - * - * @param $flags array The used flags. - */ - private function handleConfigurationForm(array $flags) - { - $this->view->form = $form = new MultiCommandFlagForm($flags); - $this->view->formElements = $form->buildCheckboxes(); - $form->setRequest($this->_request); - if ($form->isSubmittedAndValid()) { - // TODO: Handle commands - $changed = $form->getChangedValues(); - } - if ($this->_request->isPost() === false) { - $this->view->form->initFromItems($this->view->objects); - } - } -} diff --git a/modules/monitoring/application/controllers/ProcessController.php b/modules/monitoring/application/controllers/ProcessController.php index 62334d7e8..2e52733bd 100644 --- a/modules/monitoring/application/controllers/ProcessController.php +++ b/modules/monitoring/application/controllers/ProcessController.php @@ -1,7 +1,7 @@ add( 'info', array( - 'title' => $this->translate('Monitoring Health'), + 'title' => $this->translate( + 'Show information about the current monitoring instance\'s process' + . ' and it\'s performance as well as available features' + ), + 'label' => $this->translate('Monitoring Health'), 'url' =>'monitoring/process/info' ) - ); + )->extend(new DashboardAction()); } /** @@ -45,8 +49,10 @@ class Monitoring_ProcessController extends Controller array( 'is_currently_running', 'process_id', + 'endpoint_name', 'program_start_time', 'status_update_time', + 'program_version', 'last_command_check', 'last_log_rotation', 'global_service_event_handler', @@ -64,8 +70,9 @@ class Monitoring_ProcessController extends Controller 'process_performance_data' ) ) - ->getQuery() - ->fetchRow(); + ->getQuery(); + $this->handleFormatRequest($programStatus); + $programStatus = $programStatus->fetchRow(); if ($programStatus === false) { return $this->render('not-running', true, null); } @@ -91,6 +98,7 @@ class Monitoring_ProcessController extends Controller */ public function disableNotificationsAction() { + $this->assertPermission('monitoring/command/feature/instance'); $this->view->title = $this->translate('Disable Notifications'); $programStatus = $this->backend ->select() diff --git a/modules/monitoring/application/controllers/ServiceController.php b/modules/monitoring/application/controllers/ServiceController.php index c6d0e4e2f..cf7c4807c 100644 --- a/modules/monitoring/application/controllers/ServiceController.php +++ b/modules/monitoring/application/controllers/ServiceController.php @@ -1,13 +1,15 @@ backend, $this->params->get('host'), $this->params->get('service')); + $service = new Service( + $this->backend, $this->params->getRequired('host'), $this->params->getRequired('service') + ); + + $this->applyRestriction('monitoring/filter/objects', $service); + if ($service->fetch() === false) { - throw new Zend_Controller_Action_Exception($this->translate('Service not found')); + $this->httpNotFound($this->translate('Service not found')); } $this->object = $service; $this->createTabs(); + $this->getTabs()->activate('service'); + } + + /** + * Get service actions from hook + * + * @return array + */ + protected function getServiceActions() + { + $urls = array(); + + foreach (Hook::all('Monitoring\\ServiceActions') as $hook) { + foreach ($hook->getActionsForService($this->object) as $id => $url) { + $urls[$id] = $url; + } + } + + return $urls; } /** @@ -37,17 +61,21 @@ class Monitoring_ServiceController extends MonitoredObjectController */ public function showAction() { - $this->getTabs()->activate('service'); + $this->view->actions = $this->getServiceActions(); parent::showAction(); } + /** * Acknowledge a service problem */ public function acknowledgeProblemAction() { - $this->view->title = $this->translate('Acknowledge Service Problem'); - $this->handleCommandForm(new AcknowledgeProblemCommandForm()); + $this->assertPermission('monitoring/command/acknowledge-problem'); + + $form = new AcknowledgeProblemCommandForm(); + $form->setTitle($this->translate('Acknowledge Service Problem')); + $this->handleCommandForm($form); } /** @@ -55,8 +83,11 @@ class Monitoring_ServiceController extends MonitoredObjectController */ public function addCommentAction() { - $this->view->title = $this->translate('Add Service Comment'); - $this->handleCommandForm(new AddCommentCommandForm()); + $this->assertPermission('monitoring/command/comment/add'); + + $form = new AddCommentCommandForm(); + $form->setTitle($this->translate('Add Service Comment')); + $this->handleCommandForm($form); } /** @@ -64,8 +95,11 @@ class Monitoring_ServiceController extends MonitoredObjectController */ public function rescheduleCheckAction() { - $this->view->title = $this->translate('Reschedule Service Check'); - $this->handleCommandForm(new ScheduleServiceCheckCommandForm()); + $this->assertPermission('monitoring/command/schedule-check'); + + $form = new ScheduleServiceCheckCommandForm(); + $form->setTitle($this->translate('Reschedule Service Check')); + $this->handleCommandForm($form); } /** @@ -73,7 +107,35 @@ class Monitoring_ServiceController extends MonitoredObjectController */ public function scheduleDowntimeAction() { - $this->view->title = $this->translate('Schedule Service Downtime'); - $this->handleCommandForm(new ScheduleServiceDowntimeCommandForm()); + $this->assertPermission('monitoring/command/downtime/schedule'); + + $form = new ScheduleServiceDowntimeCommandForm(); + $form->setTitle($this->translate('Schedule Service Downtime')); + $this->handleCommandForm($form); + } + + /** + * Submit a passive service check result + */ + public function processCheckResultAction() + { + $this->assertPermission('monitoring/command/process-check-result'); + + $form = new ProcessCheckResultCommandForm(); + $form->setBackend($this->backend); + $form->setTitle($this->translate('Submit Passive Service Check Result')); + $this->handleCommandForm($form); + } + + /** + * Send a custom notification for a service + */ + public function sendCustomNotificationAction() + { + $this->assertPermission('monitoring/command/send-custom-notification'); + + $form = new SendCustomNotificationCommandForm(); + $form->setTitle($this->translate('Send Custom Service Notification')); + $this->handleCommandForm($form); } } diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php index 56813180d..d67880565 100644 --- a/modules/monitoring/application/controllers/ServicesController.php +++ b/modules/monitoring/application/controllers/ServicesController.php @@ -1,20 +1,21 @@ backend); - $serviceList->setFilter(Filter::fromQueryString((string) $this->params->without('service_problem', 'service_handled'))); + $this->applyRestriction('monitoring/filter/objects', $serviceList); + $serviceList->addFilter(Filter::fromQueryString( + (string) $this->params->without(array('service_problem', 'service_handled', 'view')) + )); $this->serviceList = $serviceList; + $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/services'); + $this->getTabs()->add( + 'show', + array( + 'title' => sprintf( + $this->translate('Show summarized information for %u services'), + count($this->serviceList) + ), + 'label' => $this->translate('Services') . sprintf(' (%d)', count($this->serviceList)), + 'url' => Url::fromRequest(), + 'icon' => 'services' + ) + )->extend(new DashboardAction())->activate('show'); } protected function handleCommandForm(ObjectsCommandForm $form) { + $this->serviceList->setColumns(array( + 'host_icon_image', + 'host_icon_image_alt', + 'host_name', + 'host_address', + 'host_output', + 'host_state', + 'host_problem', + 'host_handled', + 'service_icon_image', + 'service_icon_image_alt', + 'service_description', + 'service_state', + 'service_problem', + 'service_handled', + 'service_acknowledged', + 'service_in_downtime', + 'service_is_flapping', + 'service_output', + 'service_notifications_enabled', + 'service_active_checks_enabled', + 'service_passive_checks_enabled' + )); + $form ->setObjects($this->serviceList) ->setRedirectUrl(Url::fromPath('monitoring/services/show')->setParams($this->params)) ->handleRequest(); + $this->view->form = $form; - $this->_helper->viewRenderer('partials/command-form', null, true); + $this->view->objects = $this->serviceList; + $this->view->stats = $this->serviceList->getServiceStateSummary(); + $this->view->serviceStates = true; + $this->_helper->viewRenderer('partials/command/objects-command-form', null, true); return $form; } public function showAction() { - $this->getTabs()->add( - 'show', - array( - 'title' => mt('monitoring', 'Services'), - 'url' => Url::fromRequest() - ) - )->activate('show'); $this->setAutorefreshInterval(15); $checkNowForm = new CheckNowCommandForm(); $checkNowForm @@ -57,55 +95,34 @@ class Monitoring_ServicesController extends Controller ->handleRequest(); $this->view->checkNowForm = $checkNowForm; $this->serviceList->setColumns(array( + 'host_icon_image', + 'host_icon_image_alt', 'host_name', + 'host_address', + 'host_output', 'host_state', + 'host_problem', + 'host_handled', + 'service_icon_image', + 'service_icon_image_alt', + 'service_output', 'service_description', 'service_state', 'service_problem', 'service_handled', 'service_acknowledged', - 'service_in_downtime'/*, - 'service_passive_checks_enabled', + 'service_in_downtime', + 'service_is_flapping', 'service_notifications_enabled', + 'service_active_checks_enabled', + 'service_passive_checks_enabled' + /* 'service_event_handler_enabled', 'service_flap_detection_enabled', - 'service_active_checks_enabled', 'service_obsessing'*/ )); - $unhandledObjects = array(); - $acknowledgedObjects = array(); - $objectsInDowntime = array(); - $serviceStates = array( - Service::getStateText(Service::STATE_OK) => 0, - Service::getStateText(Service::STATE_WARNING) => 0, - Service::getStateText(Service::STATE_CRITICAL) => 0, - Service::getStateText(Service::STATE_UNKNOWN) => 0, - Service::getStateText(Service::STATE_PENDING) => 0 - ); - $knownHostStates = array(); - $hostStates = array( - Host::getStateText(Host::STATE_UP) => 0, - Host::getStateText(Host::STATE_DOWN) => 0, - Host::getStateText(Host::STATE_UNREACHABLE) => 0, - Host::getStateText(Host::STATE_PENDING) => 0, - ); - foreach ($this->serviceList as $service) { - /** @var Service $service */ - if ((bool) $service->problem === true && (bool) $service->handled === false) { - $unhandledObjects[] = $service; - } - if ((bool) $service->acknowledged === true) { - $acknowledgedObjects[] = $service; - } - if ((bool) $service->in_downtime === true) { - $objectsInDowntime[] = $service; - } - ++$serviceStates[$service::getStateText($service->state)]; - if (! isset($knownHostStates[$service->getHost()->getName()])) { - $knownHostStates[$service->getHost()->getName()] = true; - ++$hostStates[$service->getHost()->getStateText($service->host_state)]; - } - } + + $acknowledgedObjects = $this->serviceList->getAcknowledgedObjects(); if (! empty($acknowledgedObjects)) { $removeAckForm = new RemoveAcknowledgementCommandForm(); $removeAckForm @@ -113,55 +130,80 @@ class Monitoring_ServicesController extends Controller ->handleRequest(); $this->view->removeAckForm = $removeAckForm; } + $this->setAutorefreshInterval(15); - $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/services'); $this->view->rescheduleAllLink = Url::fromRequest()->setPath('monitoring/services/reschedule-check'); $this->view->downtimeAllLink = Url::fromRequest()->setPath('monitoring/services/schedule-downtime'); - $this->view->hostStates = $hostStates; - $this->view->serviceStates = $serviceStates; - $this->view->objects = $this->serviceList; - $this->view->unhandledObjects = $unhandledObjects; - $this->view->acknowledgeUnhandledLink = Url::fromRequest() - ->setPath('monitoring/services/acknowledge-problem') - ->addParams(array('service_problem' => 1, 'service_handled' => 0)); - $this->view->downtimeUnhandledLink = Url::fromRequest() - ->setPath('monitoring/services/schedule-downtime') - ->addParams(array('service_problem' => 1, 'service_handled' => 0)); - $this->view->acknowledgedObjects = $acknowledgedObjects; - $this->view->objectsInDowntime = $objectsInDowntime; - $this->view->inDowntimeLink = Url::fromRequest() - ->setPath('monitoring/list/downtimes'); - $this->view->havingCommentsLink = Url::fromRequest() - ->setPath('monitoring/list/comments'); - $this->view->serviceStatesPieChart = $this->createPieChart( - $serviceStates, - $this->translate('Service State'), - array('#44bb77', '#FFCC66', '#FF5566', '#E066FF', '#77AAFF') + $this->view->processCheckResultAllLink = Url::fromRequest()->setPath( + 'monitoring/services/process-check-result' ); - $this->view->hostStatesPieChart = $this->createPieChart( - $hostStates, - $this->translate('Host State'), - array('#44bb77', '#FF5566', '#E066FF', '#77AAFF') + $this->view->addCommentLink = Url::fromRequest()->setPath('monitoring/services/add-comment'); + $this->view->deleteCommentLink = Url::fromRequest()->setPath('monitoring/services/delete-comment'); + $this->view->stats = $this->serviceList->getServiceStateSummary(); + $this->view->objects = $this->serviceList; + $this->view->unhandledObjects = $this->serviceList->getUnhandledObjects(); + $this->view->problemObjects = $this->serviceList->getProblemObjects(); + $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/services/schedule-downtime') + ->setQueryString($this->serviceList->getUnhandledObjects()->objectsFilter()->toQueryString()); + $this->view->downtimeLink = Url::fromPath('monitoring/services/schedule-downtime') + ->setQueryString($this->serviceList->getProblemObjects()->objectsFilter()->toQueryString()); + $this->view->acknowledgedObjects = $acknowledgedObjects; + $this->view->acknowledgeLink = Url::fromPath('monitoring/services/acknowledge-problem') + ->setQueryString($this->serviceList->getUnacknowledgedObjects()->objectsFilter()->toQueryString()); + $this->view->unacknowledgedObjects = $this->serviceList->getUnacknowledgedObjects(); + $this->view->objectsInDowntime = $this->serviceList->getObjectsInDowntime(); + $this->view->inDowntimeLink = Url::fromPath('monitoring/list/services') + ->setQueryString($this->serviceList->getObjectsInDowntime() + ->objectsFilter(array('host' => 'host_name', 'service' => 'service_description'))->toQueryString()); + $this->view->showDowntimesLink = Url::fromPath('monitoring/downtimes/show') + ->setQueryString( + $this->serviceList->getObjectsInDowntime() + ->objectsFilter()->toQueryString() + ); + $this->view->commentsLink = Url::fromRequest() + ->setPath('monitoring/list/comments'); + $this->view->baseFilter = $this->serviceList->getFilter(); + $this->view->sendCustomNotificationLink = Url::fromRequest()->setPath( + 'monitoring/services/send-custom-notification' ); } - protected function createPieChart(array $states, $title, array $colors) + /** + * Add a service comment + */ + public function addCommentAction() { - $chart = new InlinePie(array_values($states), $title, $colors); - return $chart - ->setLabel(array_map('strtoupper', array_keys($states))) - ->setHeight(100) - ->setWidth(100) - ->setTitle($title); + $this->assertPermission('monitoring/command/comment/add'); + + $form = new AddCommentCommandForm(); + $form->setTitle($this->translate('Add Service Comments')); + $this->handleCommandForm($form); } + + /** + * Delete a comment + */ + public function deleteCommentAction() + { + $this->assertPermission('monitoring/command/comment/delete'); + + $form = new DeleteCommentCommandForm(); + $form->setTitle($this->translate('Delete Service Comments')); + $this->handleCommandForm($form); + } + + /** * Acknowledge service problems */ public function acknowledgeProblemAction() { - $this->view->title = $this->translate('Acknowledge Service Problems'); - $this->handleCommandForm(new AcknowledgeProblemCommandForm()); + $this->assertPermission('monitoring/command/acknowledge-problem'); + + $form = new AcknowledgeProblemCommandForm(); + $form->setTitle($this->translate('Acknowledge Service Problems')); + $this->handleCommandForm($form); } /** @@ -169,8 +211,11 @@ class Monitoring_ServicesController extends Controller */ public function rescheduleCheckAction() { - $this->view->title = $this->translate('Reschedule Service Checks'); - $this->handleCommandForm(new ScheduleServiceCheckCommandForm()); + $this->assertPermission('monitoring/command/schedule-check'); + + $form = new ScheduleServiceCheckCommandForm(); + $form->setTitle($this->translate('Reschedule Service Checks')); + $this->handleCommandForm($form); } /** @@ -178,7 +223,35 @@ class Monitoring_ServicesController extends Controller */ public function scheduleDowntimeAction() { - $this->view->title = $this->translate('Schedule Service Downtimes'); - $this->handleCommandForm(new ScheduleServiceDowntimeCommandForm()); + $this->assertPermission('monitoring/command/downtime/schedule'); + + $form = new ScheduleServiceDowntimeCommandForm(); + $form->setTitle($this->translate('Schedule Service Downtimes')); + $this->handleCommandForm($form); + } + + /** + * Submit passive service check results + */ + public function processCheckResultAction() + { + $this->assertPermission('monitoring/command/process-check-result'); + + $form = new ProcessCheckResultCommandForm(); + $form->setBackend($this->backend); + $form->setTitle($this->translate('Submit Passive Service Check Results')); + $this->handleCommandForm($form); + } + + /** + * Send a custom notification for services + */ + public function sendCustomNotificationAction() + { + $this->assertPermission('monitoring/command/send-custom-notification'); + + $form = new SendCustomNotificationCommandForm(); + $form->setTitle($this->translate('Send Custom Service Notification')); + $this->handleCommandForm($form); } } diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index e42a7e046..cfed8bd0d 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -1,18 +1,9 @@ view->object = MonitoredObject::fromParams($this->params); - if ($this->view->object && $this->view->object->fetch() === false) { - throw new Zend_Controller_Action_Exception($this->translate('Host or service not found')); - } - - if (Hook::has('ticket')) { - $this->view->tickets = Hook::first('ticket'); - } - if (Hook::has('grapher')) { - $this->grapher = Hook::first('grapher'); - if ($this->grapher && ! $this->grapher->hasPreviews()) { - $this->grapher = null; - } - } - - $this->createTabs(); - } - /** * @deprecated */ @@ -70,58 +33,25 @@ class Monitoring_ShowController extends Controller $this->redirectNow(Url::fromRequest()->setPath('monitoring/host/show')); } + /** + * @deprecated + */ public function historyAction() { - $this->getTabs()->activate('history'); - //$this->view->object->populate(); - $this->view->object->fetchEventHistory(); - $this->view->history = $this->view->object->eventhistory->paginate($this->params->get('limit', 50)); - $this->handleFormatRequest($this->view->object->eventhistory); - $this->fetchHostStats(); - } + if ($this->params->has('service')) { + $this->redirectNow(Url::fromRequest()->setPath('monitoring/service/history')); + } - public function servicesAction() - { - $this->setAutorefreshInterval(15); - $this->getTabs()->activate('services'); - $this->_setParam('service', ''); - // TODO: This used to be a hack and still is. Modifying query string here. - $_SERVER['QUERY_STRING'] = (string) $this->params->without('service')->set('limit', ''); - $this->view->services = $this->view->action('services', 'list', 'monitoring', array( - 'view' => 'compact', - 'sort' => 'service_description', - )); - $this->fetchHostStats(); - } - - protected function fetchHostStats() - { - $this->view->stats = $this->backend->select()->from('statusSummary', array( - 'services_total', - 'services_ok', - 'services_problem', - 'services_problem_handled', - 'services_problem_unhandled', - 'services_critical', - 'services_critical_unhandled', - 'services_critical_handled', - 'services_warning', - 'services_warning_unhandled', - 'services_warning_handled', - 'services_unknown', - 'services_unknown_unhandled', - 'services_unknown_handled', - 'services_pending', - ))->where('service_host_name', $this->params->get('host'))->getQuery()->fetchRow(); + $this->redirectNow(Url::fromRequest()->setPath('monitoring/host/history')); } public function contactAction() { - $contactName = $this->getParam('contact'); + $contactName = $this->getParam('contact_name'); if (! $contactName) { throw new Zend_Controller_Action_Exception( - $this->translate('The parameter `contact\' is required'), + $this->translate('The parameter `contact_name\' is required'), 404 ); } @@ -147,9 +77,8 @@ class Monitoring_ShowController extends Controller 'contact_notify_host_flapping', 'contact_notify_host_downtime', )); - $query->where('contact_name', $contactName); - + $this->applyRestriction('monitoring/filter/objects', $query); $contact = $query->getQuery()->fetchRow(); if ($contact) { @@ -158,88 +87,27 @@ class Monitoring_ShowController extends Controller 'command_name' ))->where('contact_id', $contact->contact_id); - $this->view->commands = $commands->paginate(); + $this->view->commands = $commands; $notifications = $this->backend->select()->from('notification', array( - 'host', - 'service', + 'host_name', + 'service_description', 'notification_output', - 'notification_contact', + 'notification_contact_name', 'notification_start_time', - 'notification_state' - ))->order('notification_start_time'); + 'notification_state', + 'host_display_name', + 'service_display_name' + )); $notifications->where('contact_object_id', $contact->contact_object_id); - - $this->view->compact = true; - $this->view->notifications = $notifications->paginate(); + $this->applyRestriction('monitoring/filter/objects', $notifications); + $this->view->notifications = $notifications; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->notifications); } $this->view->contact = $contact; $this->view->contactName = $contactName; } - - /** - * Creating tabs for this controller - * @return Tabs - */ - protected function createTabs() - { - if (($object = $this->view->object) === null) { - return; - } - if ($object->getType() === $object::TYPE_HOST) { - $params = array( - 'host' => $object->getName() - ); - } else { - $params = array( - 'host' => $object->getHost()->getName(), - 'service' => $object->getName() - ); - } - $tabs = $this->getTabs(); - $tabs->add( - 'host', - array( - 'title' => 'Host', - 'icon' => 'host', - 'url' => 'monitoring/show/host', - 'urlParams' => $params, - ) - ); - if (isset($params['service'])) { - $tabs->add( - 'service', - array( - 'title' => 'Service', - 'icon' => 'service', - 'url' => 'monitoring/show/service', - 'urlParams' => $params, - ) - ); - } - $tabs->add( - 'services', - array( - 'title' => 'Services', - 'icon' => 'services', - 'url' => 'monitoring/show/services', - 'urlParams' => $params, - ) - ); - if ($this->backend->hasQuery('eventHistory')) { - $tabs->add( - 'history', - array( - 'title' => 'History', - 'icon' => 'rewind', - 'url' => 'monitoring/show/history', - 'urlParams' => $params, - ) - ); - } - $tabs->extend(new OutputFormat()) - ->extend(new DashboardAction()); - } } diff --git a/modules/monitoring/application/controllers/TacticalController.php b/modules/monitoring/application/controllers/TacticalController.php index a48e053ca..fce4664f0 100644 --- a/modules/monitoring/application/controllers/TacticalController.php +++ b/modules/monitoring/application/controllers/TacticalController.php @@ -1,24 +1,28 @@ setAutorefreshInterval(15); $this->getTabs()->add( 'tactical_overview', array( - 'title' => $this->translate('Tactical Overview'), + 'title' => $this->translate( + 'Show an overview of all hosts and services, their current' + . ' states and monitoring feature utilisation' + ), + 'label' => $this->translate('Tactical Overview'), 'url' => Url::fromRequest() ) - )->activate('tactical_overview'); - - $this->view->statusSummary = $this->backend->select()->from( - 'statusSummary', + )->extend(new DashboardAction())->activate('tactical_overview'); + $stats = $this->backend->select()->from( + 'statussummary', array( 'hosts_up', 'hosts_pending', @@ -75,6 +79,8 @@ class Monitoring_TacticalController extends MonitoringController 'hosts_flapping', 'services_flapping' ) - )->getQuery()->fetchRow(); + ); + $this->applyRestriction('monitoring/filter/objects', $stats); + $this->view->statusSummary = $stats->fetchRow(); } } diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php index e21c580ad..aa1e20b95 100644 --- a/modules/monitoring/application/controllers/TimelineController.php +++ b/modules/monitoring/application/controllers/TimelineController.php @@ -1,32 +1,27 @@ getTabs()->add($action, array( - 'title' => $title, - 'url' => Url::fromRequest() - ))->activate($action); - $this->view->title = $title; - } - public function indexAction() { - $this->addTitleTab('index', t('Timeline')); + $this->getTabs()->add( + 'timeline', + array( + 'title' => $this->translate('Show the number of historical event records grouped by time and type'), + 'label' => $this->translate('Timeline'), + 'url' => Url::fromRequest() + ) + )->extend(new DashboardAction())->activate('timeline'); + $this->view->title = $this->translate('Timeline'); // TODO: filter for hard_states (precedence adjustments necessary!) $this->setupIntervalBox(); @@ -35,10 +30,13 @@ class Monitoring_TimelineController extends Controller $detailUrl = Url::fromPath('monitoring/list/eventhistory'); $timeline = new TimeLine( - $this->backend->select()->from('eventHistory', - array( - 'name' => 'type', - 'time' => 'timestamp' + $this->applyRestriction( + 'monitoring/filter/objects', + $this->backend->select()->from('eventhistory', + array( + 'name' => 'type', + 'time' => 'timestamp' + ) ) ), array( @@ -144,7 +142,7 @@ class Monitoring_TimelineController extends Controller case '1d': return $this->getDateFormat(); case '1w': - return '\W\e\ek #W\\of Y'; + return '\W\e\ek W\\of Y'; case '1m': return 'F Y'; case '1y': @@ -236,7 +234,7 @@ class Monitoring_TimelineController extends Controller */ private function buildTimeRanges() { - $startTime = DateTimeFactory::create(); + $startTime = new DateTime(); $startParam = $this->_request->getParam('start'); $startTimestamp = is_numeric($startParam) ? intval($startParam) : strtotime($startParam); if ($startTimestamp !== false) { @@ -273,8 +271,7 @@ class Monitoring_TimelineController extends Controller */ private function getTimeFormat() { - // TODO(mh): Missing localized format (#6077) - return 'g:i A'; + return 'H:i'; } /** @@ -284,7 +281,6 @@ class Monitoring_TimelineController extends Controller */ private function getDateFormat() { - // TODO(mh): Missing localized format (#6077) - return 'd/m/Y'; + return 'Y-m-d'; } } diff --git a/modules/monitoring/application/forms/Command/CommandForm.php b/modules/monitoring/application/forms/Command/CommandForm.php index fd67e140d..ca30dba74 100644 --- a/modules/monitoring/application/forms/Command/CommandForm.php +++ b/modules/monitoring/application/forms/Command/CommandForm.php @@ -1,6 +1,5 @@ backend; } - /** - * Get the command help description - * - * @return string|null - */ - public function getHelp() - { - return null; - } - /** * Get the transport used to send commands * diff --git a/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php b/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php index 0e64a4b03..e69b9ea11 100644 --- a/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php +++ b/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php @@ -1,6 +1,5 @@ setSubmitLabel(mt('monitoring', 'Disable Notifications')); - } - - /** - * (non-PHPDoc) - * @see \Icinga\Module\Monitoring\Forms\Command\CommandForm::getHelp() For the method documentation. - */ - public function getHelp() - { - return mt( - 'monitoring', + $this->setRequiredCue(null); + $this->setSubmitLabel($this->translate('Disable Notifications')); + $this->addDescription($this->translate( 'This command is used to disable host and service notifications for a specific time.' - ); + )); } /** @@ -49,8 +40,8 @@ class DisableNotificationsExpireCommandForm extends CommandForm 'expire_time', array( 'required' => true, - 'label' => mt('monitoring', 'Expire Time'), - 'description' => mt('monitoring', 'Set the expire time.'), + 'label' => $this->translate('Expire Time'), + 'description' => $this->translate('Set the expire time.'), 'value' => $expireTime ) ); @@ -67,7 +58,7 @@ class DisableNotificationsExpireCommandForm extends CommandForm $disableNotifications ->setExpireTime($this->getElement('expire_time')->getValue()->getTimestamp()); $this->getTransport($this->request)->send($disableNotifications); - Notification::success(mt('monitoring', 'Disabling host and service notifications..')); + Notification::success($this->translate('Disabling host and service notifications..')); return true; } } diff --git a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php index 291619b2f..d487647e2 100644 --- a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php +++ b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php @@ -1,6 +1,5 @@ setUseFormAutosubmit(); + $this->setTitle($this->translate('Feature Commands')); $this->setAttrib('class', 'inline instance-features'); + $this->loadDefaultDecorators()->getDecorator('description')->setTag('h2'); } /** @@ -59,114 +61,132 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm public function createElements(array $formData = array()) { if ((bool) $this->status->notifications_enabled) { - $notificationDescription = sprintf( - '%s', - mt('monitoring', 'Disable notifications for a specific time on a program-wide basis'), - $this->getView()->href('monitoring/process/disable-notifications'), - mt('monitoring', 'Disable temporarily') - ); + if ($this->hasPermission('monitoring/command/feature/instance')) { + $notificationDescription = sprintf( + '%3$s', + $this->translate('Disable notifications for a specific time on a program-wide basis'), + $this->getView()->href('monitoring/process/disable-notifications'), + $this->translate('Disable temporarily') + ); + } else { + $notificationDescription = null; + } } elseif ($this->status->disable_notif_expire_time) { $notificationDescription = sprintf( - mt('monitoring', 'Notifications will be re-enabled in %s'), + $this->translate('Notifications will be re-enabled in %s'), $this->getView()->timeUntil($this->status->disable_notif_expire_time) ); } else { $notificationDescription = null; } - $this->addElements(array( + + $toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : ''; + + $this->addElement( + 'checkbox', + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS, array( - 'checkbox', - ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS, - array( - 'label' => mt('monitoring', 'Active Host Checks Being Executed'), - 'autosubmit' => true - ) - ), + 'label' => $this->translate('Active Host Checks'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled + ) + ); + $this->addElement( + 'checkbox', + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS, array( - 'checkbox', - ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS, - array( - 'label' => mt('monitoring', 'Active Service Checks Being Executed'), - 'autosubmit' => true - ) - ), + 'label' => $this->translate('Active Service Checks'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled + ) + ); + $this->addElement( + 'checkbox', + ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS, array( - 'checkbox', - ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS, - array( - 'label' => mt('monitoring', 'Event Handlers Enabled'), - 'autosubmit' => true - ) - ), + 'label' => $this->translate('Event Handlers'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled + ) + ); + $this->addElement( + 'checkbox', + ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION, array( - 'checkbox', - ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION, - array( - 'label' => mt('monitoring', 'Flap Detection Enabled'), - 'autosubmit' => true - ) - ), - array( - 'checkbox', - ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS, - array( - 'label' => mt('monitoring', 'Notifications Enabled'), - 'autosubmit' => true, - 'description' => $notificationDescription, - 'decorators' => array( - 'ViewHelper', - 'Errors', - array( - 'Description', - array('tag' => 'span', 'class' => 'description', 'escape' => false) - ), - 'Label', - array('HtmlTag', array('tag' => 'div')) - ) - ) - ), + 'label' => $this->translate('Flap Detection'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled + ) + ); + $this->addElement( + 'checkbox', + ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS, array( + 'label' => $this->translate('Notifications'), + 'autosubmit' => true, + 'description' => $notificationDescription, + 'decorators' => array( + 'ViewHelper', + 'Errors', + array( + 'Description', + array('tag' => 'span', 'class' => 'description', 'escape' => false) + ), + 'Label', + array('HtmlTag', array('tag' => 'div')) + ), + 'disabled' => $toggleDisabled + ) + ); + + if (! preg_match('~^v2\.\d+\.\d+.*$~', $this->status->program_version)) { + $this->addElement( 'checkbox', ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING, array( - 'label' => mt('monitoring', 'Obsessing Over Hosts'), - 'autosubmit' => true + 'label' => $this->translate('Obsessing Over Hosts'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) - ), - array( + ); + $this->addElement( 'checkbox', ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING, array( - 'label' => mt('monitoring', 'Obsessing Over Services'), - 'autosubmit' => true + 'label' => $this->translate('Obsessing Over Services'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) - ), - array( + ); + $this->addElement( 'checkbox', ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS, array( - 'label' => mt('monitoring', 'Passive Host Checks Being Accepted'), - 'autosubmit' => true + 'label' => $this->translate('Passive Host Checks'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) - ), - array( + ); + $this->addElement( 'checkbox', ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS, array( - 'label' => mt('monitoring', 'Passive Service Checks Being Accepted'), - 'autosubmit' => true + 'label' => $this->translate('Passive Service Checks'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) - ), + ); + } + + $this->addElement( + 'checkbox', + ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA, array( - 'checkbox', - ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA, - array( - 'label' => mt('monitoring', 'Performance Data Being Processed'), - 'autosubmit' => true - ) + 'label' => $this->translate('Performance Data'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) - )); - return $this; + ); } /** @@ -182,6 +202,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm foreach ($this->getValues() as $feature => $enabled) { $this->getElement($feature)->setChecked($instanceStatus->{$feature}); } + return $this; } @@ -191,14 +212,65 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm */ public function onSuccess() { + $this->assertPermission('monitoring/command/feature/instance'); + + $notifications = array( + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS => array( + $this->translate('Enabling active host checks..'), + $this->translate('Disabling active host checks..') + ), + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS => array( + $this->translate('Enabling active service checks..'), + $this->translate('Disabling active service checks..') + ), + ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS => array( + $this->translate('Enabling event handlers..'), + $this->translate('Disabling event handlers..') + ), + ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION => array( + $this->translate('Enabling flap detection..'), + $this->translate('Disabling flap detection..') + ), + ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS => array( + $this->translate('Enabling notifications..'), + $this->translate('Disabling notifications..') + ), + ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING => array( + $this->translate('Enabling obsessing over hosts..'), + $this->translate('Disabling obsessing over hosts..') + ), + ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING => array( + $this->translate('Enabling obsessing over services..'), + $this->translate('Disabling obsessing over services..') + ), + ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS => array( + $this->translate('Enabling passive host checks..'), + $this->translate('Disabling passive host checks..') + ), + ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS => array( + $this->translate('Enabling passive service checks..'), + $this->translate('Disabling passive service checks..') + ), + ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA => array( + $this->translate('Enabling performance data..'), + $this->translate('Disabling performance data..') + ) + ); + foreach ($this->getValues() as $feature => $enabled) { - $toggleFeature = new ToggleInstanceFeatureCommand(); - $toggleFeature - ->setFeature($feature) - ->setEnabled($enabled); - $this->getTransport($this->request)->send($toggleFeature); + if ((bool) $this->status->{$feature} !== (bool) $enabled) { + $toggleFeature = new ToggleInstanceFeatureCommand(); + $toggleFeature + ->setFeature($feature) + ->setEnabled($enabled); + $this->getTransport($this->request)->send($toggleFeature); + + Notification::success( + $notifications[$feature][$enabled ? 0 : 1] + ); + } } - Notification::success(mt('monitoring', 'Toggling feature..')); + return true; } } diff --git a/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php b/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php index 3456e1bfb..ef6d626b3 100644 --- a/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php @@ -1,6 +1,5 @@ addDescription($this->translate( + 'This command is used to acknowledge host or service problems. When a problem is acknowledged,' + . ' future notifications about problems are temporarily disabled until the host or service' + . ' recovers.' + )); + } + /** * (non-PHPDoc) * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation. */ public function getSubmitLabel() { - return mtp( - 'monitoring', 'Acknowledge problem', 'Acknowledge problems', count($this->objects) - ); - } - - /** - * (non-PHPDoc) - * @see \Icinga\Module\Monitoring\Forms\Command\CommandForm::getHelp() For the method documentation. - */ - public function getHelp() - { - return mt( - 'monitoring', - 'This command is used to acknowledge host or service problems. When a problem is acknowledged,' - . ' future notifications about problems are temporarily disabled until the host or service' - . ' recovers.' - ); + return $this->translatePlural('Acknowledge problem', 'Acknowledge problems', count($this->objects)); } /** @@ -51,9 +46,8 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm 'comment', array( 'required' => true, - 'label' => mt('monitoring', 'Comment'), - 'description' => mt( - 'monitoring', + 'label' => $this->translate('Comment'), + 'description' => $this->translate( 'If you work with other administrators, you may find it useful to share information about the' . ' the host or service that is having problems. Make sure you enter a brief description of' . ' what you are doing.' @@ -64,9 +58,8 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm 'checkbox', 'persistent', array( - 'label' => mt('monitoring', 'Persistent Comment'), - 'description' => mt( - 'monitoring', + 'label' => $this->translate('Persistent Comment'), + 'description' => $this->translate( 'If you would like the comment to remain even when the acknowledgement is removed, check this' . ' option.' ) @@ -76,8 +69,10 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm 'checkbox', 'expire', array( - 'label' => mt('monitoring', 'Use Expire Time'), - 'description' => mt('monitoring', 'If the acknowledgement should expire, check this option.'), + 'label' => $this->translate('Use Expire Time'), + 'description' => $this->translate( + 'If the acknowledgement should expire, check this option.' + ), 'autosubmit' => true ) ) @@ -89,10 +84,9 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm 'dateTimePicker', 'expire_time', array( - 'label' => mt('monitoring', 'Expire Time'), + 'label' => $this->translate('Expire Time'), 'value' => $expireTime, - 'description' => mt( - 'monitoring', + 'description' => $this->translate( 'Enter the expire date and time for this acknowledgement here. Icinga will delete the' . ' acknowledgement after this time expired.' ) @@ -104,7 +98,7 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm array( 'decorators' => array( 'FormElements', - array('HtmlTag', array('tag' => 'div', 'class' => 'control-group')) + array('HtmlTag', array('tag' => 'div')) ) ) ); @@ -114,10 +108,9 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm 'checkbox', 'sticky', array( - 'label' => mt('monitoring', 'Sticky Acknowledgement'), + 'label' => $this->translate('Sticky Acknowledgement'), 'value' => true, - 'description' => mt( - 'monitoring', + 'description' => $this->translate( 'If you want the acknowledgement to disable notifications until the host or service recovers,' . ' check this option.' ) @@ -127,10 +120,9 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm 'checkbox', 'notify', array( - 'label' => mt('monitoring', 'Send Notification'), + 'label' => $this->translate('Send Notification'), 'value' => true, - 'description' => mt( - 'monitoring', + 'description' => $this->translate( 'If you do not want an acknowledgement notification to be sent out to the appropriate contacts,' . ' uncheck this option.' ) @@ -161,8 +153,7 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm } $this->getTransport($this->request)->send($ack); } - Notification::success(mtp( - 'monitoring', + Notification::success($this->translatePlural( 'Acknowledging problem..', 'Acknowledging problems..', count($this->objects) diff --git a/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php b/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php index f5adf6c71..d167061f9 100644 --- a/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php @@ -1,6 +1,5 @@ addDescription($this->translate('This command is used to add host or service comments.')); + } + /** * (non-PHPDoc) * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation. */ public function getSubmitLabel() { - return mtp( - 'monitoring', 'Add comment', 'Add comments', count($this->objects) - ); - } - - /** - * (non-PHPDoc) - * @see \Icinga\Module\Monitoring\Forms\Command\CommandForm::getHelp() For the method documentation. - */ - public function getHelp() - { - return mt( - 'monitoring', - 'This command is used to add host or service comments.' - ); + return $this->translatePlural('Add comment', 'Add comments', count($this->objects)); } /** @@ -47,9 +40,8 @@ class AddCommentCommandForm extends ObjectsCommandForm 'comment', array( 'required' => true, - 'label' => mt('monitoring', 'Comment'), - 'description' => mt( - 'monitoring', + 'label' => $this->translate('Comment'), + 'description' => $this->translate( 'If you work with other administrators, you may find it useful to share information about the' . ' the host or service that is having problems. Make sure you enter a brief description of' . ' what you are doing.' @@ -60,10 +52,9 @@ class AddCommentCommandForm extends ObjectsCommandForm 'checkbox', 'persistent', array( - 'label' => mt('monitoring', 'Persistent'), + 'label' => $this->translate('Persistent'), 'value' => true, - 'description' => mt( - 'monitoring', + 'description' => $this->translate( 'If you uncheck this option, the comment will automatically be deleted the next time Icinga is' . ' restarted.' ) @@ -88,8 +79,7 @@ class AddCommentCommandForm extends ObjectsCommandForm $comment->setPersistent($this->getElement('persistent')->isChecked()); $this->getTransport($this->request)->send($comment); } - Notification::success(mtp( - 'monitoring', + Notification::success($this->translatePlural( 'Adding comment..', 'Adding comments..', count($this->objects) diff --git a/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php b/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php index 01006683f..850443e0e 100644 --- a/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php @@ -1,6 +1,5 @@ true, 'type' => 'submit', - 'value' => mt('monitoring', 'Check now'), - 'label' => ' ' . mt('monitoring', 'Check now'), + 'value' => $this->translate('Check now'), + 'label' => '' + . $this->translate('Check now'), 'decorators' => array('ViewHelper'), 'escape' => false, - 'class' => 'link-like' + 'class' => 'link-like spinner', + 'title' => $this->translate('Schedule the next active check to run immediately') ) ) )); diff --git a/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php index 3d85afcec..691b7dbed 100644 --- a/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php @@ -1,61 +1,76 @@ setAttrib('class', 'inline link-like'); + $this->setAttrib('class', 'inline'); } /** - * (non-PHPDoc) - * @see \Icinga\Web\Form::createElements() For the method documentation. + * {@inheritdoc} */ public function createElements(array $formData = array()) { - $this->addElements(array( + $this->addElements( array( - 'hidden', - 'comment_id', array( - 'required' => true + 'hidden', + 'comment_id', + array( + 'required' => true, + 'validators' => array('NotEmpty'), + 'decorators' => array('ViewHelper') + ) + ), + array( + 'hidden', + 'comment_is_service', + array( + 'filters' => array('Boolean'), + 'decorators' => array('ViewHelper') + ) + ), + array( + 'hidden', + 'redirect', + array( + 'decorators' => array('ViewHelper') + ) ) - ), - array( - 'hidden', - 'redirect' ) - )); + ); return $this; } /** - * (non-PHPDoc) - * @see \Icinga\Web\Form::addSubmitButton() For the method documentation. + * {@inheritdoc} */ public function addSubmitButton() { $this->addElement( - 'submit', + 'button', 'btn_submit', array( 'ignore' => true, - 'label' => 'X', - 'title' => mt('monitoring', 'Delete comment'), + 'escape' => false, + 'type' => 'submit', + 'class' => 'link-like', + 'label' => $this->getView()->icon('trash'), + 'title' => $this->translate('Delete this comment'), 'decorators' => array('ViewHelper') ) ); @@ -63,24 +78,19 @@ class DeleteCommentCommandForm extends ObjectsCommandForm } /** - * (non-PHPDoc) - * @see \Icinga\Web\Form::onSuccess() For the method documentation. + * {@inheritdoc} */ public function onSuccess() { - foreach ($this->objects as $object) { - /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ - $delComment = new DeleteCommentCommand(); - $delComment - ->setObject($object) - ->setCommentId($this->getElement('comment_id')->getValue()); - $this->getTransport($this->request)->send($delComment); - } + $cmd = new DeleteCommentCommand(); + $cmd->setIsService($this->getElement('comment_is_service')->getValue()) + ->setCommentId($this->getElement('comment_id')->getValue()); + $this->getTransport($this->request)->send($cmd); $redirect = $this->getElement('redirect')->getValue(); if (! empty($redirect)) { $this->setRedirectUrl($redirect); } - Notification::success(mt('monitoring', 'Deleting comment..')); + Notification::success($this->translate('Deleting comment..')); return true; } } diff --git a/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php new file mode 100644 index 000000000..6554b9b88 --- /dev/null +++ b/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php @@ -0,0 +1,84 @@ +setAttrib('class', 'inline'); + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData = array()) + { + $this->addElements(array( + array( + 'hidden', + 'redirect', + array('decorators' => array('ViewHelper')) + ) + )); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getSubmitLabel() + { + return $this->translatePlural('Remove', 'Remove All', count($this->downtimes)); + } + + /** + * {@inheritdoc} + */ + public function onSuccess() + { + foreach ($this->comments as $comment) { + $cmd = new DeleteCommentCommand(); + $cmd->setCommentId($comment->id) + ->setIsService(isset($comment->service_description)); + $this->getTransport($this->request)->send($cmd); + } + $redirect = $this->getElement('redirect')->getValue(); + if (! empty($redirect)) { + $this->setRedirectUrl($redirect); + } + Notification::success($this->translate('Deleting comment..')); + return true; + } + + /** + * Set the comments to be deleted upon success + * + * @param array $comments + * + * @return $this + */ + public function setComments(array $comments) + { + $this->comments = $comments; + return $this; + } +} diff --git a/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php index 1c7095b82..c58179fe6 100644 --- a/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php @@ -1,61 +1,76 @@ setAttrib('class', 'inline link-like'); + $this->setAttrib('class', 'inline'); } /** - * (non-PHPDoc) - * @see \Icinga\Web\Form::createElements() For the method documentation. + * {@inheritdoc} */ public function createElements(array $formData = array()) { - $this->addElements(array( + $this->addElements( array( - 'hidden', - 'downtime_id', array( - 'required' => true + 'hidden', + 'downtime_id', + array( + 'required' => true, + 'validators' => array('NotEmpty'), + 'decorators' => array('ViewHelper') + ) + ), + array( + 'hidden', + 'downtime_is_service', + array( + 'filters' => array('Boolean'), + 'decorators' => array('ViewHelper') + ) + ), + array( + 'hidden', + 'redirect', + array( + 'decorators' => array('ViewHelper') + ) ) - ), - array( - 'hidden', - 'redirect' ) - )); + ); return $this; } /** - * (non-PHPDoc) - * @see \Icinga\Web\Form::addSubmitButton() For the method documentation. + * {@inheritdoc} */ public function addSubmitButton() { $this->addElement( - 'submit', + 'button', 'btn_submit', array( 'ignore' => true, - 'label' => 'X', - 'title' => mt('monitoring', 'Delete downtime'), + 'escape' => false, + 'type' => 'submit', + 'class' => 'link-like', + 'label' => $this->getView()->icon('trash'), + 'title' => $this->translate('Delete this downtime'), 'decorators' => array('ViewHelper') ) ); @@ -63,24 +78,20 @@ class DeleteDowntimeCommandForm extends ObjectsCommandForm } /** - * (non-PHPDoc) - * @see \Icinga\Web\Form::onSuccess() For the method documentation. + * {@inheritdoc} */ public function onSuccess() { - foreach ($this->objects as $object) { - /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ - $delDowntime = new DeleteDowntimeCommand(); - $delDowntime - ->setObject($object) - ->setDowntimeId($this->getElement('downtime_id')->getValue()); - $this->getTransport($this->request)->send($delDowntime); - } + $cmd = new DeleteDowntimeCommand(); + $cmd->setDowntimeId($this->getElement('downtime_id')->getValue()); + $cmd->setIsService($this->getElement('downtime_is_service')->getValue()); + $this->getTransport($this->request)->send($cmd); + $redirect = $this->getElement('redirect')->getValue(); if (! empty($redirect)) { $this->setRedirectUrl($redirect); } - Notification::success(mt('monitoring', 'Deleting downtime..')); + Notification::success($this->translate('Deleting downtime.')); return true; } } diff --git a/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php new file mode 100644 index 000000000..b5f7d8c08 --- /dev/null +++ b/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php @@ -0,0 +1,84 @@ +setAttrib('class', 'inline'); + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData = array()) + { + $this->addElements(array( + array( + 'hidden', + 'redirect', + array('decorators' => array('ViewHelper')) + ) + )); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getSubmitLabel() + { + return $this->translatePlural('Remove', 'Remove All', count($this->downtimes)); + } + + /** + * {@inheritdoc} + */ + public function onSuccess() + { + foreach ($this->downtimes as $downtime) { + $delDowntime = new DeleteDowntimeCommand(); + $delDowntime->setDowntimeId($downtime->id); + $delDowntime->setIsService(isset($downtime->service_description)); + $this->getTransport($this->request)->send($delDowntime); + } + $redirect = $this->getElement('redirect')->getValue(); + if (! empty($redirect)) { + $this->setRedirectUrl($redirect); + } + Notification::success($this->translate('Deleting downtime.')); + return true; + } + + /** + * Set the downtimes to be deleted upon success + * + * @param array $downtimes + * + * @return $this + */ + public function setDowntimes(array $downtimes) + { + $this->downtimes = $downtimes; + return $this; + } +} diff --git a/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php b/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php index 1dbf07840..74dd60ea1 100644 --- a/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php @@ -1,6 +1,5 @@ addDescription($this->translate( + 'This command is used to submit passive host or service check results.' + )); + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation. + */ + public function getSubmitLabel() + { + return $this->translatePlural( + 'Submit Passive Check Result', 'Submit Passive Check Results', count($this->objects) + ); + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::createElements() For the method documentation. + */ + public function createElements(array $formData) + { + foreach ($this->getObjects() as $object) { + /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ + // Nasty, but as getObjects() returns everything but an object with a real + // iterator interface this is the only way to fetch just the first element + break; + } + + $this->addElement( + 'select', + 'status', + array( + 'required' => true, + 'label' => $this->translate('Status'), + 'description' => $this->translate('The state this check result should report'), + 'multiOptions' => $object->getType() === $object::TYPE_HOST ? $this->getHostMultiOptions() : array( + ProcessCheckResultCommand::SERVICE_OK => $this->translate('OK', 'icinga.state'), + ProcessCheckResultCommand::SERVICE_WARNING => $this->translate('WARNING', 'icinga.state'), + ProcessCheckResultCommand::SERVICE_CRITICAL => $this->translate('CRITICAL', 'icinga.state'), + ProcessCheckResultCommand::SERVICE_UNKNOWN => $this->translate('UNKNOWN', 'icinga.state') + ) + ) + ); + $this->addElement( + 'text', + 'output', + array( + 'required' => true, + 'label' => $this->translate('Output'), + 'description' => $this->translate('The plugin output of this check result') + ) + ); + $this->addElement( + 'text', + 'perfdata', + array( + 'allowEmpty' => true, + 'label' => $this->translate('Performance Data'), + 'description' => $this->translate( + 'The performance data of this check result. Leave empty' + . ' if this check result has no performance data' + ) + ) + ); + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::onSuccess() For the method documentation. + */ + public function onSuccess() + { + foreach ($this->objects as $object) { + /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ + $command = new ProcessCheckResultCommand(); + $command->setObject($object); + $command->setStatus($this->getValue('status')); + $command->setOutput($this->getValue('output')); + + if ($perfdata = $this->getValue('perfdata')) { + $command->setPerformanceData($perfdata); + } + + $this->getTransport($this->request)->send($command); + } + + Notification::success($this->translatePlural( + 'Processing check result..', + 'Processing check results..', + count($this->objects) + )); + + return true; + } + + /** + * Returns the available host options based on the program version + * + * @return array + */ + protected function getHostMultiOptions() + { + $options = array( + ProcessCheckResultCommand::HOST_UP => $this->translate('UP', 'icinga.state'), + ProcessCheckResultCommand::HOST_DOWN => $this->translate('DOWN', 'icinga.state') + ); + + if (substr($this->getBackend()->getProgramVersion(), 0, 2) !== 'v2') { + $options[ProcessCheckResultCommand::HOST_UNREACHABLE] = $this->translate('UNREACHABLE', 'icinga.state'); + } + + return $options; + } +} diff --git a/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php b/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php index 189261c94..c30adf9d0 100644 --- a/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php @@ -1,6 +1,5 @@ mt('monitoring', 'All Services'), - 'description' => mt( - 'monitoring', + 'label' => $this->translate('All Services'), + 'description' => $this->translate( 'Schedule check for all services on the hosts and the hosts themselves.' ) ) @@ -49,8 +47,7 @@ class ScheduleHostCheckCommandForm extends ScheduleServiceCheckCommandForm ->setOfAllServices($this->getElement('all_services')->isChecked()); $this->scheduleCheck($check, $this->request); } - Notification::success(mtp( - 'monitoring', + Notification::success($this->translatePlural( 'Scheduling host check..', 'Scheduling host checks..', count($this->objects) diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php index beb0793ef..c35cb5413 100644 --- a/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php @@ -1,6 +1,5 @@ mt('monitoring', 'All Services'), - 'description' => mt( - 'monitoring', + 'label' => $this->translate('All Services'), + 'description' => $this->translate( 'Schedule downtime for all services on the hosts and the hosts themselves.' ) ) @@ -38,15 +35,14 @@ class ScheduleHostDowntimeCommandForm extends ScheduleServiceDowntimeCommandForm 'select', 'child_hosts', array( - 'label' => mt('monitoring', 'Child Hosts'), + 'label' => $this->translate('Child Hosts'), 'required' => true, 'multiOptions' => array( - 0 => mt('monitoring', 'Do nothing with child hosts'), - 1 => mt('monitoring', 'Schedule triggered downtime for all child hosts'), - 2 => mt('monitoring', 'Schedule non-triggered downtime for all child hosts') + 0 => $this->translate('Do nothing with child hosts'), + 1 => $this->translate('Schedule triggered downtime for all child hosts'), + 2 => $this->translate('Schedule non-triggered downtime for all child hosts') ), - 'description' => mt( - 'monitoring', + 'description' => $this->translate( 'Define what should be done with the child hosts of the hosts.' ) ) @@ -86,8 +82,7 @@ class ScheduleHostDowntimeCommandForm extends ScheduleServiceDowntimeCommandForm $hostDowntime->setObject($object); $this->scheduleDowntime($hostDowntime, $this->request); } - Notification::success(mtp( - 'monitoring', + Notification::success($this->translatePlural( 'Scheduling host downtime..', 'Scheduling host downtimes..', count($this->objects) diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php index 9e04a2254..f0f15559e 100644 --- a/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php @@ -1,6 +1,5 @@ addDescription($this->translate( + 'This command is used to schedule the next check of hosts or services. Icinga will re-queue the' + . ' hosts or services to be checked at the time you specify.' + )); + } + /** * (non-PHPDoc) * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation. */ public function getSubmitLabel() { - return mtp( - 'monitoring', 'Schedule check', 'Schedule checks', count($this->objects) - ); - } - - /** - * (non-PHPDoc) - * @see \Icinga\Module\Monitoring\Forms\Command\CommandForm::getHelp() For the method documentation. - */ - public function getHelp() - { - return mt( - 'monitoring', - 'This command is used to schedule the next check of hosts or services. Icinga will re-queue the' - . ' hosts or services to be checked at the time you specify.' - ); + return $this->translatePlural('Schedule check', 'Schedule checks', count($this->objects)); } /** @@ -48,24 +43,15 @@ class ScheduleServiceCheckCommandForm extends ObjectsCommandForm $checkTime = new DateTime(); $checkTime->add(new DateInterval('PT1H')); $this->addElements(array( - array( - 'note', - 'command-info', - array( - 'value' => mt( - 'monitoring', - 'This command is used to schedule the next check of hosts or services. Icinga will re-queue the' - . ' hosts or services to be checked at the time you specify.' - ) - ) - ), array( 'dateTimePicker', 'check_time', array( 'required' => true, - 'label' => mt('monitoring', 'Check Time'), - 'description' => mt('monitoring', 'Set the date and time when the check should be scheduled.'), + 'label' => $this->translate('Check Time'), + 'description' => $this->translate( + 'Set the date and time when the check should be scheduled.' + ), 'value' => $checkTime ) ), @@ -73,9 +59,8 @@ class ScheduleServiceCheckCommandForm extends ObjectsCommandForm 'checkbox', 'force_check', array( - 'label' => mt('monitoring', 'Force Check'), - 'description' => mt( - 'monitoring', + 'label' => $this->translate('Force Check'), + 'description' => $this->translate( 'If you select this option, Icinga will force a check regardless of both what time the' . ' scheduled check occurs and whether or not checks are enabled.' ) @@ -111,8 +96,7 @@ class ScheduleServiceCheckCommandForm extends ObjectsCommandForm $check->setObject($object); $this->scheduleCheck($check, $this->request); } - Notification::success(mtp( - 'monitoring', + Notification::success($this->translatePlural( 'Scheduling service check..', 'Scheduling service checks..', count($this->objects) diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php index 9fd86a82b..627138a88 100644 --- a/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php @@ -1,6 +1,5 @@ objects) - ); - } - - /** - * (non-PHPDoc) - * @see \Icinga\Module\Monitoring\Forms\Command\CommandForm::getHelp() For the method documentation. - */ - public function getHelp() - { - return mt( - 'monitoring', + $this->addDescription($this->translate( 'This command is used to schedule host and service downtimes. During the specified downtime,' . ' Icinga will not send notifications out about the hosts and services. When the scheduled' . ' downtime expires, Icinga will send out notifications for the hosts and services as it' . ' normally would. Scheduled downtimes are preserved across program shutdowns and' . ' restarts.' - ); + )); + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation. + */ + public function getSubmitLabel() + { + return $this->translatePlural('Schedule downtime', 'Schedule downtimes', count($this->objects)); } /** @@ -67,9 +62,8 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm 'comment', array( 'required' => true, - 'label' => mt('monitoring', 'Comment'), - 'description' => mt( - 'monitoring', + 'label' => $this->translate('Comment'), + 'description' => $this->translate( 'If you work with other administrators, you may find it useful to share information about the' . ' the host or service that is having problems. Make sure you enter a brief description of' . ' what you are doing.' @@ -81,8 +75,8 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm 'start', array( 'required' => true, - 'label' => mt('monitoring', 'Start Time'), - 'description' => mt('monitoring', 'Set the start date and time for the downtime.'), + 'label' => $this->translate('Start Time'), + 'description' => $this->translate('Set the start date and time for the downtime.'), 'value' => $start ) ), @@ -91,8 +85,8 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm 'end', array( 'required' => true, - 'label' => mt('monitoring', 'End Time'), - 'description' => mt('monitoring', 'Set the end date and time for the downtime.'), + 'label' => $this->translate('End Time'), + 'description' => $this->translate('Set the end date and time for the downtime.'), 'value' => $end ) ), @@ -102,17 +96,16 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm array( 'required' => true, 'autosubmit' => true, - 'label' => mt('monitoring', 'Type'), - 'description' => mt( - 'monitoring', + 'label' => $this->translate('Type'), + 'description' => $this->translate( 'If you select the fixed option, the downtime will be in effect between the start and end' . ' times you specify whereas a flexible downtime starts when the host or service enters a' . ' problem state sometime between the start and end times you specified and lasts as long' . ' as the duration time you enter. The duration fields do not apply for fixed downtimes.' ), 'multiOptions' => array( - self::FIXED => mt('monitoring', 'Fixed'), - self::FLEXIBLE => mt('monitoring', 'Flexible') + self::FIXED => $this->translate('Fixed'), + self::FLEXIBLE => $this->translate('Flexible') ), 'validators' => array( array( @@ -130,7 +123,7 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm array( 'decorators' => array( 'FormElements', - array('HtmlTag', array('tag' => 'div', 'class' => 'control-group')) + array('HtmlTag', array('tag' => 'div')) ) ) ); @@ -141,7 +134,7 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm 'hours', array( 'required' => true, - 'label' => mt('monitoring', 'Hours'), + 'label' => $this->translate('Hours'), 'value' => 2, 'min' => -1 ) @@ -151,7 +144,7 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm 'minutes', array( 'required' => true, - 'label' => mt('monitoring', 'Minutes'), + 'label' => $this->translate('Minutes'), 'value' => 0, 'min' => -1 ) @@ -161,15 +154,14 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm array('hours', 'minutes'), 'duration', array( - 'legend' => mt('monitoring', 'Flexible Duration'), - 'description' => mt( - 'monitoring', + 'legend' => $this->translate('Flexible Duration'), + 'description' => $this->translate( 'Enter here the duration of the downtime. The downtime will be automatically deleted after this' . ' time expired.' ), 'decorators' => array( 'FormElements', - array('HtmlTag', array('tag' => 'div', 'class' => 'control-group')), + array('HtmlTag', array('tag' => 'div')), array( 'Description', array('tag' => 'span', 'class' => 'description', 'placement' => 'prepend') @@ -211,8 +203,7 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm $downtime->setObject($object); $this->scheduleDowntime($downtime, $this->request); } - Notification::success(mtp( - 'monitoring', + Notification::success($this->translatePlural( 'Scheduling service downtime..', 'Scheduling service downtimes..', count($this->objects) diff --git a/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php b/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php new file mode 100644 index 000000000..b9ab60065 --- /dev/null +++ b/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php @@ -0,0 +1,101 @@ +addDescription( + $this->translate('This command is used to send custom notifications about hosts or services.') + ); + } + + /** + * {@inheritdoc} + */ + public function getSubmitLabel() + { + return $this->translatePlural('Send custom notification', 'Send custom notifications', count($this->objects)); + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData = array()) + { + $this->addElements(array( + array( + 'textarea', + 'comment', + array( + 'required' => true, + 'label' => $this->translate('Comment'), + 'description' => $this->translate( + 'If you work with other administrators, you may find it useful to share information about the' + . ' the host or service that is having problems. Make sure you enter a brief description of' + . ' what you are doing.' + ) + ) + ), + array( + 'checkbox', + 'forced', + array( + 'label' => $this->translate('Forced'), + 'value' => false, + 'description' => $this->translate( + 'If you check this option, the notification is sent out regardless of time restrictions and' + . ' whether or not notifications are enabled.' + ) + ) + ), + array( + 'checkbox', + 'broadcast', + array( + 'label' => $this->translate('Broadcast'), + 'value' => false, + 'description' => $this->translate( + 'If you check this option, the notification is sent out to all normal and escalated contacts.' + ) + ) + ) + )); + return $this; + } + + /** + * {@inheritdoc} + */ + public function onSuccess() + { + foreach ($this->objects as $object) { + /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ + $notification = new SendCustomNotificationCommand(); + $notification + ->setObject($object) + ->setComment($this->getElement('comment')->getValue()) + ->setAuthor($this->request->getUser()->getUsername()) + ->setForced($this->getElement('forced')->isChecked()) + ->setBroadcast($this->getElement('broadcast')->isChecked()); + $this->getTransport($this->request)->send($notification); + } + Notification::success($this->translatePlural( + 'Sending custom notification..', + 'Sending custom notifications..', + count($this->objects) + )); + return true; + } +} diff --git a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php index c33527895..7b8ee9738 100644 --- a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php @@ -1,6 +1,5 @@ setUseFormAutosubmit(); + $this->setTitle('Feature Commands'); $this->setAttrib('class', 'inline object-features'); + $this->loadDefaultDecorators()->getDecorator('description')->setTag('h2'); } /** @@ -28,57 +30,66 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm */ public function createElements(array $formData = array()) { - $this->addElements(array( + $toggleDisabled = $this->hasPermission('monitoring/command/feature/object') ? null : ''; + + $this->addElement( + 'checkbox', + ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS, array( - 'checkbox', - ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS, - array( - 'label' => mt('monitoring', 'Active Checks'), - 'autosubmit' => true - ) - ), - array( - 'checkbox', - ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS, - array( - 'label' => mt('monitoring', 'Passive Checks'), - 'autosubmit' => true - ) - ), + 'label' => $this->translate('Active Checks'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled + ) + ); + $this->addElement( + 'checkbox', + ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS, array( + 'label' => $this->translate('Passive Checks'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled + ) + ); + + if (! preg_match('~^v2\.\d+\.\d+.*$~', $this->getIcingaVersion())) { + $this->addElement( 'checkbox', ToggleObjectFeatureCommand::FEATURE_OBSESSING, array( - 'label' => mt('monitoring', 'Obsessing'), - 'autosubmit' => true + 'label' => $this->translate('Obsessing'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) - ), + ); + } + + $this->addElement( + 'checkbox', + ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS, array( - 'checkbox', - ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS, - array( - 'label' => mt('monitoring', 'Notifications'), - 'autosubmit' => true - ) - ), - array( - 'checkbox', - ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER, - array( - 'label' => mt('monitoring', 'Event Handler'), - 'autosubmit' => true - ) - ), - array( - 'checkbox', - ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION, - array( - 'label' => mt('monitoring', 'Flap Detection'), - 'autosubmit' => true - ) + 'label' => $this->translate('Notifications'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) - )); - return $this; + ); + $this->addElement( + 'checkbox', + ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER, + array( + 'label' => $this->translate('Event Handler'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled + ) + ); + $this->addElement( + 'checkbox', + ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION, + array( + 'label' => $this->translate('Flap Detection'), + 'autosubmit' => true, + 'disabled' => $toggleDisabled + ) + ); } /** @@ -95,9 +106,10 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm $element = $this->getElement($feature); $element->setChecked($object->{$feature}); if ((bool) $object->{$feature . '_changed'} === true) { - $element->setDescription(mt('monitoring', 'changed')); + $element->setDescription($this->translate('changed')); } } + return $this; } @@ -107,6 +119,35 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm */ public function onSuccess() { + $this->assertPermission('monitoring/command/feature/object'); + + $notifications = array( + ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS => array( + $this->translate('Enabling active checks..'), + $this->translate('Disabling active checks..') + ), + ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS => array( + $this->translate('Enabling passive checks..'), + $this->translate('Disabling passive checks..') + ), + ToggleObjectFeatureCommand::FEATURE_OBSESSING => array( + $this->translate('Enabling obsessing..'), + $this->translate('Disabling obsessing..') + ), + ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS => array( + $this->translate('Enabling notifications..'), + $this->translate('Disabling notifications..') + ), + ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER => array( + $this->translate('Enabling event handler..'), + $this->translate('Disabling event handler..') + ), + ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION => array( + $this->translate('Enabling flap detection..'), + $this->translate('Disabling flap detection..') + ) + ); + foreach ($this->objects as $object) { /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ foreach ($this->getValues() as $feature => $enabled) { @@ -117,10 +158,24 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm ->setObject($object) ->setEnabled($enabled); $this->getTransport($this->request)->send($toggleFeature); + + Notification::success( + $notifications[$feature][$enabled ? 0 : 1] + ); } } } - Notification::success(mt('monitoring', 'Toggling feature..')); + return true; } + + /** + * Fetch and return the program version of the current instance + * + * @return string + */ + protected function getIcingaVersion() + { + return $this->getBackend()->select()->from('programstatus', array('program_version'))->fetchOne(); + } } diff --git a/modules/monitoring/application/forms/Config/BackendConfigForm.php b/modules/monitoring/application/forms/Config/BackendConfigForm.php index 4b1aa65b6..d5a960d20 100644 --- a/modules/monitoring/application/forms/Config/BackendConfigForm.php +++ b/modules/monitoring/application/forms/Config/BackendConfigForm.php @@ -1,17 +1,21 @@ setName('form_config_monitoring_backends'); - $this->setSubmitLabel(mt('monitoring', 'Save Changes')); + $this->setSubmitLabel($this->translate('Save Changes')); } /** * Set the resource configuration to use * - * @param Config $resources The resource configuration + * @param Config $resourceConfig The resource configuration * - * @return self + * @return $this * * @throws ConfigurationError In case there are no valid monitoring backend resources */ @@ -44,13 +55,15 @@ class BackendConfigForm extends ConfigForm { $resources = array(); foreach ($resourceConfig as $name => $resource) { - if ($resource->type === 'db' || $resource->type === 'livestatus') { - $resources[$resource->type === 'db' ? 'ido' : 'livestatus'][$name] = $name; + if ($resource->type === 'db') { + $resources['ido'][$name] = $name; } } if (empty($resources)) { - throw new ConfigurationError(mt('monitoring', 'Could not find any valid monitoring backend resources')); + throw new ConfigurationError($this->translate( + 'Could not find any valid monitoring backend resources. Please configure a database resource first.' + )); } $this->resources = $resources; @@ -58,149 +71,118 @@ class BackendConfigForm extends ConfigForm } /** - * Add a particular monitoring backend + * Populate the form with the given backend's config * - * The backend to add is identified by the array-key `name'. + * @param string $name * - * @param array $values The values to extend the configuration with + * @return $this * - * @return self - * - * @throws InvalidArgumentException In case the backend does already exist + * @throws NotFoundError In case no backend with the given name is found */ - public function add(array $values) + public function load($name) { - $name = isset($values['name']) ? $values['name'] : ''; - if (! $name) { - throw new InvalidArgumentException(mt('monitoring', 'Monitoring backend name missing')); - } elseif ($this->config->hasSection($name)) { - throw new InvalidArgumentException(mt('monitoring', 'Monitoring backend already exists')); + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No monitoring backend called "%s" found', $name); } - unset($values['name']); - $this->config->setSection($name, $values); + $this->backendToLoad = $name; return $this; } /** - * Edit a particular monitoring backend + * Add a new monitoring backend * - * @param string $name The name of the backend to edit - * @param array $values The values to edit the configuration with + * The backend to add is identified by the array-key `name'. * - * @return array The edited backend configuration + * @param array $data * - * @throws InvalidArgumentException In case the backend does not exist + * @return $this + * + * @throws InvalidArgumentException In case $data does not contain a backend name + * @throws IcingaException In case a backend with the same name already exists */ - public function edit($name, array $values) + public function add(array $data) { - if (! $name) { - throw new InvalidArgumentException(mt('monitoring', 'Old monitoring backend name missing')); - } elseif (! ($newName = isset($values['name']) ? $values['name'] : '')) { - throw new InvalidArgumentException(mt('monitoring', 'New monitoring backend name missing')); - } elseif (! $this->config->hasSection($name)) { - throw new InvalidArgumentException(mt('monitoring', 'Unknown monitoring backend provided')); + if (! isset($data['name'])) { + throw new InvalidArgumentException('Key \'name\' missing'); } - unset($values['name']); - $this->config->setSection($name, $values); - return $this->config->getSection($name); + $backendName = $data['name']; + if ($this->config->hasSection($backendName)) { + throw new IcingaException( + $this->translate('A monitoring backend with the name "%s" does already exist'), + $backendName + ); + } + + unset($data['name']); + $this->config->setSection($backendName, $data); + return $this; } /** - * Remove the given monitoring backend + * Edit a monitoring backend * - * @param string $name The name of the backend to remove + * @param string $name + * @param array $data * - * @return array The removed backend configuration + * @return $this * - * @throws InvalidArgumentException In case the backend does not exist + * @throws NotFoundError In case no backend with the given name is found */ - public function remove($name) + public function edit($name, array $data) { - if (! $name) { - throw new InvalidArgumentException(mt('monitoring', 'Monitoring backend name missing')); - } elseif (! $this->config->hasSection($name)) { - throw new InvalidArgumentException(mt('monitoring', 'Unknown monitoring backend provided')); + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No monitoring backend called "%s" found', $name); } $backendConfig = $this->config->getSection($name); + if (isset($data['name'])) { + if ($data['name'] !== $name) { + $this->config->removeSection($name); + $name = $data['name']; + } + + unset($data['name']); + } + + $backendConfig->merge($data); + foreach ($backendConfig->toArray() as $k => $v) { + if ($v === null) { + unset($backendConfig->$k); + } + } + + $this->config->setSection($name, $backendConfig); + return $this; + } + + /** + * Remove a monitoring backend + * + * @param string $name + * + * @return $this + */ + public function delete($name) + { $this->config->removeSection($name); - return $backendConfig; + return $this; } /** - * Add or edit a monitoring backend and save the configuration + * Create and add elements to this form * - * @see Form::onSuccess() - */ - public function onSuccess() - { - $monitoringBackend = $this->request->getQuery('backend'); - try { - if ($monitoringBackend === null) { // create new backend - $this->add($this->getValues()); - $message = mt('monitoring', 'Monitoring backend "%s" has been successfully created'); - } else { // edit existing backend - $this->edit($monitoringBackend, $this->getValues()); - $message = mt('monitoring', 'Monitoring backend "%s" has been successfully changed'); - } - } catch (InvalidArgumentException $e) { - Notification::error($e->getMessage()); - return; - } - - if ($this->save()) { - Notification::success(sprintf($message, $this->getElement('name')->getValue())); - } else { - return false; - } - } - - /** - * Populate the form in case a monitoring backend is being edited - * - * @see Form::onRequest() - * - * @throws ConfigurationError In case the backend name is missing in the request or is invalid - */ - public function onRequest() - { - $monitoringBackend = $this->request->getQuery('backend'); - if ($monitoringBackend !== null) { - if ($monitoringBackend === '') { - throw new ConfigurationError(mt('monitoring', 'Monitoring backend name missing')); - } elseif (! $this->config->hasSection($monitoringBackend)) { - throw new ConfigurationError(mt('monitoring', 'Unknown monitoring backend provided')); - } - - $backendConfig = $this->config->getSection($monitoringBackend)->toArray(); - $backendConfig['name'] = $monitoringBackend; - $this->populate($backendConfig); - } - } - - /** - * @see Form::createElements() + * @param array $formData */ public function createElements(array $formData) { - $resourceType = isset($formData['type']) ? $formData['type'] : key($this->resources); - - $resourceTypes = array(); - if ($resourceType === 'ido' || array_key_exists('ido', $this->resources)) { - $resourceTypes['ido'] = 'IDO Backend'; - } - if ($resourceType === 'livestatus' || array_key_exists('livestatus', $this->resources)) { - $resourceTypes['livestatus'] = 'Livestatus'; - } - $this->addElement( 'checkbox', 'disabled', array( 'required' => true, - 'label' => mt('monitoring', 'Disable This Backend') + 'label' => $this->translate('Disable This Backend') ) ); $this->addElement( @@ -208,57 +190,215 @@ class BackendConfigForm extends ConfigForm 'name', array( 'required' => true, - 'label' => mt('monitoring', 'Backend Name'), - 'description' => mt('monitoring', 'The identifier of this backend') + 'label' => $this->translate('Backend Name'), + 'description' => $this->translate( + 'The name of this monitoring backend that is used to differentiate it from others' + ), + 'validators' => array( + array( + 'Regex', + false, + array( + 'pattern' => '/^[^\\[\\]:]+$/', + 'messages' => array( + 'regexNotMatch' => $this->translate( + 'The name cannot contain \'[\', \']\' or \':\'.' + ) + ) + ) + ) + ) ) ); + + $resourceType = isset($formData['type']) ? $formData['type'] : null; + + $resourceTypes = array(); + if ($resourceType === 'ido' || array_key_exists('ido', $this->resources)) { + $resourceTypes['ido'] = 'IDO Backend'; + } + + if ($resourceType === null) { + $resourceType = key($resourceTypes); + } elseif ($resourceType === 'livestatus') { + throw new ConfigurationError( + 'We\'ve disabled livestatus support for now because it\'s not feature complete yet' + ); + } + $this->addElement( 'select', 'type', array( 'required' => true, 'autosubmit' => true, - 'label' => mt('monitoring', 'Backend Type'), - 'description' => mt('monitoring', 'The data source used for retrieving monitoring information'), - 'multiOptions' => $resourceTypes, - 'value' => $resourceType + 'label' => $this->translate('Backend Type'), + 'description' => $this->translate( + 'The type of data source used for retrieving monitoring information' + ), + 'multiOptions' => $resourceTypes ) ); - $resourceElement = $this->createElement( + $decorators = static::$defaultElementDecorators; + array_pop($decorators); // Removes the HtmlTag decorator + $this->addElement( 'select', 'resource', array( 'required' => true, - 'label' => mt('monitoring', 'Resource'), - 'description' => mt('monitoring', 'The resource to use'), + 'label' => $this->translate('Resource'), + 'description' => $this->translate('The resource to use'), 'multiOptions' => $this->resources[$resourceType], + 'value' => current($this->resources[$resourceType]), + 'decorators' => $decorators, 'autosubmit' => true ) ); - - if (empty($formData)) { - $options = $resourceElement->options; - $resourceName = array_shift($options); - } else { - $resourceName = (isset($formData['resource'])) ? $formData['resource'] : $this->getValue('resource'); - } - - $this->addElement($resourceElement); - - if ($resourceElement) { - $this->addElement( - 'note', - 'resource_note', - array( - 'value' => sprintf( - '%s', - $this->getView()->href('/icingaweb/config/editresource', array('resource' => $resourceName)), - mt('monitoring', 'Show resource configuration') - ), - 'escape' => false + $resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource'); + $this->addElement( + 'note', + 'resource_note', + array( + 'escape' => false, + 'decorators' => $decorators, + 'value' => sprintf( + '%3$s', + $this->getView()->url('config/editresource', array('resource' => $resourceName)), + sprintf($this->translate('Show the configuration of the %s resource'), $resourceName), + $this->translate('Show resource configuration') ) - ); + ) + ); + $this->addDisplayGroup( + array('resource', 'resource_note'), + 'resource-group', + array( + 'decorators' => array( + 'FormElements', + array('HtmlTag', array('tag' => 'div', 'class' => 'element')) + ) + ) + ); + + if (isset($formData['skip_validation']) && $formData['skip_validation']) { + // In case another error occured and the checkbox was displayed before + $this->addSkipValidationCheckbox(); } } + + /** + * Populate the configuration of the backend to load + */ + public function onRequest() + { + if ($this->backendToLoad) { + $data = $this->config->getSection($this->backendToLoad)->toArray(); + $data['name'] = $this->backendToLoad; + $this->populate($data); + } + } + + /** + * Return whether the given values are valid + * + * @param array $formData The data to validate + * + * @return bool + */ + public function isValid($formData) + { + if (! parent::isValid($formData)) { + return false; + } + + if (($el = $this->getElement('skip_validation')) === null || false === $el->isChecked()) { + $resourceConfig = ResourceFactory::getResourceConfig($this->getValue('resource')); + if (! self::isValidIdoSchema($this, $resourceConfig) || !self::isValidIdoInstance($this, $resourceConfig)) { + if ($el === null) { + $this->addSkipValidationCheckbox(); + } + + return false; + } + } + + return true; + } + + /** + * Add a checkbox to the form by which the user can skip the schema validation + */ + protected function addSkipValidationCheckbox() + { + $this->addElement( + 'checkbox', + 'skip_validation', + array( + 'order' => 0, + 'ignore' => true, + 'required' => true, + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate( + 'Check this to not to validate the IDO schema of the chosen resource.' + ) + ) + ); + } + + /** + * Return whether the given resource contains a valid IDO schema + * + * @param Form $form + * @param ConfigObject $resourceConfig + * + * @return bool + */ + public static function isValidIdoSchema(Form $form, ConfigObject $resourceConfig) + { + try { + $db = ResourceFactory::createResource($resourceConfig); + $db->select()->from('icinga_dbversion', array('version'))->fetchOne(); + } catch (Exception $_) { + $form->error($form->translate( + 'Cannot find the IDO schema. Please verify that the given database ' + . 'contains the schema and that the configured user has access to it.' + )); + return false; + } + + return true; + } + + /** + * Return whether a single icinga instance is writing to the given resource + * + * @param Form $form + * @param ConfigObject $resourceConfig + * + * @return bool True if it's a single instance, false if none + * or multiple instances are writing to it + */ + public static function isValidIdoInstance(Form $form, ConfigObject $resourceConfig) + { + $db = ResourceFactory::createResource($resourceConfig); + $rowCount = $db->select()->from('icinga_instances')->count(); + + if ($rowCount === 0) { + $form->warning($form->translate( + 'There is currently no icinga instance writing to the IDO. Make sure ' + . 'that a icinga instance is configured and able to write to the IDO.' + )); + return false; + } elseif ($rowCount > 1) { + $form->warning($form->translate( + 'There is currently more than one icinga instance writing to the IDO. You\'ll see all objects from all' + . ' instances without any differentation. If this is not desired, consider setting up a separate IDO' + . ' for each instance.' + )); + return false; + } + + return true; + } } diff --git a/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php b/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php index ac1139268..15e1ed055 100644 --- a/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php +++ b/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php @@ -1,6 +1,5 @@ true, - 'label' => mt('monitoring', 'Command File'), - 'value' => '/usr/local/icinga/var/rw/icinga.cmd', - 'description' => mt('monitoring', 'Path to the local Icinga command file') + 'label' => $this->translate('Command File'), + 'value' => '/var/run/icinga2/cmd/icinga2.cmd', + 'description' => $this->translate('Path to the local Icinga command file') ) ); return $this; diff --git a/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php b/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php index c93436ab2..e290866cc 100644 --- a/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php +++ b/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php @@ -1,13 +1,21 @@ setName('form_config_monitoring_instance_remote'); } + /** + * Load all available ssh identity resources + * + * @return $this + * + * @throws \Icinga\Exception\ConfigurationError + */ + public function loadResources() + { + $resourceConfig = ResourceFactory::getResourceConfigs(); + + $resources = array(); + foreach ($resourceConfig as $name => $resource) { + if ($resource->type === 'ssh') { + $resources['ssh'][$name] = $name; + } + } + + if (empty($resources)) { + throw new ConfigurationError($this->translate('Could not find any valid monitoring instance resources')); + } + + $this->resources = $resources; + + return $this; + } + + /** + * Check whether ssh identity resources exists or not + * + * @return boolean + */ + public function hasResources() + { + $resourceConfig = ResourceFactory::getResourceConfigs(); + + foreach ($resourceConfig as $name => $resource) { + if ($resource->type === 'ssh') { + return true; + } + } + return false; + } + /** * (non-PHPDoc) * @see Form::createElements() For the method documentation. */ public function createElements(array $formData = array()) { + $useResource = false; + + if ($this->hasResources()) { + $useResource = isset($formData['use_resource']) + ? $formData['use_resource'] : $this->getValue('use_resource'); + + $this->addElement( + 'checkbox', + 'use_resource', + array( + 'label' => $this->translate('Use SSH Identity'), + 'description' => $this->translate('Make use of the ssh identity resource'), + 'autosubmit' => true, + 'ignore' => true + ) + ); + } + + if ($useResource) { + + $this->loadResources(); + + $decorators = static::$defaultElementDecorators; + array_pop($decorators); // Removes the HtmlTag decorator + + $this->addElement( + 'select', + 'resource', + array( + 'required' => true, + 'label' => $this->translate('SSH Identity'), + 'description' => $this->translate('The resource to use'), + 'decorators' => $decorators, + 'multiOptions' => $this->resources['ssh'], + 'value' => current($this->resources['ssh']), + 'autosubmit' => false + ) + ); + $resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource'); + $this->addElement( + 'note', + 'resource_note', + array( + 'escape' => false, + 'decorators' => $decorators, + 'value' => sprintf( + '%3$s', + $this->getView()->url('config/editresource', array('resource' => $resourceName)), + sprintf($this->translate('Show the configuration of the %s resource'), $resourceName), + $this->translate('Show resource configuration') + ) + ) + ); + } + $this->addElements(array( array( 'text', 'host', array( 'required' => true, - 'label' => mt('monitoring', 'Host'), - 'description' => mt('monitoring', + 'label' => $this->translate('Host'), + 'description' => $this->translate( 'Hostname or address of the remote Icinga instance' ) ) @@ -40,34 +147,39 @@ class RemoteInstanceForm extends Form 'port', array( 'required' => true, - 'label' => mt('monitoring', 'Port'), - 'description' => mt('monitoring', 'SSH port to connect to on the remote Icinga instance'), + 'label' => $this->translate('Port'), + 'description' => $this->translate('SSH port to connect to on the remote Icinga instance'), 'value' => 22 ) - ), - array( + ) + )); + + if (! $useResource) { + $this->addElement( 'text', 'user', array( 'required' => true, - 'label' => mt('monitoring', 'User'), - 'description' => mt('monitoring', + 'label' => $this->translate('User'), + 'description' => $this->translate( 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be' . ' possible for this user' ) ) - ), + ); + } + + $this->addElement( + 'text', + 'path', array( - 'text', - 'path', - array( - 'required' => true, - 'label' => mt('monitoring', 'Command File'), - 'value' => '/usr/local/icinga/var/rw/icinga.cmd', - 'description' => mt('monitoring', 'Path to the Icinga command file on the remote Icinga instance') - ) + 'required' => true, + 'label' => $this->translate('Command File'), + 'value' => '/var/run/icinga2/cmd/icinga2.cmd', + 'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance') ) - )); + ); + return $this; } } diff --git a/modules/monitoring/application/forms/Config/InstanceConfigForm.php b/modules/monitoring/application/forms/Config/InstanceConfigForm.php index b89e48c0a..27e1e410d 100644 --- a/modules/monitoring/application/forms/Config/InstanceConfigForm.php +++ b/modules/monitoring/application/forms/Config/InstanceConfigForm.php @@ -1,39 +1,44 @@ setName('form_config_monitoring_instance'); - $this->setSubmitLabel(mt('monitoring', 'Save Changes')); + $this->setSubmitLabel($this->translate('Save Changes')); } /** - * Get a form object for the given instance type + * Return a form object for the given instance type * - * @param string $type The instance type for which to return a form + * @param string $type The instance type for which to return a form * - * @return LocalInstanceForm|RemoteInstanceForm + * @return Form * * @throws InvalidArgumentException In case the given instance type is invalid */ @@ -41,173 +46,200 @@ class InstanceConfigForm extends ConfigForm { switch (strtolower($type)) { case LocalCommandFile::TRANSPORT: - $form = new LocalInstanceForm(); - break; + return new LocalInstanceForm(); case RemoteCommandFile::TRANSPORT; - $form = new RemoteInstanceForm(); - break; + return new RemoteInstanceForm(); default: throw new InvalidArgumentException( - sprintf(mt('monitoring', 'Invalid instance type "%s" given'), $type) + sprintf($this->translate('Invalid monitoring instance type "%s" given'), $type) ); } - return $form; + } + + /** + * Populate the form with the given instance's config + * + * @param string $name + * + * @return $this + * + * @throws NotFoundError In case no instance with the given name is found + */ + public function load($name) + { + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No monitoring instance called "%s" found', $name); + } + + $this->instanceToLoad = $name; + return $this; } /** * Add a new instance * - * The resource to add is identified by the array-key `name'. + * The instance to add is identified by the array-key `name'. * - * @param array $values The values to extend the configuration with + * @param array $data * - * @return self + * @return $this * - * @throws InvalidArgumentException In case the resource already exists + * @throws InvalidArgumentException In case $data does not contain a instance name + * @throws IcingaException In case a instance with the same name already exists */ - public function add(array $values) + public function add(array $data) { - $name = isset($values['name']) ? $values['name'] : ''; - if (! $name) { - throw new InvalidArgumentException(mt('monitoring', 'Instance name missing')); - } - if ($this->config->hasSection($name)) { - throw new InvalidArgumentException(mt('monitoring', 'Instance already exists')); + if (! isset($data['name'])) { + throw new InvalidArgumentException('Key \'name\' missing'); } - unset($values['name']); - $this->config->setSection($name, $values); + $instanceName = $data['name']; + if ($this->config->hasSection($instanceName)) { + throw new IcingaException( + $this->translate('A monitoring instance with the name "%s" does already exist'), + $instanceName + ); + } + + unset($data['name']); + $this->config->setSection($instanceName, $data); return $this; } /** * Edit an existing instance * - * @param string $name The name of the resource to edit - * @param array $values The values to edit the configuration with + * @param string $name + * @param array $data * - * @return array The edited resource configuration + * @return $this * - * @throws InvalidArgumentException In case the resource name is missing or invalid + * @throws NotFoundError In case no instance with the given name is found */ - public function edit($name, array $values) + public function edit($name, array $data) { - if (! $name) { - throw new InvalidArgumentException(mt('monitoring', 'Old instance name missing')); - } elseif (! ($newName = isset($values['name']) ? $values['name'] : '')) { - throw new InvalidArgumentException(mt('monitoring', 'New instance name missing')); - } elseif (! $this->config->hasSection($name)) { - throw new InvalidArgumentException(mt('monitoring', 'Unknown instance name provided')); + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No monitoring instance called "%s" found', $name); } - unset($values['name']); - $this->config->setSection($name, $values); - return $this->config->getSection($name); + $instanceConfig = $this->config->getSection($name); + if (isset($data['name'])) { + if ($data['name'] !== $name) { + $this->config->removeSection($name); + $name = $data['name']; + } + + unset($data['name']); + } + + $instanceConfig->merge($data); + foreach ($instanceConfig->toArray() as $k => $v) { + if ($v === null) { + unset($instanceConfig->$k); + } + } + + $this->config->setSection($name, $instanceConfig); + return $this; } /** * Remove a instance * - * @param string $name The name of the resource to remove + * @param string $name * - * @return array The removed resource configuration - * - * @throws InvalidArgumentException In case the resource name is missing or invalid + * @return $this */ - public function remove($name) + public function delete($name) { - if (! $name) { - throw new InvalidArgumentException(mt('monitoring', 'Instance name missing')); - } elseif (! $this->config->hasSection($name)) { - throw new InvalidArgumentException(mt('monitoring', 'Unknown instance name provided')); - } - - $instanceConfig = $this->config->getSection($name); $this->config->removeSection($name); - return $instanceConfig; + return $this; } /** - * @see Form::onRequest() For the method documentation. - * @throws ConfigurationError In case the instance name is missing or invalid + * Create and add elements to this form + * + * @param array $formData */ - public function onRequest() + public function createElements(array $formData) { - $instanceName = $this->request->getQuery('instance'); - if ($instanceName !== null) { - if (! $instanceName) { - throw new ConfigurationError(mt('monitoring', 'Instance name missing')); - } - if (! $this->config->hasSection($instanceName)) { - throw new ConfigurationError(mt('monitoring', 'Unknown instance name given')); - } + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Instance Name'), + 'description' => $this->translate( + 'The name of this monitoring instance that is used to differentiate it from others' + ), + 'validators' => array( + array( + 'Regex', + false, + array( + 'pattern' => '/^[^\\[\\]:]+$/', + 'messages' => array( + 'regexNotMatch' => $this->translate( + 'The name cannot contain \'[\', \']\' or \':\'.' + ) + ) + ) + ) + ) + ) + ); - $instanceConfig = $this->config->getSection($instanceName)->toArray(); - $instanceConfig['name'] = $instanceName; - $this->populate($instanceConfig); + $instanceTypes = array( + LocalCommandFile::TRANSPORT => $this->translate('Local Command File'), + RemoteCommandFile::TRANSPORT => $this->translate('Remote Command File') + ); + + $instanceType = isset($formData['transport']) ? $formData['transport'] : null; + if ($instanceType === null) { + $instanceType = key($instanceTypes); } - } - - /** - * (non-PHPDoc) - * @see Form::onSuccess() For the method documentation. - */ - public function onSuccess() - { - $instanceName = $this->request->getQuery('instance'); - try { - if ($instanceName === null) { // create new instance - $this->add($this->getValues()); - $message = mt('monitoring', 'Instance "%s" created successfully.'); - } else { // edit existing instance - $this->edit($instanceName, $this->getValues()); - $message = mt('monitoring', 'Instance "%s" edited successfully.'); - } - } catch (InvalidArgumentException $e) { - Notification::error($e->getMessage()); - return; - } - - if ($this->save()) { - Notification::success(sprintf($message, $this->getElement('name')->getValue())); - } else { - return false; - } - } - - /** - * (non-PHPDoc) - * @see Form::createElements() For the method documentation. - */ - public function createElements(array $formData = array()) - { - $instanceType = isset($formData['transport']) ? $formData['transport'] : LocalCommandFile::TRANSPORT; $this->addElements(array( - array( - 'text', - 'name', - array( - 'required' => true, - 'label' => mt('monitoring', 'Instance Name') - ) - ), array( 'select', 'transport', array( 'required' => true, 'autosubmit' => true, - 'label' => mt('monitoring', 'Instance Type'), - 'multiOptions' => array( - LocalCommandFile::TRANSPORT => mt('monitoring', 'Local Command File'), - RemoteCommandFile::TRANSPORT => mt('monitoring', 'Remote Command File') - ), - 'value' => $instanceType + 'label' => $this->translate('Instance Type'), + 'description' => $this->translate('The type of transport to use for this monitoring instance'), + 'multiOptions' => $instanceTypes ) ) )); - $this->addElements($this->getInstanceForm($instanceType)->createElements($formData)->getElements()); + $this->addSubForm($this->getInstanceForm($instanceType)->create($formData), 'instance_form'); + } + + /** + * Populate the configuration of the instance to load + */ + public function onRequest() + { + if ($this->instanceToLoad) { + $data = $this->config->getSection($this->instanceToLoad)->toArray(); + $data['name'] = $this->instanceToLoad; + $this->populate($data); + } + } + + /** + * Retrieve all form element values + * + * @param bool $suppressArrayNotation Ignored + * + * @return array + */ + public function getValues($suppressArrayNotation = false) + { + $values = parent::getValues(); + $values = array_merge($values, $values['instance_form']); + unset($values['instance_form']); + return $values; } } diff --git a/modules/monitoring/application/forms/Config/SecurityConfigForm.php b/modules/monitoring/application/forms/Config/SecurityConfigForm.php index 40c0b7b8c..c865bf295 100644 --- a/modules/monitoring/application/forms/Config/SecurityConfigForm.php +++ b/modules/monitoring/application/forms/Config/SecurityConfigForm.php @@ -1,6 +1,5 @@ setName('form_config_monitoring_security'); - $this->setSubmitLabel(mt('monitoring', 'Save Changes')); + $this->setSubmitLabel($this->translate('Save Changes')); } /** @@ -29,7 +28,7 @@ class SecurityConfigForm extends ConfigForm $this->config->setSection('security', $this->getValues()); if ($this->save()) { - Notification::success(mt('monitoring', 'New security configuration has successfully been stored')); + Notification::success($this->translate('New security configuration has successfully been stored')); } else { return false; } @@ -54,8 +53,8 @@ class SecurityConfigForm extends ConfigForm array( 'allowEmpty' => true, 'value' => '*pw*,*pass*,community', - 'label' => mt('monitoring', 'Protected Custom Variables'), - 'description' => mt('monitoring', + 'label' => $this->translate('Protected Custom Variables'), + 'description' => $this->translate( 'Comma separated case insensitive list of protected custom variables.' . ' Use * as a placeholder for zero or more wildcard characters.' . ' Existance of those custom variables will be shown, but their values will be masked.' diff --git a/modules/monitoring/application/forms/EventOverviewForm.php b/modules/monitoring/application/forms/EventOverviewForm.php index 25d268f76..b835b77e0 100644 --- a/modules/monitoring/application/forms/EventOverviewForm.php +++ b/modules/monitoring/application/forms/EventOverviewForm.php @@ -1,12 +1,9 @@ t('State Changes'), + 'label' => $this->translate('State Changes'), 'class' => 'autosubmit', 'decorators' => $decorators, 'value' => strpos($url, $this->stateChangeFilter()->toQueryString()) === false ? 0 : 1 @@ -54,7 +51,7 @@ class EventOverviewForm extends Form 'checkbox', 'downtime', array( - 'label' => t('Downtimes'), + 'label' => $this->translate('Downtimes'), 'class' => 'autosubmit', 'decorators' => $decorators, 'value' => strpos($url, $this->downtimeFilter()->toQueryString()) === false ? 0 : 1 @@ -64,7 +61,7 @@ class EventOverviewForm extends Form 'checkbox', 'comment', array( - 'label' => t('Comments'), + 'label' => $this->translate('Comments'), 'class' => 'autosubmit', 'decorators' => $decorators, 'value' => strpos($url, $this->commentFilter()->toQueryString()) === false ? 0 : 1 @@ -74,7 +71,7 @@ class EventOverviewForm extends Form 'checkbox', 'notification', array( - 'label' => t('Notifications'), + 'label' => $this->translate('Notifications'), 'class' => 'autosubmit', 'decorators' => $decorators, 'value' => strpos($url, $this->notificationFilter()->toQueryString()) === false ? 0 : 1 @@ -84,7 +81,7 @@ class EventOverviewForm extends Form 'checkbox', 'flapping', array( - 'label' => t('Flapping'), + 'label' => $this->translate('Flapping'), 'class' => 'autosubmit', 'decorators' => $decorators, 'value' => strpos($url, $this->flappingFilter()->toQueryString()) === false ? 0 : 1 diff --git a/modules/monitoring/application/forms/Setup/BackendPage.php b/modules/monitoring/application/forms/Setup/BackendPage.php index 4bee056ca..077ca3594 100644 --- a/modules/monitoring/application/forms/Setup/BackendPage.php +++ b/modules/monitoring/application/forms/Setup/BackendPage.php @@ -1,6 +1,5 @@ setName('setup_monitoring_backend'); + $this->setTitle($this->translate('Monitoring Backend', 'setup.page.title')); + $this->addDescription($this->translate( + 'Please configure below how Icinga Web 2 should retrieve monitoring information.' + )); } public function createElements(array $formData) { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('monitoring', 'Monitoring Backend', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'monitoring', - 'Please configure below how Icinga Web 2 should retrieve monitoring information.' - ) - ) - ); - $this->addElement( 'text', 'name', array( 'required' => true, 'value' => 'icinga', - 'label' => mt('monitoring', 'Backend Name'), - 'description' => mt('monitoring', 'The identifier of this backend') + 'label' => $this->translate('Backend Name'), + 'description' => $this->translate('The identifier of this backend') ) ); $resourceTypes = array(); - if (Platform::extensionLoaded('mysql') || Platform::extensionLoaded('pgsql')) { + if (Platform::hasMysqlSupport() || Platform::hasPostgresqlSupport()) { $resourceTypes['ido'] = 'IDO'; } - $resourceTypes['livestatus'] = 'Livestatus'; + // $resourceTypes['livestatus'] = 'Livestatus'; $this->addElement( 'select', 'type', array( 'required' => true, - 'label' => mt('monitoring', 'Backend Type'), - 'description' => mt('monitoring', 'The data source used for retrieving monitoring information'), + 'label' => $this->translate('Backend Type'), + 'description' => $this->translate( + 'The data source used for retrieving monitoring information' + ), 'multiOptions' => $resourceTypes ) ); diff --git a/modules/monitoring/application/forms/Setup/IdoResourcePage.php b/modules/monitoring/application/forms/Setup/IdoResourcePage.php index 189d65fea..2bb4de3a9 100644 --- a/modules/monitoring/application/forms/Setup/IdoResourcePage.php +++ b/modules/monitoring/application/forms/Setup/IdoResourcePage.php @@ -1,19 +1,34 @@ setName('setup_monitoring_ido'); + $this->setTitle($this->translate('Monitoring IDO Resource', 'setup.page.title')); + $this->addDescription($this->translate( + 'Please fill out the connection details below to access the IDO database of your monitoring environment.' + )); + $this->setValidatePartial(true); } + /** + * Create and add elements to this form + * + * @param array $formData + */ public function createElements(array $formData) { $this->addElement( @@ -24,30 +39,9 @@ class IdoResourcePage extends Form 'value' => 'db' ) ); - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('monitoring', 'Monitoring IDO Resource', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'monitoring', - 'Please fill out the connection details below to access' - . ' the IDO database of your monitoring environment.' - ) - ) - ); if (isset($formData['skip_validation']) && $formData['skip_validation']) { + // In case another error occured and the checkbox was displayed before $this->addSkipValidationCheckbox(); } else { $this->addElement( @@ -60,20 +54,42 @@ class IdoResourcePage extends Form ); } - $livestatusResourceForm = new DbResourceForm(); - $this->addElements($livestatusResourceForm->createElements($formData)->getElements()); + $dbResourceForm = new DbResourceForm(); + $this->addElements($dbResourceForm->createElements($formData)->getElements()); $this->getElement('name')->setValue('icinga_ido'); } - public function isValid($data) + /** + * Return whether the given values are valid + * + * @param array $formData The data to validate + * + * @return bool + */ + public function isValid($formData) { - if (false === parent::isValid($data)) { + if (! parent::isValid($formData)) { return false; } - if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) { - if (false === DbResourceForm::isValidResource($this)) { - $this->addSkipValidationCheckbox(); + if (! isset($formData['skip_validation']) || !$formData['skip_validation']) { + $inspection = ResourceConfigForm::inspectResource($this); + if ($inspection !== null && $inspection->hasError()) { + $this->error($inspection->getError()); + $this->addSkipValidationCheckbox($this->translate( + 'Check this to not to validate connectivity with the given database server.' + )); + return false; + } + + $configObject = new ConfigObject($this->getValues()); + if ( + ! BackendConfigForm::isValidIdoSchema($this, $configObject) + || !BackendConfigForm::isValidIdoInstance($this, $configObject) + ) { + $this->addSkipValidationCheckbox($this->translate( + 'Check this to not to validate the IDO schema in the given database.' + )); return false; } } @@ -82,17 +98,74 @@ class IdoResourcePage extends Form } /** - * Add a checkbox to the form by which the user can skip the connection validation + * Run the configured backend's inspection checks and show the result, if necessary + * + * This will only run any validation if the user pushed the 'backend_validation' button. + * + * @param array $formData + * + * @return bool */ - protected function addSkipValidationCheckbox() + public function isValidPartial(array $formData) { + if (isset($formData['backend_validation']) && parent::isValid($formData)) { + $inspection = ResourceConfigForm::inspectResource($this); + if ($inspection !== null) { + $join = function ($e) use (& $join) { + return is_string($e) ? $e : join("\n", array_map($join, $e)); + }; + $this->addElement( + 'note', + 'inspection_output', + array( + 'order' => 0, + 'value' => '' . $this->translate('Validation Log') . "\n\n" + . join("\n", array_map($join, $inspection->toArray())), + 'decorators' => array( + 'ViewHelper', + array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')), + ) + ) + ); + + if ($inspection->hasError()) { + $this->warning(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $inspection->getError() + )); + return false; + } + } + + $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); + } + + return true; + } + + /** + * Add a checkbox to the form by which the user can skip the resource validation + * + * @param string $description + */ + protected function addSkipValidationCheckbox($description = null) + { + if (empty($description)) { + $description = $this->translate( + 'Proceed without any further (custom) validation.' + ); + } + $this->addElement( 'checkbox', 'skip_validation', array( 'required' => true, - 'label' => t('Skip Validation'), - 'description' => t('Check this to not to validate connectivity with the given database server') + 'label' => $this->translate('Skip Validation'), + 'description' => $description ) ); } diff --git a/modules/monitoring/application/forms/Setup/InstancePage.php b/modules/monitoring/application/forms/Setup/InstancePage.php index dccfd1d91..d16646e3e 100644 --- a/modules/monitoring/application/forms/Setup/InstancePage.php +++ b/modules/monitoring/application/forms/Setup/InstancePage.php @@ -1,6 +1,5 @@ setName('setup_monitoring_instance'); + $this->setTitle($this->translate('Monitoring Instance', 'setup.page.title')); + $this->addDescription($this->translate( + 'Please define the settings specific to your monitoring instance below.' + )); } public function createElements(array $formData) { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('monitoring', 'Monitoring Instance', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'monitoring', - 'Please define the settings specific to your monitoring instance below.' - ) - ) - ); - - if (isset($formData['host'])) { - $formData['type'] = 'remote'; // This is necessary as the type element gets ignored by Form::getValues() - } - $instanceConfigForm = new InstanceConfigForm(); - $instanceConfigForm->createElements($formData); - $this->addElements($instanceConfigForm->getElements()); - $this->getElement('name')->setValue('icinga'); + $this->addSubForm($instanceConfigForm, 'instance_form'); + $instanceConfigForm->create($formData); + $instanceConfigForm->getElement('name')->setValue('icinga'); + } + + public function getValues($suppressArrayNotation = false) + { + return $this->getSubForm('instance_form')->getValues($suppressArrayNotation); } } diff --git a/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php b/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php index 4faa17416..48b688218 100644 --- a/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php +++ b/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php @@ -1,6 +1,5 @@ setName('setup_monitoring_livestatus'); + $this->setTitle($this->translate('Monitoring Livestatus Resource', 'setup.page.title')); + $this->addDescription($this->translate( + 'Please fill out the connection details below to access the Livestatus' + . ' socket interface for your monitoring environment.' + )); } public function createElements(array $formData) @@ -24,28 +28,6 @@ class LivestatusResourcePage extends Form 'value' => 'livestatus' ) ); - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('monitoring', 'Monitoring Livestatus Resource', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'monitoring', - 'Please fill out the connection details below to access the Livestatus' - . ' socket interface for your monitoring environment.' - ) - ) - ); if (isset($formData['skip_validation']) && $formData['skip_validation']) { $this->addSkipValidationCheckbox(); @@ -91,8 +73,10 @@ class LivestatusResourcePage extends Form 'skip_validation', array( 'required' => true, - 'label' => t('Skip Validation'), - 'description' => t('Check this to not to validate connectivity with the given Livestatus socket') + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate( + 'Check this to not to validate connectivity with the given Livestatus socket' + ) ) ); } diff --git a/modules/monitoring/application/forms/Setup/SecurityPage.php b/modules/monitoring/application/forms/Setup/SecurityPage.php index 0c7d3d1de..30eac67ab 100644 --- a/modules/monitoring/application/forms/Setup/SecurityPage.php +++ b/modules/monitoring/application/forms/Setup/SecurityPage.php @@ -1,6 +1,5 @@ setName('setup_monitoring_security'); + $this->setTitle($this->translate('Monitoring Security', 'setup.page.title')); + $this->addDescription($this->translate( + 'To protect your monitoring environment against prying eyes please fill out the settings below.' + )); } public function createElements(array $formData) { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('monitoring', 'Monitoring Security', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'monitoring', - 'To protect your monitoring environment against prying eyes please fill out the settings below.' - ) - ) - ); - $securityConfigForm = new SecurityConfigForm(); $securityConfigForm->createElements($formData); $this->addElements($securityConfigForm->getElements()); diff --git a/modules/monitoring/application/forms/Setup/WelcomePage.php b/modules/monitoring/application/forms/Setup/WelcomePage.php index d910e2e01..2fa817c73 100644 --- a/modules/monitoring/application/forms/Setup/WelcomePage.php +++ b/modules/monitoring/application/forms/Setup/WelcomePage.php @@ -1,6 +1,5 @@ mt( - 'monitoring', - 'Welcome to the configuration of the monitoring module for Icinga Web 2!' - ), + 'value' => $this->translate('Welcome to the configuration of the monitoring module for Icinga Web 2!'), 'decorators' => array( 'ViewHelper', array('HtmlTag', array('tag' => 'h2')) @@ -34,7 +30,7 @@ class WelcomePage extends Form 'note', 'core_hint', array( - 'value' => mt('monitoring', 'This is the core module for Icinga Web 2.') + 'value' => $this->translate('This is the core module for Icinga Web 2.') ) ); @@ -42,8 +38,7 @@ class WelcomePage extends Form 'note', 'description', array( - 'value' => mt( - 'monitoring', + 'value' => $this->translate( 'It offers various status and reporting views with powerful filter capabilities that allow' . ' you to keep track of the most important events in your monitoring environment.' ) diff --git a/modules/monitoring/application/forms/StatehistoryForm.php b/modules/monitoring/application/forms/StatehistoryForm.php index 9fa1bdc26..f6c148d8c 100644 --- a/modules/monitoring/application/forms/StatehistoryForm.php +++ b/modules/monitoring/application/forms/StatehistoryForm.php @@ -1,10 +1,8 @@ setName('form_event_overview'); - $this->setSubmitLabel(mt('monitoring', 'Apply')); + $this->setSubmitLabel($this->translate('Apply')); } /** @@ -57,7 +55,7 @@ class StatehistoryForm extends Form } /** - * @see Form::createElements() + * {@inheritdoc} */ public function createElements(array $formData) { @@ -65,14 +63,14 @@ class StatehistoryForm extends Form 'select', 'from', array( - 'label' => mt('monitoring', 'From'), + 'label' => $this->translate('From'), 'value' => $this->getRequest()->getParam('from', strtotime('3 months ago')), 'multiOptions' => array( - strtotime('midnight 3 months ago') => mt('monitoring', '3 Months'), - strtotime('midnight 4 months ago') => mt('monitoring', '4 Months'), - strtotime('midnight 8 months ago') => mt('monitoring', '8 Months'), - strtotime('midnight 12 months ago') => mt('monitoring', '1 Year'), - strtotime('midnight 24 months ago') => mt('monitoring', '2 Years') + strtotime('midnight 3 months ago') => $this->translate('3 Months'), + strtotime('midnight 4 months ago') => $this->translate('4 Months'), + strtotime('midnight 8 months ago') => $this->translate('8 Months'), + strtotime('midnight 12 months ago') => $this->translate('1 Year'), + strtotime('midnight 24 months ago') => $this->translate('2 Years') ), 'class' => 'autosubmit' ) @@ -81,10 +79,10 @@ class StatehistoryForm extends Form 'select', 'to', array( - 'label' => mt('monitoring', 'To'), + 'label' => $this->translate('To'), 'value' => $this->getRequest()->getParam('to', time()), 'multiOptions' => array( - time() => mt('monitoring', 'Today') + time() => $this->translate('Today') ), 'class' => 'autosubmit' ) @@ -95,11 +93,11 @@ class StatehistoryForm extends Form 'select', 'objecttype', array( - 'label' => mt('monitoring', 'Object type'), + 'label' => $this->translate('Object type'), 'value' => $objectType, 'multiOptions' => array( - 'services' => mt('monitoring', 'Services'), - 'hosts' => mt('monitoring', 'Hosts') + 'services' => $this->translate('Services'), + 'hosts' => $this->translate('Hosts') ), 'class' => 'autosubmit' ) @@ -113,13 +111,13 @@ class StatehistoryForm extends Form 'select', 'state', array( - 'label' => mt('monitoring', 'State'), + 'label' => $this->translate('State'), 'value' => $serviceState, 'multiOptions' => array( - 'cnt_critical_hard' => mt('monitoring', 'Critical'), - 'cnt_warning_hard' => mt('monitoring', 'Warning'), - 'cnt_unknown_hard' => mt('monitoring', 'Unknown'), - 'cnt_ok' => mt('monitoring', 'Ok') + 'cnt_critical_hard' => $this->translate('Critical'), + 'cnt_warning_hard' => $this->translate('Warning'), + 'cnt_unknown_hard' => $this->translate('Unknown'), + 'cnt_ok' => $this->translate('Ok') ), 'class' => 'autosubmit' ) @@ -133,12 +131,12 @@ class StatehistoryForm extends Form 'select', 'state', array( - 'label' => mt('monitoring', 'State'), + 'label' => $this->translate('State'), 'value' => $hostState, 'multiOptions' => array( - 'cnt_up' => mt('monitoring', 'Up'), - 'cnt_down_hard' => mt('monitoring', 'Down'), - 'cnt_unreachable_hard' => mt('monitoring', 'Unreachable') + 'cnt_up' => $this->translate('Up'), + 'cnt_down_hard' => $this->translate('Down'), + 'cnt_unreachable_hard' => $this->translate('Unreachable') ), 'class' => 'autosubmit' ) diff --git a/modules/monitoring/application/locale/de_DE/LC_MESSAGES/monitoring.mo b/modules/monitoring/application/locale/de_DE/LC_MESSAGES/monitoring.mo index 96f792026..3be118e1c 100644 Binary files a/modules/monitoring/application/locale/de_DE/LC_MESSAGES/monitoring.mo and b/modules/monitoring/application/locale/de_DE/LC_MESSAGES/monitoring.mo differ diff --git a/modules/monitoring/application/locale/de_DE/LC_MESSAGES/monitoring.po b/modules/monitoring/application/locale/de_DE/LC_MESSAGES/monitoring.po index d21fec919..4d8b6c0c2 100644 --- a/modules/monitoring/application/locale/de_DE/LC_MESSAGES/monitoring.po +++ b/modules/monitoring/application/locale/de_DE/LC_MESSAGES/monitoring.po @@ -1,5 +1,5 @@ # Icinga Web 2 - Head for multiple monitoring backends. -# Copyright (C) 2014 Icinga Development Team +# Copyright (C) 2015 Icinga Development Team # This file is distributed under the same license as Monitoring Module. # FIRST AUTHOR , YEAR. # @@ -7,1574 +7,4550 @@ msgid "" msgstr "" "Project-Id-Version: Monitoring Module (2.0.0~alpha4)\n" "Report-Msgid-Bugs-To: dev@icinga.org\n" -"POT-Creation-Date: 2014-08-22 18:04+0200\n" -"PO-Revision-Date: 2014-08-22 18:05+0100\n" +"POT-Creation-Date: 2015-03-13 16:08+0100\n" +"PO-Revision-Date: 2015-03-13 16:48+0100\n" "Last-Translator: Thomas Gelf \n" +"Language: de_DE\n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.5.4\n" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:235 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:382 msgid " Down Hosts (Handled)" msgstr "Hosts Down (bestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:236 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:383 msgid " Down Hosts (Unhandled)" msgstr "Hosts Down (unbestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:257 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:350 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:404 msgid " Down Services (Handled)" msgstr "Hosts Down (bestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:258 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:351 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:405 msgid " Down Services (Unhandled)" -msgstr "" +msgstr "Services Down (Unbestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:41 -msgid " If you work with other administrators you may find it useful to share information about a host or service that is having problems if more than one of you may be working on it. Make sure you enter a brief description of what you are doing." -msgstr "Wenn du mit anderen Administratoren zusammenarbeitest wirst du es als nützlich empfinden, Informationen über Probleme auf Hosts oder Services an denen du arbeitest zu teilen. Stell sicher eine kurze Beschreibung von dem was du gerade machst zu hinterlassen." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:239 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:386 msgid " Pending Hosts" msgstr "Ungeprüfte Hosts" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:261 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:354 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:408 msgid " Pending Services" msgstr "Ungeprüfte Services" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:237 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:384 msgid " Unreachable Hosts (Handled)" msgstr "Nicht erreichbare Hosts (bestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:238 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:385 msgid " Unreachable Hosts (Unhandled)" msgstr "Nicht erreichbare Hosts (unbestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:259 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:352 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:406 msgid " Unreachable Services (Handled)" msgstr "Nicht erreichbare Services (bestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:260 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:353 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:407 msgid " Unreachable Services (Unhandled)" msgstr "Nicht erreichbare Services (unbestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:255 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:381 +msgid " Up Hosts" +msgstr "Hosts UP" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:401 +msgid " Up Services" +msgstr "Services UP" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:348 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:402 msgid " Warning Services (Handled)" msgstr "Service-Warnungen (bestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:256 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:349 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:403 msgid " Warning Services (Unhandled)" msgstr "Service-Warnungen (unbestätigt)" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:10 -#, php-format -msgid "%d Hosts DOWN" -msgstr "%d Hosts DOWN" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:73 +#, fuzzy, php-format +msgid "%d Active" +msgid_plural "%d Active" +msgstr[0] "Aktiv" +msgstr[1] "Aktiv" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:22 -#, php-format -msgid "%d Hosts PENDING" -msgstr "%d Hosts UNGEPRÜFT" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:109 +#, fuzzy, php-format +msgid "%d Disabled" +msgid_plural "%d Disabled" +msgstr[0] "Deaktiviert" +msgstr[1] "Deaktiviert" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:17 -#, php-format -msgid "%d Hosts UNREACHABLE" -msgstr "%d Hosts NICHT ERREICHBAR" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:35 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:91 +#, fuzzy, php-format +msgid "%d Passive" +msgid_plural "%d Passive" +msgstr[0] "Passiv" +msgstr[1] "Passiv" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:15 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:43 #, php-format -msgid "%d Hosts UP" -msgstr "%d Hosts UP" +msgid "%d hosts down on %s" +msgstr "%d hosts down auf %s" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:57 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:127 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:197 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:232 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:267 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:21 #, php-format -msgid "%d are not checked at all" -msgstr "%d werden nicht überwacht" +msgid "%d hosts ok on %s" +msgstr "%d hosts OK auf %s" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:32 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:102 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:172 -#, php-format -msgid "%d are passively checked" -msgstr "%d werden passiv überwacht" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:20 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:87 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:138 -#, php-format -msgid "%d hosts disabled" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:26 +#, fuzzy, php-format +msgid "%d hosts unreachable on %s" msgstr "%d deaktivierte Hosts" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:36 -#, php-format -msgid "%d hosts flapping" -msgstr "%d flapping Hosts" +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:64 +#, fuzzy, php-format +msgid "%d more ..." +msgstr "und %d weitere" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:62 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:132 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:202 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:237 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:272 -#, php-format -msgid "%d is not checked at all" -msgstr "%d wird nicht überwacht" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:37 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:107 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:177 -#, php-format -msgid "%d is passively checked" -msgstr "%d wird passiv überwacht" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:48 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:107 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:158 -#, php-format -msgid "%d services disabled" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:31 +#, fuzzy, php-format +msgid "%d services critical on %s" msgstr "%d deaktivierte Services" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:64 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:53 +#, fuzzy, php-format +msgid "%d services ok on %s" +msgstr "%d deaktivierte Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:48 #, php-format -msgid "%d services flapping" +msgid "%d services unknown on %s" +msgstr "%d services unbekannt auf %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:37 +#, fuzzy, php-format +msgid "%d services warning on %s" msgstr "%d flapping services" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hosts.phtml:105 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Plugin/Perfdata.php:437 #, php-format -msgid "%d unhandled services" -msgstr "%d unbestätigte Services" +msgid "%s %s (%s%%)" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/notifications.phtml:22 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:28 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:32 +#, php-format +msgid "%s ago" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:40 +#, php-format +msgctxt "timespan" +msgid "%s ago" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:57 +#, fuzzy, php-format +msgid "%s has been up and running with PID %d since %s" +msgstr "läuft mit PID %d" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:8 +#, fuzzy, php-format +msgid "%s hosts:" +msgstr "%d Hosts UP" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/not-running.phtml:5 +#, php-format +msgid "%s is currently not up and running" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:64 +#, fuzzy, php-format +msgid "%s is not running" +msgstr "läuft nicht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:43 #, php-format msgid "%s notications have been sent for this issue" msgstr "%s Benachrichtigungen wurden für dieses Problem versandt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:3 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Link.php:54 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:142 +#, php-format +msgctxt "Service running on host" +msgid "%s on %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:9 #, php-format msgid "%s services:" msgstr "%s Services:" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:91 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:84 +#, fuzzy, php-format +msgid "%u Acknowledged Host Problem" +msgid_plural "%u Acknowledged Host Problems" +msgstr[0] "Problem bestätigen" +msgstr[1] "Problem bestätigen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:84 +#, fuzzy, php-format +msgid "%u Acknowledged Service Problem" +msgid_plural "%u Acknowledged Service Problems" +msgstr[0] "Problem bestätigen" +msgstr[1] "Problem bestätigen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:17 +#, fuzzy, php-format +msgid "%u Active" +msgid_plural "%u Active" +msgstr[0] "Aktiv" +msgstr[1] "Aktiv" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:8 +#, fuzzy, php-format +msgid "%u Host" +msgid_plural "%u Hosts" +msgstr[0] "%d Hosts UP" +msgstr[1] "%d Hosts UP" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:10 +#, fuzzy, php-format +msgid "%u Host DOWN" +msgid_plural "%u Hosts DOWN" +msgstr[0] "%d Hosts DOWN" +msgstr[1] "%d Hosts DOWN" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:18 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:139 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:218 +#, fuzzy, php-format +msgid "%u Host Disabled" +msgid_plural "%u Hosts Disabled" +msgstr[0] "%d deaktivierte Hosts" +msgstr[1] "%d deaktivierte Hosts" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:48 +#, fuzzy, php-format +msgid "%u Host Flapping" +msgid_plural "%u Hosts Flapping" +msgstr[0] "%d flapping Hosts" +msgstr[1] "%d flapping Hosts" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:33 +#, fuzzy, php-format +msgid "%u Host PENDING" +msgid_plural "%u Hosts PENDING" +msgstr[0] "%d Hosts UNGEPRÜFT" +msgstr[1] "%d Hosts UNGEPRÜFT" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:29 +#, fuzzy, php-format +msgid "%u Host UNREACHABLE" +msgid_plural "%u Hosts UNREACHABLE" +msgstr[0] "%d Hosts NICHT ERREICHBAR" +msgstr[1] "%d Hosts NICHT ERREICHBAR" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:15 +#, fuzzy, php-format +msgid "%u Host UP" +msgid_plural "%u Hosts UP" +msgstr[0] "%d Hosts UP" +msgstr[1] "%d Hosts UP" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:8 +#, fuzzy, php-format +msgid "%u Service" +msgid_plural "%u Services" +msgstr[0] "Service" +msgstr[1] "Service" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:73 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:173 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:252 +#, fuzzy, php-format +msgid "%u Service Disabled" +msgid_plural "%u Services Disabled" +msgstr[0] "%d deaktivierte Services" +msgstr[1] "%d deaktivierte Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:103 +#, fuzzy, php-format +msgid "%u Service Flapping" +msgid_plural "%u Services Flapping" +msgstr[0] "%d flapping services" +msgstr[1] "%d flapping services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:46 +#, fuzzy, php-format +msgid "%u Unhandled Host Problem" +msgid_plural "%u Unhandled Host Problems" +msgstr[0] "Unbestätigte Hosts" +msgstr[1] "Unbestätigte Hosts" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:46 +#, fuzzy, php-format +msgid "%u Unhandled Service Problem" +msgid_plural "%u Unhandled Service Problems" +msgstr[0] "Unbestätigte Services" +msgstr[1] "Unbestätigte Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:13 +#, fuzzy, php-format +msgid "%u configured service:" +msgid_plural "%u configured services:" +msgstr[0] "%d unbestätigte Services" +msgstr[1] "%d unbestätigte Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:80 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:179 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:278 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:325 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:372 +#, fuzzy, php-format +msgid "%u is not checked at all" +msgid_plural "%u are not checked at all" +msgstr[0] "%d wird nicht überwacht" +msgstr[1] "%d wird nicht überwacht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:152 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:251 +#, fuzzy, php-format +msgid "%u is passively checked" +msgid_plural "%u are passively checked" +msgstr[0] "%d wird passiv überwacht" +msgstr[1] "%d wird passiv überwacht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:110 +#, fuzzy, php-format +msgid "%u unhandled service" +msgid_plural "%u unhandled services" +msgstr[0] "%d unbestätigte Services" +msgstr[1] "%d unbestätigte Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:73 +msgid "1 Year" +msgstr "1 Jahr" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:74 +msgid "2 Years" +msgstr "2 Jahre" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:70 +msgid "3 Months" +msgstr "3 Monate" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:97 msgid "4 Hours" msgstr "4 Stunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/notifications.phtml:17 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:71 +msgid "4 Months" +msgstr "4 Monate" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:72 +msgid "8 Months" +msgstr "8 Monate" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Plugin/Perfdata.php:437 +#, php-format +msgid "%s %s (%s%%)" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:453 +msgid "{title}: {value}m max. reaction time" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:443 +msgid "{title}: {value}m min. reaction time" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:296 +msgid "{title}:
    {value} of {sum} hosts are {label}" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:247 +msgid "{title}:
    {value} of {sum} services are {label}" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:38 #, php-format msgid "A notication has been sent for this issue %s ago" msgstr "Eine Benachrichtigung für dieses Problem wurde vor %s gesendet" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:93 -msgid "Accept passive host checks" -msgstr "Akzeptiere passive Host Checks" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:71 -msgid "Accept passive service checks" -msgstr "Akzeptiere passive Service-Checks" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:43 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:78 msgid "Ack removed" msgstr "Bestätigung entfernt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:38 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:73 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:39 msgid "Acknowledge" msgstr "Bestätigen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:116 -msgid "Acknowledge Problem" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:68 +#, fuzzy, php-format +msgid "Acknowledge %u unhandled host problem" +msgid_plural "Acknowledge %u unhandled host problems" +msgstr[0] "Problem bestätigen" +msgstr[1] "Problem bestätigen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:68 +#, fuzzy, php-format +msgid "Acknowledge %u unhandled service problem" +msgid_plural "Acknowledge %u unhandled service problems" +msgstr[0] "%d unbestätigte Services" +msgstr[1] "%d unbestätigte Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:71 +#, fuzzy +msgid "Acknowledge Host Problem" msgstr "Problem bestätigen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/comments.phtml:14 -msgid "Acknowledge all problems on the selected hosts or services" -msgstr "Bestätige alle Probleme auf den ausgewählten Hosts oder Services" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:184 +#, fuzzy +msgid "Acknowledge Host Problems" +msgstr "Problem bestätigen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicegroups.phtml:59 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicegroups.phtml:89 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicegroups.phtml:155 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicegroups.phtml:189 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicegroups.phtml:223 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hostgroups.phtml:56 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hostgroups.phtml:86 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hostgroups.phtml:152 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hostgroups.phtml:186 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hostgroups.phtml:220 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:16 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:86 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:156 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:48 +#, fuzzy +msgid "Acknowledge Service Problem" +msgstr "Problem bestätigen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:233 +#, fuzzy +msgid "Acknowledge Service Problems" +msgstr "Problem bestätigen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:34 +#, fuzzy +msgid "Acknowledge problem" +msgid_plural "Acknowledge problems" +msgstr[0] "Problem bestätigen" +msgstr[1] "Problem bestätigen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:46 +msgid "" +"Acknowledge this problem, suppress all future notifications for it and tag " +"it as being handled" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:75 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:31 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:130 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:229 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:9 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:9 msgid "Acknowledged" msgstr "Bestätigt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:45 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:51 msgid "Acknowledgement" msgstr "Bestätigung" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:945 -msgid "Acknowledgement has been sent" -msgstr "Bestätigung wurde gesendet" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:967 -msgid "Acknowledgement removal has been requested" -msgstr "Löschung der Bestätigung wurde angefragt" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:53 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:59 msgid "Acknowledgements" msgstr "Bestätigungen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:20 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:52 -msgid "Active" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:157 +#, fuzzy +msgid "Acknowledging problem.." +msgid_plural "Acknowledging problems.." +msgstr[0] "Problem bestätigen" +msgstr[1] "Problem bestätigen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/actions.phtml:44 +#, fuzzy +msgid "Actions" msgstr "Aktiv" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/flags.phtml:24 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:70 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:98 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:24 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:24 +#, fuzzy +msgid "Active And Passive Checks Disabled" +msgstr "Passive Checks nicht mehr akzeptieren" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:39 msgid "Active Checks" msgstr "Aktive Checks" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/comments.phtml:47 -msgid "Add comment" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:72 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:100 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:22 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:22 +#, fuzzy +msgid "Active Checks Disabled" +msgstr "Aktive Checks" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:88 +msgid "Active Host Checks Being Executed" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:97 +msgid "Active Service Checks Being Executed" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:120 +#, fuzzy +msgid "Active checks" +msgstr "Aktive Checks" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:83 +#, fuzzy +msgid "Add Host Comment" +msgstr "Kommentar absenden" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:48 +msgid "Add New Backend" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:164 +msgid "Add New Instance" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:60 +#, fuzzy +msgid "Add Service Comment" msgstr "Kommentar hinzufügen" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:109 -msgid "All Events" -msgstr "Alle Ereignisse" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:14 +msgid "Add a new comment to this host" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:28 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:95 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:146 -msgid "All hosts enabled" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:25 +msgid "Add a new comment to this service" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:28 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:8 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:19 +#, fuzzy +msgid "Add comment" +msgid_plural "Add comments" +msgstr[0] "Kommentar hinzufügen" +msgstr[1] "Kommentar hinzufügen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:83 +#, fuzzy +msgid "Adding comment.." +msgid_plural "Adding comments.." +msgstr[0] "Kommentar hinzufügen" +msgstr[1] "Kommentar hinzufügen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:135 +msgid "Address" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:201 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:44 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:48 +msgid "Alert Summary" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:11 +msgid "Alert summary" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:377 +msgid "Alias" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:158 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:237 +#, fuzzy +msgid "All Hosts Enabled" msgstr "Alle Hosts aktiviert" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/services.phtml:3 -msgid "All services configured on this host" -msgstr "Alle auf diesem Host konfigurierten Services" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php:26 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:28 +#, fuzzy +msgid "All Services" +msgstr "Services" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:56 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:115 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:166 -msgid "All services enabled" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:92 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:192 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:271 +#, fuzzy +msgid "All Services Enabled" msgstr "Alle Services aktiviert" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CommandForm.php:93 -msgid "Author (Your Name)" -msgstr "Autor (Dein Name)" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:178 +#, fuzzy +msgid "All backends are disabled" +msgstr "Alle Services aktiviert" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CustomNotificationForm.php:63 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:16 +msgid "Allow acknowledging host and service problems" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:24 +msgid "Allow adding and deleting host and service comments" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:8 +msgid "Allow all commands" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:28 +msgid "Allow commenting on hosts and services" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:32 +msgid "Allow deleting host and service comments" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:44 +msgid "Allow deleting host and service downtimes" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:52 +msgid "" +"Allow processing commands for toggling features on an instance-wide basis" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:56 +msgid "" +"Allow processing commands for toggling features on host and service objects" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:48 +#, fuzzy +msgid "Allow processing host and service check results" +msgstr "Service-Checks verfolgen" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:20 +#, fuzzy +msgid "Allow removing problem acknowledgements" +msgstr "Problembestätigung entfernen" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:36 +msgid "Allow scheduling and deleting host and service downtimes" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:12 +#, fuzzy +msgid "Allow scheduling host and service checks" +msgstr "Service-Checks verfolgen" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:40 +msgid "Allow scheduling host and service downtimes" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:60 +msgid "Allow sending custom notifications for hosts and services" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:21 +msgid "Apply" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:131 +msgid "Are you sure you want to remove this instance?" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:300 +msgid "Author" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:33 +msgid "Average" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:108 +msgid "Average services per host" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:439 +msgid "Avg (min)" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:78 +#, php-format +msgid "Backend \"%s\" successfully removed." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:216 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:28 +msgid "Backend Name" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:226 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:44 +msgid "Backend Type" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:75 +msgid "Backends" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:77 msgid "Broadcast" msgstr "Broadcast" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:52 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:52 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:184 msgid "CRITICAL" msgstr "KRITISCH" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:140 -msgid "Check Output" -msgstr "Check Ausgabe" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:62 +#, fuzzy +msgctxt "icinga.state" +msgid "CRITICAL" +msgstr "KRITISCH" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:116 -msgid "Check Result" -msgstr "Check-Ergebnis" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/checksource.phtml:3 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checksource.phtml:4 msgid "Check Source" msgstr "Check-Quelle" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/RescheduleNextCheckForm.php:33 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:51 msgid "Check Time" msgstr "Ausführungszeit" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:32 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:61 +#, fuzzy +msgid "Check attempts" +msgstr "Check Ausgabe" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:70 msgid "Check execution time" msgstr "Check-Ausführungsdauer" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:38 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:76 msgid "Check latency" msgstr "Check-Latenz" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:8 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:37 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:39 msgid "Check now" msgstr "Jetzt prüfen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:253 -msgid "Child Objects" -msgstr "Kind-Objekte" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php:78 +msgid "" +"Check this to not to validate connectivity with the given Livestatus socket" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/command.phtml:7 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/IdoResourcePage.php:77 +msgid "" +"Check this to not to validate connectivity with the given database server" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:38 +#, fuzzy +msgid "Child Hosts" +msgstr "Unbestätigte Hosts" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/SecurityConfigForm.php:58 +msgid "" +"Comma separated case insensitive list of protected custom variables. Use * " +"as a placeholder for zero or more wildcard characters. Existance of those " +"custom variables will be shown, but their values will be masked." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/command.phtml:9 msgid "Command" msgstr "Befehl" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:290 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:721 -msgid "Command has been sent, active checks will be disabled" -msgstr "Befehl wurde gesendet, aktive Checks werden deaktiviert" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php:30 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:64 +#, fuzzy +msgid "Command File" +msgstr "Befehl" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:315 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:741 -msgid "Command has been sent, active checks will be enabled" -msgstr "Befehl wurde gesendet, aktive Checks werden aktiviert" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:49 +#, fuzzy +msgid "Commands" +msgstr "Befehl" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:334 -msgid "Command has been sent, check will be rescheduled" -msgstr "Befehl wurde gesendet, Check wird neu geplant" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:701 -msgid "Command has been sent, checks will be rescheduled" -msgstr "Befehl wurde gesendet, Checks werden neu geplant" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:772 -msgid "Command has been sent, event handlers will be disabled" -msgstr "Befehl wurde gesendet, Eventhandler werden deaktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:804 -msgid "Command has been sent, event handlers will be enabled" -msgstr "Befehl wurde gesendet, Eventhandler werden aktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:837 -msgid "Command has been sent, flap detection will be disabled" -msgstr "Befehl wurde gesendet, Flap-Erkennung wird deaktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:870 -msgid "Command has been sent, flap detection will be enabled" -msgstr "Befehl wurde gesendet, Flap-Erkennung wird aktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1047 -msgid "Command has been sent, monitoring process will restart now" -msgstr "Befehl wurde gesendet, der Monitoring-Prozess wird jetzt neustarten" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:510 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:538 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:661 -msgid "Command has been sent, notifications will be disabled" -msgstr "Befehl wurde gesendet, Benachrichtigungen werden deaktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:568 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:682 -msgid "Command has been sent, notifications will be enabled" -msgstr "Befehl wurde gesendet, Benachrichtigungen werden aktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:391 -msgid "Command has been sent, obsessing will be disabled" -msgstr "Befehl wurde gesendet, Verfolgung wird deaktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:425 -msgid "Command has been sent, obsessing will be enabled" -msgstr "Befehl wurde gesendet, Verfolgung wird aktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:492 -msgid "Command has been sent, passive check results will be accepted" -msgstr "Befehl wurde gesendet, passive Checks werden akzeptiert werden" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:459 -msgid "Command has been sent, passive check results will be refused" -msgstr "Befehl wurde gesendet, passive Checks werden abgewiesen werden" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1069 -msgid "Command has been sent, performance data processing will be disabled" -msgstr "Befehl wurde gesendet, Performancedatenverarbeitung wird deaktiviert werden" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1091 -msgid "Command has been sent, performance data processing will be enabled" -msgstr "Befehl wurde gesendet, Performancedatenverarbeitung wird aktiviert werden" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1026 -msgid "Command has been sent, process will shut down" -msgstr "Befehl wurde gesendet, der Monitoringprozess wird herunterfahren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:32 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:28 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:36 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CommentForm.php:30 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CustomNotificationForm.php:34 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:134 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:49 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:43 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:65 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:52 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:62 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:67 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:46 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:63 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:54 msgid "Comment" msgstr "Kommentar" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:33 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:489 +#, fuzzy +msgid "Comment Timestamp" +msgstr "Kommentare" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:492 +#, fuzzy +msgid "Comment Type" +msgstr "Kommentar" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:68 msgid "Comment deleted" msgstr "Kommentar gelöscht" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:909 -msgid "Comment removal has been requested" -msgstr "Löschung des Kommentars wurde angefordert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:42 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:36 msgid "Comment was caused by a downtime." msgstr "Kommentar wurde durch eine Downtime erstellt." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:32 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:26 msgid "Comment was caused by a flapping host or service." msgstr "Kommentar wurde durch einen flappenden Host oder service erstellt." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:46 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:40 msgid "Comment was caused by an acknowledgement." msgstr "Kommentar wurde durch eine Bestätigung erstellt." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:31 msgid "Comment was created by an user." msgstr "Kommentar wurde von einem Benutzer erstellt." -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:86 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:48 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/comments.phtml:45 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/comments.phtml:6 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:163 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:468 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:54 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:66 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:2 msgid "Comments" msgstr "Kommentare" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:78 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/contacts.phtml:33 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:196 +#, php-format +msgid "Configuration for backend %s is disabled" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:79 +msgid "" +"Configure how to protect your monitoring environment against prying eyes" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:74 +msgid "Configure how to retrieve monitoring information" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:434 +#, fuzzy +msgid "Contact Groups" +msgstr "Kontaktgruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:4 +#, fuzzy +msgid "Contact details" +msgstr "Kontakte" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:159 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/contacts.phtml:37 msgid "Contactgroups" msgstr "Kontaktgruppen" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:90 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/contacts.phtml:14 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:155 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:351 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/contacts.phtml:17 msgid "Contacts" msgstr "Kontakte" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:160 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:299 +msgid "Contains host states of each service group." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:240 +msgid "Contains service states for each service group." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:55 +msgid "Could not find any valid monitoring backend resources" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:53 +msgid "Create New Instance" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:9 +#, fuzzy +msgid "Create New Monitoring Backend" +msgstr "Monitoring-Prozess neu starten" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:264 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:118 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 msgid "Critical" msgstr "Kritisch" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:101 -msgid "Critical Events" -msgstr "Kritische Ereignisse" - -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:45 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:123 msgid "Current Downtimes" msgstr "Aktuelle Downtimes" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:584 -msgid "Custom notification has been sent" -msgstr "Benutzerdefinierte Bestätigung wurde gesendet" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:227 +#, fuzzy +msgid "Current Host State" +msgstr "Host-Status" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CustomNotificationForm.php:52 -msgid "Custom notifications normally follow the regular notification logic in Icinga. Selecting this option will force the notification to be sent out, regardless of time restrictions, whether or not notifications are enabled, etc." -msgstr "Benutzeredefinierte Benachrichtigungen verhalten sich gemäß der gewöhnlichen Benachrichtigungslogik in Icinga. Wenn du diese Option wählst wird eine Benachrichtigung erzwungen. Einschränkungen durch Zeitspannen und durch eventuell deaktivierte Benachrichtigungen werden dabei ignoriert." +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:217 +#, fuzzy +msgid "Current Incidents" +msgstr "Aktuelle Downtimes" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:47 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:223 +#, fuzzy +msgid "Current Service State" +msgstr "Service-Zustand" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:133 +#, fuzzy +msgid "Current State" +msgstr "Aktuelle Downtimes" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Host.php:176 msgid "DOWN" msgstr "DOWN" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/DelayNotificationForm.php:51 -msgid "Delay Notification" -msgstr "Benachrichtigung verzögern" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:57 +#, fuzzy +msgctxt "icinga.state" +msgid "DOWN" +msgstr "DOWN" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/comments.phtml:13 -msgid "Delay Notifications" -msgstr "Benachrichtigungen verzögern" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:104 +msgid "Database Name" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:997 -msgid "Delete Downtime" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:83 +msgid "Database Resource" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:92 +#, fuzzy +msgid "Database Type" +msgstr "Downtime-Typ" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:643 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:646 +msgid "Day" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:468 +msgid "Defect Chart" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:489 +msgid "Defects" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:46 +msgid "Define what should be done with the child hosts of the hosts." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php:64 +#, fuzzy +msgid "Delete this comment" msgstr "Downtime löschen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:999 -msgid "Delete a single downtime with the id shown above" -msgstr "Lösche eine einzelne Downtime mit der oben gezeigten ID." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php:64 +#, fuzzy +msgid "Delete this downtime" +msgstr "Downtime löschen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:278 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:713 -msgid "Disable Active Checks" -msgstr "Aktive Checks deaktivieren" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php:89 +#, fuzzy +msgid "Deleting comment.." +msgstr "Kommentar entfernen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:753 -msgid "Disable Event Handler" -msgstr "Event-Handler deaktivieren" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php:89 +#, fuzzy +msgid "Deleting downtime.." +msgstr "Downtime löschen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:816 -msgid "Disable Flapping Detection" -msgstr "Flap-Erkennung deaktivieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:523 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:652 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ProcessController.php:99 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:24 msgid "Disable Notifications" msgstr "Benachrichtigungen deaktivieren" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1060 -msgid "Disable Performance Data" -msgstr "Performancedaten deaktivieren" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:208 +msgid "Disable This Backend" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:714 -msgid "Disable active checks for this host and its services." -msgstr "Deaktiviere aktive Service-Checks für diesen Host und seine Service-Checks." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:283 -msgid "Disable active checks for this object." -msgstr "Deaktiviere aktive Service-Checks für dieses Objekt." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:281 -msgid "Disable active checks on a program-wide basis." -msgstr "Deaktiviere applikationsweit sämtliche aktiven Service-Checks." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:756 -msgid "Disable event handler for the whole system." -msgstr "Dektiviere Event-Handler für das ganze System." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:758 -msgid "Disable event handler for this object." -msgstr "Dektiviere Event-Handler für dieses Objekt." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:821 -msgid "Disable flapping detection for this object." -msgstr "Deaktiviere die Flap-Erkennnung für dieses Objekt." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:819 -msgid "Disable flapping detection on a program-wide basis." -msgstr "Deaktiviere die Flap-Erkennnung applikationsweit." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:49 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:67 msgid "Disable notifications for a specific time on a program-wide basis" -msgstr "Deaktivieren Benachrichtigungen applikationsweit für eine bestimmte Zeitspanne." +msgstr "" +"Deaktivieren Benachrichtigungen applikationsweit für eine bestimmte " +"Zeitspanne." -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:526 -msgid "Disable notifications on a program-wide basis." -msgstr "Deaktiviere die Flap-Erkennnung applikationsweit." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:69 +msgid "Disable temporarily" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:372 -msgid "Disable obsessing on a program-wide basis." -msgstr "Deaktiviere die Verfolgung applikationsweit." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:61 +#, fuzzy +msgid "Disabling host and service notifications.." +msgstr "Benachrichtigungen deaktivieren" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:440 -msgid "Disable passive checks on a program-wide basis." -msgstr "Deaktiviere Passive Checks applikationsweit." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1061 -msgid "Disable processing of performance data on a program-wide basis." -msgstr "Deaktiviere die Verarbeitung von Performance-Daten applikationsweit." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:40 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:72 -msgid "Disabled" -msgstr "Deaktiviert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:256 -msgid "Do nothing with child objects" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:41 +#, fuzzy +msgid "Do nothing with child hosts" msgstr "Mach nichts mit Kind-Objekten" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:205 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:314 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:139 msgid "Down" msgstr "Down" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:41 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:35 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:68 msgid "Downtime" msgstr "Downtime" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:77 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:93 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:88 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:120 msgid "Downtime End" msgstr "Downtime Ende" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:72 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:88 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:83 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:115 msgid "Downtime Start" msgstr "Downtime Beginn" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:185 -msgid "Downtime Type" -msgstr "Downtime-Typ" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1006 -msgid "Downtime removal has been requested" -msgstr "Entfernung dieser Downtime wurde angefordert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:640 -msgid "Downtime removal requested" -msgstr "Entfernung dieser Downtime wurde angefordert" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:88 msgid "Downtime removed" msgstr "Downtime entfernt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:603 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:621 -msgid "Downtime scheduling requested" -msgstr "Die Planung dieser Downtime wurde angefordert" - -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:82 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/downtime.phtml:55 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:167 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:269 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:56 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:2 msgid "Downtimes" msgstr "Downtimes" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/contacts.phtml:28 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/contact.phtml:18 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:305 +#, fuzzy +msgid "Duration" +msgstr "Flexible Dauer" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:33 +msgid "Edit Existing Backend" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:150 +msgid "Edit Existing Instance" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:27 +#, fuzzy, php-format +msgid "Edit monitoring backend %s" +msgstr "Monitoring-Prozess neu starten" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:71 +#, fuzzy, php-format +msgid "Edit monitoring instance %s" +msgstr "Monitoring-Prozess neu starten" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:378 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:28 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:22 msgid "Email" msgstr "E-Mail" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:305 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:733 -msgid "Enable Active Checks" -msgstr "Aktive Checks aktivieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:785 -msgid "Enable Event Handler" -msgstr "Eventhandler aktivieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:849 -msgid "Enable Flapping Detection" -msgstr "Flap-Erkennung aktivieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:553 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:673 -msgid "Enable Notifications" -msgstr "Benachrichtigungen aktivieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1082 -msgid "Enable Performance Data" -msgstr "Performancedaten aktivieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:734 -msgid "Enable active checks for this host and its services." -msgstr "Aktive Checks für diesen Host und seine Services aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:309 -msgid "Enable active checks for this object." -msgstr "Aktive Checks für dieses Objekt aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:307 -msgid "Enable active checks on a program-wide basis." -msgstr "Aktive Checks anwendungsweit aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:790 -msgid "Enable event handler for this object." -msgstr "Event-Handler für dieses Objekt aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:788 -msgid "Enable event handlers on the whole system." -msgstr "Event-Handler für das gesamte System aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:854 -msgid "Enable flapping detection for this object." -msgstr "Flap-Erkennung für dieses Objekt aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:852 -msgid "Enable flapping detection on a program-wide basis." -msgstr "Flap-Erkennung anwendungsweit aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:556 -msgid "Enable notifications on a program-wide basis." -msgstr "Benachrichtigungen anwendungsweit aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:406 -msgid "Enable obsessing on a program-wide basis." -msgstr "Verfolgung anwendungsweit aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:474 -msgid "Enable passive checks on a program-wide basis." -msgstr "Passive Checks anwendungsweit aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1083 -msgid "Enable processing of performance data on a program-wide basis." -msgstr "Performancedaten-Verarbeitung anwendungsweit aktivieren." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:173 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:302 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:88 msgid "End Time" msgstr "Endzeitpunkt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:63 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:69 msgid "Ended downtimes" msgstr "Beendete Downtimes" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:238 -msgid "Enter here the duration of the downtime. Icinga will automatically delete the downtime after this time expired." -msgstr "Die gewünschte Dauer der Downtime hier eintragen. Icinga wird die Downtime nach Ablauf dieser Zeit automatisch löschen." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:159 +#, fuzzy +msgid "" +"Enter here the duration of the downtime. The downtime will be automatically " +"deleted after this time expired." +msgstr "" +"Die gewünschte Dauer der Downtime hier eintragen. Icinga wird die Downtime " +"nach Ablauf dieser Zeit automatisch löschen." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:81 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/DisableNotificationWithExpireForm.php:32 -msgid "Enter the expire date/time for this acknowledgement here. Icinga will delete the acknowledgement after this date expired." -msgstr "Den gewünschten Verfallszeitpunkt der Downtime hier eintragen. Icinga wird die Downtime bei Überschreitung dieses Zeitpunkts löschen." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:90 +#, fuzzy +msgid "" +"Enter the expire date and time for this acknowledgement here. Icinga will " +"delete the acknowledgement after this time expired." +msgstr "" +"Den gewünschten Verfallszeitpunkt der Downtime hier eintragen. Icinga wird " +"die Downtime bei Überschreitung dieses Zeitpunkts löschen." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/flags.phtml:46 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:299 +#, fuzzy +msgid "Entry Time" +msgstr "Endzeitpunkt" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:183 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:390 +#, fuzzy +msgid "Event Grid" +msgstr "Ereignisse" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:75 msgid "Event Handler" msgstr "Eventhandler" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:124 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:126 -msgid "Event handlers" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:206 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:208 +#, fuzzy +msgid "Event Handlers" msgstr "Eventhandler" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:104 -msgid "Eventhandlers enabled" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:106 +#, fuzzy +msgid "Event Handlers Enabled" msgstr "Eventhandler aktiviert" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:108 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:188 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:613 +#, fuzzy +msgid "Event Overview" +msgstr "Übersicht" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:187 msgid "Events" msgstr "Ereignisse" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:82 -msgid "Execute active host checks" -msgstr "Aktiven Host Check ausführen" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:127 +#, fuzzy +msgid "Execution time" +msgstr "Check-Ausführungsdauer" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:60 -msgid "Execute active service checks" -msgstr "Aktive Service-Checks ausführen" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:493 +msgid "Expiration" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:77 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/DisableNotificationWithExpireForm.php:28 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:43 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:87 msgid "Expire Time" msgstr "Verfallszeitpunkt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:43 msgid "Expires" msgstr "Verfällt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:144 -msgid "Fill in the check output string which should be send to Icinga." -msgstr "Ausgabetext des Checkergebnisses welches an Icinga gesendet werden soll hier eintragen." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:29 +#, fuzzy +msgid "Feature Commands" +msgstr "Liste unterstützter Befehle" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:155 -msgid "Fill in the performance data string which should be send to Icinga." -msgstr "Performancedaten des Checkergebnisses welches an Icinga gesendet werden soll hier eintragen." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:52 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:107 msgid "Fixed" msgstr "Fix" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/contact.phtml:35 -msgid "Flags (host)" -msgstr "Flags (Host)" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/contact.phtml:31 -msgid "Flags (service)" -msgstr "Flags (Service)" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/flags.phtml:57 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:84 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:6 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:8 msgid "Flap Detection" msgstr "Flap-Erkennung" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:6 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:8 -msgid "Flap detection" -msgstr "Flap-Erkennung" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:137 -msgid "Flap detection enabled" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:115 +#, fuzzy +msgid "Flap Detection Enabled" msgstr "Flap-Erkennung aktiv" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:31 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:47 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:86 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:25 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:61 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:57 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:81 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:93 msgid "Flapping" msgstr "Flapping" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:63 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:66 +#, fuzzy +msgid "Flapping Stopped" +msgstr "Flapping beendet" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:98 msgid "Flapping stopped" msgstr "Flapping beendet" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:53 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:108 msgid "Flexible" msgstr "Flexibel" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:209 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:157 msgid "Flexible Duration" msgstr "Flexible Dauer" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/RescheduleNextCheckForm.php:46 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:62 msgid "Force Check" msgstr "Check erzwingen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CustomNotificationForm.php:50 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:64 msgid "Forced" msgstr "Erzwungen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:31 -msgid "Global host event handler" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:67 +msgid "From" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:47 +#, fuzzy +msgid "Global Host Event Handler" msgstr "Globaler Host Event Handler" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:35 -msgid "Global service event handler" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:41 +#, fuzzy +msgid "Global Service Event Handler" msgstr "Globaler Service Event Handler" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:34 -#, php-format -msgid "Handled services with state %s" -msgstr "Bestätigte Services mit Status %s" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:43 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:49 msgid "Hard state changes" msgstr "Harte Status-Änderungen" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:98 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:327 +msgid "Healing Chart" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:180 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:258 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:69 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:233 msgid "History" msgstr "Historie" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:37 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:297 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:490 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:211 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:31 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:63 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:96 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:186 +#, fuzzy +msgid "Host" +msgstr "Hosts" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml:6 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml:21 +#, php-format +msgid "Host (%u)" +msgid_plural "Hosts (%u)" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:229 +#, fuzzy +msgid "Host Address" +msgstr "Host-Probleme" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:133 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:155 +#, fuzzy +msgid "Host Checks" +msgstr "Aktive Checks" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:25 +#, fuzzy +msgid "Host Group" +msgstr "Hostgruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:298 +#, fuzzy +msgid "Host Group Chart" +msgstr "Hostgruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:596 +#, fuzzy +msgid "Host Group Name" +msgstr "Hostgruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:561 +#, fuzzy +msgid "Host Groups" +msgstr "Hostgruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:381 +#, fuzzy +msgid "Host Notification Timeperiod" +msgstr "Host-Benachrichtigungszeitraum" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:111 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:227 msgid "Host Problems" msgstr "Host-Probleme" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/MultiController.php:60 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/MultiController.php:128 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:226 +#, fuzzy +msgid "Host Severity" +msgstr "Host-Status" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:64 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:158 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:85 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:207 msgid "Host State" msgstr "Host-Status" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/contacts.phtml:44 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/contact.phtml:43 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:2 +#, fuzzy +msgid "Host and Service Checks" +msgstr "Host- und Servicechecks" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/disable-notifications.phtml:8 +#, fuzzy +msgid "Host and service notifications are already disabled." +msgstr "Befehl wurde gesendet, Benachrichtigungen werden deaktiviert" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:34 +#, fuzzy +msgid "Host not found" +msgstr "Kein Host gefunden" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:48 msgid "Host notification period" msgstr "Host-Benachrichtigungszeitraum" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:2 -msgid "Host- and Servicechecks" -msgstr "Host- und Servicechecks" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:40 +msgid "Host or service not found" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:74 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:90 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:151 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml:16 msgid "Hostgroups" msgstr "Hostgruppen" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:58 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:194 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicegroups.phtml:28 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hostgroups.phtml:25 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:7 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml:37 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/summary.phtml:5 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:134 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:228 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:657 +#, fuzzy +msgid "Hostname" +msgstr "Host-Status" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:33 +msgid "Hostname or address of the remote Icinga instance" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:88 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:139 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:302 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:80 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:97 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:101 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:84 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:7 msgid "Hosts" msgstr "Hosts" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:217 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:640 +#, fuzzy +msgid "Hour" +msgstr "Stunden" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:137 msgid "Hours" msgstr "Stunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:66 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:44 +msgid "" +"Icinga Web 2 will protect your monitoring environment against prying eyes " +"using the configuration specified below:" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:76 +#, php-format +msgid "" +"Icinga Web 2 will retrieve information from your monitoring environment " +"using a backend called \"%s\" and the specified resource below:" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:75 +#, php-format +msgid "" +"Icinga Web 2 will use the named pipe located at \"%s\" to send commands to " +"your monitoring instance." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:48 +#, php-format +msgid "" +"Icinga Web 2 will use the named pipe located on a remote machine at \"%s\" " +"to send commands to your monitoring instance by using the connection details " +"listed below:" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:74 msgid "If the acknowledgement should expire, check this option." msgstr "Diese Option aktivieren, wenn die Bestätigung verfallen soll" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:110 -msgid "If you do not want an acknowledgement notification to be sent out to the appropriate contacts, uncheck this option." -msgstr "Wenn du keine Bestätigungsbenachrichtigung zu den jeweiligen Kontakten senden willst, deaktiviere diese Option." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:80 +msgid "" +"If you check this option, a notification is sent to all normal and escalated " +"contacts." +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:198 -msgid "If you select the fixed option, the downtime will be in effect between the start and end times you specify whereas a flexible downtime starts when the service enters a non-OK state (sometime between the start and end times you specified) and lasts as long as the duration of time you enter. The duration fields do not apply for fixed downtime." -msgstr "Wenn du die Option \"fix\" wählst, wird die Downtime zwischen dem gewählten Start- und Endzeitpunkt aktiv sein. Bei einer flexiblen Downtime beginnt die Downtime sobald der Dienst (irgendwann zwischen dem gewählten Start- und Endzeitpunkt) einen nicht-OK Zustand erreicht, und dauert so lange, wie im Feld \"Dauer\" angegeben. Diese Dauer gibt es für fixe Downtimes nicht." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:67 +#, fuzzy +msgid "" +"If you check this option, a notification is sentregardless of the current " +"time and whether notifications are enabled." +msgstr "" +"Wenn diese Option aktiviert wird, erzwingt Icinga einen Check unabhängig vom " +"eingeplanten zeitraum und auch dann, wenn aktive Checks nicht aktiv sind." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/RescheduleNextCheckForm.php:49 -msgid "If you select this option, Icinga will force a check regardless of both what time the scheduled check occurs and whether or not checks are enabled." -msgstr "Wenn diese Option aktiviert wird, erzwingt Icinga einen Check unabhängig vom eingeplanten zeitraum und auch dann, wenn aktive Checks nicht aktiv sind." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:126 +msgid "" +"If you do not want an acknowledgement notification to be sent out to the " +"appropriate contacts, uncheck this option." +msgstr "" +"Wenn du keine Bestätigungsbenachrichtigung zu den jeweiligen Kontakten " +"senden willst, deaktiviere diese Option." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CommentForm.php:49 -msgid "If you uncheck this option, the comment will automatically be deleted the next time Icinga is restarted." -msgstr "Wenn diese Option deaktiviert wird, wird der Kommentar beim nächsten Neustart von Icinga automatisch gelöscht." +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:124 +msgid "" +"If you have still any environments or views referring to this instance, you " +"won't be able to send commands anymore after deletion." +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:97 -msgid "If you want the acknowledgement to disable notifications until the host/service recovers, check this option." -msgstr "Diese Option aktivieren, wenn die Bestätigung Benachrichtigungen unterdrücken soll, bis der Host/Service sich erholt." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:101 +#, fuzzy +msgid "" +"If you select the fixed option, the downtime will be in effect between the " +"start and end times you specify whereas a flexible downtime starts when the " +"host or service enters a problem state sometime between the start and end " +"times you specified and lasts as long as the duration time you enter. The " +"duration fields do not apply for fixed downtimes." +msgstr "" +"Wenn du die Option \"fix\" wählst, wird die Downtime zwischen dem gewählten " +"Start- und Endzeitpunkt aktiv sein. Bei einer flexiblen Downtime beginnt die " +"Downtime sobald der Dienst (irgendwann zwischen dem gewählten Start- und " +"Endzeitpunkt) einen nicht-OK Zustand erreicht, und dauert so lange, wie im " +"Feld \"Dauer\" angegeben. Diese Dauer gibt es für fixe Downtimes nicht." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CommentForm.php:35 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CustomNotificationForm.php:39 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:139 -msgid "If you work with other administrators, you may find it useful to share information about a host or service that is having problems if more than one of you may be working on it. Make sure you enter a brief description of what you are doing." -msgstr "Wenn du mit anderen Administratoren zusammenarbeitest, wirst du es nützlich finden, Informationen zu Hosts oder Services mit Problemen zu teilen, insbesondere dann, wenn gemeinsam daran gearbeitet wird. Stell sicher eine kurze Beschreibung zu deiner Tätigkeit hier einzutragen." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:64 +msgid "" +"If you select this option, Icinga will force a check regardless of both what " +"time the scheduled check occurs and whether or not checks are enabled." +msgstr "" +"Wenn diese Option aktiviert wird, erzwingt Icinga einen Check unabhängig vom " +"eingeplanten zeitraum und auch dann, wenn aktive Checks nicht aktiv sind." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:55 -msgid "If you would like the comment to remain even when the acknowledgement is removed, check this option." -msgstr "Diese Option aktivieren, wenn der Kommentar auch nach dem Löschen der Bestätigung bestehen bleiben soll" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:58 +msgid "" +"If you uncheck this option, the comment will automatically be deleted the " +"next time Icinga is restarted." +msgstr "" +"Wenn diese Option deaktiviert wird, wird der Kommentar beim nächsten " +"Neustart von Icinga automatisch gelöscht." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:42 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:48 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:114 +#, fuzzy +msgid "" +"If you want the acknowledgement to disable notifications until the host or " +"service recovers, check this option." +msgstr "" +"Diese Option aktivieren, wenn die Bestätigung Benachrichtigungen " +"unterdrücken soll, bis der Host/Service sich erholt." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:51 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:45 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:67 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:54 +#, fuzzy +msgid "" +"If you work with other administrators, you may find it useful to share " +"information about the the host or service that is having problems. Make sure " +"you enter a brief description of what you are doing." +msgstr "" +"Wenn du mit anderen Administratoren zusammenarbeitest, wirst du es nützlich " +"finden, Informationen zu Hosts oder Services mit Problemen zu teilen, " +"insbesondere dann, wenn gemeinsam daran gearbeitet wird. Stell sicher eine " +"kurze Beschreibung zu deiner Tätigkeit hier einzutragen." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:63 +msgid "" +"If you would like the comment to remain even when the acknowledgement is " +"removed, check this option." +msgstr "" +"Diese Option aktivieren, wenn der Kommentar auch nach dem Löschen der " +"Bestätigung bestehen bleiben soll" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:56 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:65 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:89 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:83 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:17 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:17 msgid "In Downtime" msgstr "In Downtime" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:6 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:147 +msgid "" +"In case it's desired that a TCP connection is being used by Icinga Web 2 to " +"access a Livestatus interface, the Sockets module for PHP is required." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:58 +msgid "Instance" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:160 +#, php-format +msgid "Instance \"%s\" created successfully." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:163 +#, php-format +msgid "Instance \"%s\" edited successfully." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:114 +#, php-format +msgid "Instance \"%s\" successfully removed." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:191 +msgid "Instance Name" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:200 +#, fuzzy +msgid "Instance Type" +msgstr "Downtime-Typ" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:74 +msgid "Instance already exists" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:71 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:119 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:138 +msgid "Instance name missing" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:50 +#, php-format +msgid "Invalid instance type \"%s\" given" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:296 +msgid "Is In Effect" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/WelcomePage.php:42 +msgid "" +"It offers various status and reporting views with powerful filter " +"capabilities that allow you to keep track of the most important events in " +"your monitoring environment." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Label" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:136 +#, fuzzy +msgid "Last Check" +msgstr "Letzter Check" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:77 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:93 +#, fuzzy +msgid "Last Comment: " +msgstr "Kommentar absenden" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:31 +msgid "Last External Command Check" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:230 +#, fuzzy +msgid "Last Host Check" +msgstr "Letzter Check" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:35 +msgid "Last Log File Rotation" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:24 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:24 +#, fuzzy +msgid "Last Problem" +msgstr "Host-Probleme" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:225 +#, fuzzy +msgid "Last Service Check" +msgstr "Letzter Check" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:27 +#, fuzzy +msgid "Last Status Update" +msgstr "Letzer Status-Aktualisierung" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:18 msgid "Last check" msgstr "Letzter Check" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:27 -msgid "Last check command" -msgstr "Letztes Check-Kommando" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:126 +#, fuzzy +msgid "Latency" +msgstr "Check-Latenz" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:23 -msgid "Last status update" -msgstr "Letzer Status-Aktualisierung" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/timeline/index.phtml:15 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:16 msgid "Legend" msgstr "Legende" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/command/list.phtml:1 -msgid "List Of Supported Commands" -msgstr "Liste unterstützter Befehle" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:292 +#, php-format +msgid "" +"List %s service that is currenlty in state PENDING in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state PENDING in service group \"%s\"" +msgstr[0] "" +msgstr[1] "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml:49 -msgid "List all" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:157 +#, php-format +msgid "" +"List %s service that is currently in state CRITICAL (Acknowledged) in " +"service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state CRITICAL (Acknowledged) in " +"service group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:133 +#, php-format +msgid "" +"List %s service that is currently in state CRITICAL in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state CRITICAL in service group \"%s" +"\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:106 +#, php-format +msgid "List %s service that is currently in state OK in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state OK in service group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:211 +#, php-format +msgid "" +"List %s service that is currently in state UNKNOWN (Acknowledged) in service " +"group \"%s\"" +msgid_plural "" +"List %s services which are currently in state UNKNOWN (Acknowledged) in " +"service group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:187 +#, php-format +msgid "" +"List %s service that is currently in state UNKNOWN in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state UNKNOWN in service group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:265 +#, php-format +msgid "" +"List %s service that is currently in state WARNING (Acknowledged) in service " +"group \"%s\"" +msgid_plural "" +"List %s services which are currently in state WARNING (Acknowledged) in " +"service group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:241 +#, php-format +msgid "" +"List %s service that is currently in state WARNING in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state WARNING in service group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:123 +#, fuzzy, php-format +msgid "List %s unhandled service problem on host %s" +msgid_plural "List %s unhandled service problems on host %s" +msgstr[0] "%d unbestätigte Services" +msgstr[1] "%d unbestätigte Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:111 +#, php-format +msgctxt "timeline.link.title" +msgid "List %u %s registered %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:24 +#, fuzzy, php-format +msgid "List %u actively checked host" +msgid_plural "List %u actively checked hosts" +msgstr[0] "%d wird passiv überwacht" +msgstr[1] "%d wird passiv überwacht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:80 +#, fuzzy, php-format +msgid "List %u actively checked service" +msgid_plural "List %u actively checked services" +msgstr[0] "%d wird passiv überwacht" +msgstr[1] "%d wird passiv überwacht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:112 +#, fuzzy, php-format +msgid "List %u host comment" +msgid_plural "List %u host comments" +msgstr[0] "Kommentar absenden" +msgstr[1] "Kommentar absenden" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:97 +#, fuzzy, php-format +msgid "List %u host currently in downtime" +msgid_plural "List %u hosts currently in downtime" +msgstr[0] "Aktueller Zustand dieses Host" +msgstr[1] "Aktueller Zustand dieses Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:27 +#, fuzzy, php-format +msgid "List %u host for which flap detection has been disabled" +msgid_plural "List %u hosts for which flap detection has been disabled" +msgstr[0] "Befehl wurde gesendet, Flap-Erkennung wird deaktiviert" +msgstr[1] "Befehl wurde gesendet, Flap-Erkennung wird deaktiviert" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:148 +#, php-format +msgid "List %u host for which notifications are suppressed" +msgid_plural "List %u hosts for which notifications are suppressed" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:57 +#, php-format +msgid "List %u host that is currently flapping" +msgid_plural "List %u hosts which are currently flapping" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:17 +#, fuzzy, php-format +msgid "List %u host that is currently in state DOWN" +msgid_plural "List %u hosts which are currently in state DOWN" +msgstr[0] "Aktueller Zustand dieses Host" +msgstr[1] "Aktueller Zustand dieses Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:59 +#, php-format +msgid "List %u host that is currently in state DOWN (Acknowledged)" +msgid_plural "List %u hosts which are currently in state DOWN (Acknowledged)" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:126 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:40 +#, fuzzy, php-format +msgid "List %u host that is currently in state PENDING" +msgid_plural "List %u hosts which are currently in state PENDING" +msgstr[0] "Aktueller Zustand dieses Host" +msgstr[1] "Aktueller Zustand dieses Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:84 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:39 +#, php-format +msgid "List %u host that is currently in state UNREACHABLE" +msgid_plural "List %u hosts which are currently in state UNREACHABLE" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:104 +#, php-format +msgid "List %u host that is currently in state UNREACHABLE (Acknowledged)" +msgid_plural "" +"List %u hosts which are currently in state UNREACHABLE (Acknowledged)" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:18 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:22 +#, fuzzy, php-format +msgid "List %u host that is currently in state UP" +msgid_plural "List %u hosts which are currently in state UP" +msgstr[0] "Aktueller Zustand dieses Host" +msgstr[1] "Aktueller Zustand dieses Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:60 +#, fuzzy, php-format +msgid "List %u host that is not being checked at all" +msgid_plural "List %u hosts which are not being checked at all" +msgstr[0] "%d wird nicht überwacht" +msgstr[1] "%d wird nicht überwacht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:227 +#, php-format +msgid "List %u host that is not processing any event handlers" +msgid_plural "List %u hosts which are not processing any event handlers" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:42 +#, fuzzy, php-format +msgid "List %u passively checked host" +msgid_plural "List %u passively checked hosts" +msgstr[0] "%d wird passiv überwacht" +msgstr[1] "%d wird passiv überwacht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:98 +#, fuzzy, php-format +msgid "List %u passively checked service" +msgid_plural "List %u passively checked services" +msgstr[0] "%d wird passiv überwacht" +msgstr[1] "%d wird passiv überwacht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:112 +#, php-format +msgid "List %u service comment" +msgid_plural "List %u service comments" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:97 +#, fuzzy, php-format +msgid "List %u service currently in downtime" +msgid_plural "List %u services currently in downtime" +msgstr[0] "Aktueller Zustand dieses Services" +msgstr[1] "Aktueller Zustand dieses Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:82 +#, php-format +msgid "List %u service for which flap detection has been disabled" +msgid_plural "List %u services for which flap detection has been disabled" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:182 +#, php-format +msgid "List %u service for which notifications are suppressed" +msgid_plural "List %u services for which notifications are suppressed" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:112 +#, fuzzy, php-format +msgid "List %u service that is currently flapping" +msgid_plural "List %u services which are currently flapping" +msgstr[0] "%d flapping services" +msgstr[1] "%d flapping services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:57 +#, fuzzy, php-format +msgid "List %u service that is currently in state %s" +msgid_plural "List %u services which are currently in state %s" +msgstr[0] "Aktueller Zustand dieses Services" +msgstr[1] "Aktueller Zustand dieses Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:82 +#, php-format +msgid "List %u service that is currently in state %s (Acknowledged)" +msgid_plural "List %u services which are currently in state %s (Acknowledged)" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:108 +#, php-format +msgid "List %u service that is currently in state %s (Acknowledged) on host %s" +msgid_plural "" +"List %u services which are currently in state %s (Acknowledged) on host %s" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:82 +#, php-format +msgid "List %u service that is currently in state %s on host %s" +msgid_plural "List %u services which are currently in state %s on host %s" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:20 +#, fuzzy, php-format +msgid "List %u service that is currently in state CRITICAL" +msgid_plural "List %u services which are currently in state CRITICAL" +msgstr[0] "Aktueller Zustand dieses Services" +msgstr[1] "Aktueller Zustand dieses Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:41 +#, php-format +msgid "List %u service that is currently in state CRITICAL (Acknowledged)" +msgid_plural "" +"List %u services which are currently in state CRITICAL (Acknowledged)" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:165 +#, php-format +msgid "" +"List %u service that is currently in state CRITICAL (Acknowledged) on hosts " +"in the host group \"%s\"" +msgid_plural "" +"List %u services which are currently in state CRITICAL (Acknowledged) on " +"hosts in the host group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:95 +#, php-format +msgid "" +"List %u service that is currently in state CRITICAL and not checked at all" +msgid_plural "" +"List %u services which are currently in state CRITICAL and not checked at all" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:68 +#, php-format +msgid "" +"List %u service that is currently in state CRITICAL and passively checked" +msgid_plural "" +"List %u services which are currently in state CRITICAL and passively checked" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:141 +#, php-format +msgid "" +"List %u service that is currently in state CRITICAL on hosts in the host " +"group \"%s\"" +msgid_plural "" +"List %u services which are currently in state CRITICAL on hosts in the host " +"group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:19 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:314 +#, fuzzy, php-format +msgid "List %u service that is currently in state OK" +msgid_plural "List %u services which are currently in state OK" +msgstr[0] "Aktueller Zustand dieses Services" +msgstr[1] "Aktueller Zustand dieses Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:340 +#, php-format +msgid "List %u service that is currently in state OK and not checked at all" +msgid_plural "" +"List %u services which are currently in state OK and not checked at all" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:43 +#, php-format +msgid "List %u service that is currently in state OK on host %s" +msgid_plural "List %u services which are currently in state OK on host %s" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:114 +#, php-format +msgid "" +"List %u service that is currently in state OK on hosts in the host group \"%s" +"\"" +msgid_plural "" +"List %u services which are currently in state OK on hosts in the host group " +"\"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:106 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:361 +#, fuzzy, php-format +msgid "List %u service that is currently in state PENDING" +msgid_plural "List %u services which are currently in state PENDING" +msgstr[0] "Aktueller Zustand dieses Services" +msgstr[1] "Aktueller Zustand dieses Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:387 +#, php-format +msgid "" +"List %u service that is currently in state PENDING and not checked at all" +msgid_plural "" +"List %u services which are currently in state PENDING and not checked at all" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:133 +#, php-format +msgid "List %u service that is currently in state PENDING on host %s" +msgid_plural "List %u services which are currently in state PENDING on host %s" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:300 +#, php-format +msgid "" +"List %u service that is currently in state PENDING on hosts in the host " +"group \"%s\"" +msgid_plural "" +"List %u services which are currently in state PENDING on hosts in the host " +"group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:218 +#, fuzzy, php-format +msgid "List %u service that is currently in state UNKNOWN" +msgid_plural "List %u services which are currently in state UNKNOWN" +msgstr[0] "Aktueller Zustand dieses Services" +msgstr[1] "Aktueller Zustand dieses Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:239 +#, php-format +msgid "List %u service that is currently in state UNKNOWN (Acknowledged)" +msgid_plural "" +"List %u services which are currently in state UNKNOWN (Acknowledged)" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:219 +#, php-format +msgid "" +"List %u service that is currently in state UNKNOWN (Acknowledged) on hosts " +"in the host group \"%s\"" +msgid_plural "" +"List %u services which are currently in state UNKNOWN (Acknowledged) on " +"hosts in the host group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:293 +#, php-format +msgid "" +"List %u service that is currently in state UNKNOWN and not checked at all" +msgid_plural "" +"List %u services which are currently in state UNKNOWN and not checked at all" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:266 +#, php-format +msgid "" +"List %u service that is currently in state UNKNOWN and passively checked" +msgid_plural "" +"List %u services which are currently in state UNKNOWN and passively checked" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:195 +#, php-format +msgid "" +"List %u service that is currently in state UNKNOWN on hosts in the host " +"group \"%s\"" +msgid_plural "" +"List %u services which are currently in state UNKNOWN on hosts in the host " +"group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:119 +#, fuzzy, php-format +msgid "List %u service that is currently in state WARNING" +msgid_plural "List %u services which are currently in state WARNING" +msgstr[0] "Aktueller Zustand dieses Services" +msgstr[1] "Aktueller Zustand dieses Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:140 +#, php-format +msgid "List %u service that is currently in state WARNING (Acknowledged)" +msgid_plural "" +"List %u services which are currently in state WARNING (Acknowledged)" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:273 +#, php-format +msgid "" +"List %u service that is currently in state WARNING (Acknowledged) on hosts " +"in the host group \"%s\"" +msgid_plural "" +"List %u services which are currently in state WARNING (Acknowledged) on " +"hosts in the host group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:194 +#, php-format +msgid "" +"List %u service that is currently in state WARNING and not checked at all" +msgid_plural "" +"List %u services which are currently in state WARNING and not checked at all" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:167 +#, php-format +msgid "" +"List %u service that is currently in state WARNING and passively checked" +msgid_plural "" +"List %u services which are currently in state WARNING and passively checked" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:249 +#, php-format +msgid "" +"List %u service that is currently in state WARNING on hosts in the host " +"group \"%s\"" +msgid_plural "" +"List %u services which are currently in state WARNING on hosts in the host " +"group \"%s\"" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:116 +#, fuzzy, php-format +msgid "List %u service that is not being checked at all" +msgid_plural "List %u services which are not being checked at all" +msgstr[0] "%d wird nicht überwacht" +msgstr[1] "%d wird nicht überwacht" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:261 +#, php-format +msgid "List %u service that is not processing any event handlers" +msgid_plural "List %u services which are not processing any event handlers" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:10 +#, fuzzy, php-format +msgid "List all %u hosts" msgstr "Alle anzeigen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml:52 -msgid "List all selected objects" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:23 +#, fuzzy, php-format +msgid "List all %u service on host %s" +msgid_plural "List all %u services on host %s" +msgstr[0] "Alle gewählten Objekte anzeigen" +msgstr[1] "Alle gewählten Objekte anzeigen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:10 +#, fuzzy, php-format +msgid "List all %u services" +msgstr "%s Services:" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:73 +#, php-format +msgctxt "timeline.link.title" +msgid "List all event records registered %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:86 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml:11 +#, php-format +msgid "List all hosts in the group \"%s\"" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:41 +msgid "List all hosts, for which flap detection is enabled entirely" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:162 +msgid "List all hosts, for which notifications are enabled entirely" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:241 +msgid "List all hosts, which are processing event handlers entirely" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:72 +#, php-format +msgid "List all reported services on host %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:86 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml:11 +#, php-format +msgid "List all services in the group \"%s\"" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:95 +#, php-format +msgid "List all services of all hosts in host group \"%s\"" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:237 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:212 +#, fuzzy, php-format +msgid "List all services on host %s" msgstr "Alle gewählten Objekte anzeigen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:222 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:231 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:53 +#, php-format +msgid "List all services with the name \"%s\" on all reported hosts" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:96 +msgid "List all services, for which flap detection is enabled entirely" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:196 +msgid "List all services, for which notifications are enabled entirely" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:275 +msgid "List all services, which are processing event handlers entirely" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:468 +#, fuzzy +msgid "List comments" +msgstr "Kommentar absenden" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:435 +#, fuzzy +msgid "List contact groups" +msgstr "Kontaktgruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:351 +#, fuzzy +msgid "List contacts" +msgstr "Kontakte" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/contacts.phtml:31 +#, php-format +msgid "List contacts in contact-group \"%s\"" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:269 +#, fuzzy +msgid "List downtimes" +msgstr "Begonnene Downtimes" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:614 +msgid "List event records" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:561 +#, fuzzy +msgid "List host groups" +msgstr "Hostgruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:97 +msgid "List hosts" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:326 +#, fuzzy +msgid "List notifications" +msgstr "Benachrichtigungen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:510 +#, fuzzy +msgid "List service groups" +msgstr "Servicegruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:171 +#, fuzzy +msgid "List services" +msgstr "%s Services:" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:118 +msgid "Livestatus Resource" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:76 +msgid "Local" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:202 +msgid "Local Command File" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Max" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:449 +msgid "Max (min)" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +#, fuzzy +msgid "Min" +msgstr "Minuten" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:147 msgid "Minutes" msgstr "Minuten" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:2 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:14 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:72 +#, fuzzy +msgid "Monitoring Backend" +msgstr "Monitoring-Prozess" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:14 +#, fuzzy +msgctxt "setup.page.title" +msgid "Monitoring Backend" +msgstr "Monitoring-Prozess" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:3 +#, fuzzy +msgid "Monitoring Backends" +msgstr "Monitoring-Prozess" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:2 msgid "Monitoring Features" msgstr "Monitoring-Features" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:159 -msgid "Monitoring Process" +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:209 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ProcessController.php:29 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ProcessController.php:40 +#, fuzzy +msgid "Monitoring Health" +msgstr "Monitoring-Features" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/IdoResourcePage.php:14 +#, fuzzy +msgctxt "setup.page.title" +msgid "Monitoring IDO Resource" +msgstr "Monitoring-Features" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:42 +#, fuzzy +msgid "Monitoring Instance" msgstr "Monitoring-Prozess" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:21 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/InstancePage.php:14 +#, fuzzy +msgctxt "setup.page.title" +msgid "Monitoring Instance" +msgstr "Monitoring-Prozess" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:50 +#, fuzzy +msgid "Monitoring Instances" +msgstr "Monitoring-Prozess" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php:14 +#, fuzzy +msgctxt "setup.page.title" +msgid "Monitoring Livestatus Resource" +msgstr "Monitoring-Features" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:41 +#, fuzzy +msgid "Monitoring Security" +msgstr "Monitoring-Features" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/SecurityPage.php:14 +#, fuzzy +msgctxt "setup.page.title" +msgid "Monitoring Security" +msgstr "Monitoring-Features" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:146 +#, php-format +msgid "Monitoring backend \"%s\" has been successfully changed" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:143 +#, php-format +msgid "Monitoring backend \"%s\" has been successfully created" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:79 +#, fuzzy +msgid "Monitoring backend already exists" +msgstr "Monitoring-Features" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:146 +#, php-format +msgid "" +"Monitoring backend configuration could not be written to: %s; An error " +"occured:" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:141 +#, php-format +msgid "Monitoring backend configuration has been successfully written to: %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:77 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:124 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:170 +#, fuzzy +msgid "Monitoring backend name missing" +msgstr "Monitoring-Prozess" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:93 +#, php-format +msgid "" +"Monitoring instance configuration could not be written to: %s; An error " +"occured:" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:88 +#, php-format +msgid "Monitoring instance configuration has been successfully created: %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:71 +#, php-format +msgid "" +"Monitoring security configuration could not be written to: %s; An error " +"occured:" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:66 +#, php-format +msgid "Monitoring security configuration has been successfully created: %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:649 +msgid "Month" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:38 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:44 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:50 +msgid "N/A" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:376 +msgid "Name" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:97 +msgid "New instance name missing" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:102 +msgid "New monitoring backend name missing" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/SecurityConfigForm.php:31 +msgid "New security configuration has successfully been stored" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:27 msgid "Next check" msgstr "Nächster Check" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicematrix.phtml:26 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checksource.phtml:17 +msgid "No" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:23 msgid "No Services matching the filter" msgstr "Zu diesem Filter wurden keine Services gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:20 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:25 +#, fuzzy +msgid "No active downtimes" +msgstr "Begonnene Downtimes" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:176 +msgid "No backend has been configured" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:14 msgid "No comments matching the filter" msgstr "Zu diesem Filter wurden keine Kommentare gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/contactgroups.phtml:11 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/contacts.phtml:16 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:189 +#, php-format +msgid "No configuration for backend %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contactgroups.phtml:11 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:12 msgid "No contacts matching the filter" msgstr "Zu diesem Filter wurden keine Kontakte gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:18 -msgid "No downtimes matching the filter" -msgstr "Zu diesem Filter wurden keine Downtimes gefunden" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:10 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:24 msgid "No history available for this object" msgstr "Zu diesem Filter wurden keine historischen Ereignisse gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml:12 -msgid "No history entry matching the filter" -msgstr "Zu diesem Filter wurden keine historischen Ereignisse gefunden" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:14 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:26 msgid "No history events matching the filter" msgstr "Zu diesem Filter wurden keine historischen Ereignisse gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hostgroups.phtml:11 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:17 msgid "No host groups matching the filter" msgstr "Zu diesem Filter wurden keine Hostgruppen gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hosts.phtml:28 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/host.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:6 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:27 msgid "No hosts matching the filter" msgstr "Zu diesem Filter wurden keine Hosts gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/notifications.phtml:31 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:53 msgid "No notification has been sent for this issue" msgstr "Für dieses Problem wurde keine Benachrichtigung gesendet" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/notifications.phtml:29 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:62 +#, fuzzy +msgid "No notifications have been sent for this contact" +msgstr "Für dieses Problem wurde keine Benachrichtigung gesendet" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:21 msgid "No notifications matching the filter" msgstr "Zu diesem Filter wurde keine Benachrichtigung gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicegroups.phtml:11 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:17 msgid "No service groups matching the filter" msgstr "Zu diesem Filter wurden keine Servicegruppen gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/services.phtml:40 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/service.phtml:17 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:32 +#, fuzzy +msgid "No services configured on this host" +msgstr "Alle auf diesem Host konfigurierten Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:6 msgid "No services matching the filter" msgstr "Zu diesem Filter wurden keine Services gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/contact.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:69 +msgid "No state changes in the selected time period." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:10 msgid "No such contact" msgstr "Der gewünschte Kontakt existiert nicht" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:32 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:55 +msgid "None" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:23 msgid "Not acknowledged" msgstr "Nicht bestätigt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:32 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:36 -msgid "Not set" -msgstr "Nicht gesetzt" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:27 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:23 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:41 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:52 msgid "Notification" msgstr "Benachrichtigung" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/DelayNotificationForm.php:30 -msgid "Notification Delay (Minutes From Now)" -msgstr "Benachrichtigungsverzögerung (Minuten ab jetzt)" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:342 +#, fuzzy +msgid "Notification Start" +msgstr "Benachrichtigung" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:984 -msgid "Notification delay has been requested" -msgstr "Benachrichtigungsverzögerung wurde angefragt" - -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:105 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:38 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/flags.phtml:35 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/notifications.phtml:10 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:73 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:75 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:171 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:331 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:429 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:472 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:479 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:325 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:44 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:76 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:66 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:2 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:127 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:129 msgid "Notifications" msgstr "Benachrichtigungen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:39 -msgid "Notifications enabled" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:61 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:85 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:13 +#, fuzzy +msgid "Notifications Disabled" msgstr "Benachrichtigungen aktiv" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:653 -msgid "Notifications for this host and its services will be disabled." -msgstr "Benachrichtigungen für diesen Host und seine Services werden deaktiviert." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:124 +#, fuzzy +msgid "Notifications Enabled" +msgstr "Benachrichtigungen aktiv" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:674 -msgid "Notifications for this host and its services will be enabled." -msgstr "Benachrichtigungen für diesen Host und seine Services werden aktiviert." +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:15 +#, fuzzy +msgid "Notifications and Problems" +msgstr "Benachrichtigungen aktiv" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:528 -msgid "Notifications for this object will be disabled." -msgstr "Benachrichtigungen für dieses Objekt werden deaktiviert." +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:328 +msgid "Notifications and average reaction time per hour." +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:558 -msgid "Notifications for this object will be enabled." -msgstr "Benachrichtigungen für dieses Objekt werden aktiviert." +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:469 +#, fuzzy +msgid "Notifications and defects per hour" +msgstr "Zu diesem Filter wurde keine Benachrichtigung gefunden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:51 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:56 +#, fuzzy +msgid "Notifications sent to this contact" +msgstr "Für dieses Problem wurde keine Benachrichtigung gesendet" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:76 +#, php-format +msgid "Notifications will be re-enabled in %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/disable-notifications.phtml:11 +#, php-format +msgid "Notifications will be re-enabled in %s." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:70 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:70 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:178 msgid "OK" msgstr "OK" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/flags.phtml:68 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:60 +#, fuzzy +msgctxt "icinga.state" +msgid "OK" +msgstr "OK" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:72 +msgid "Object summaries" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:97 +msgid "Object type" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:57 msgid "Obsessing" msgstr "Verfolgung" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:115 -msgid "Obsessing over host checks" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:144 +#, fuzzy +msgid "Obsessing Over Hosts" msgstr "Host-Checks verfolgen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:126 -msgid "Obsessing over service checks" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:153 +#, fuzzy +msgid "Obsessing Over Services" msgstr "Service-Checks verfolgen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:148 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:636 +msgid "Occurence" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:250 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:121 msgid "Ok" msgstr "Ok" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:92 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:95 +msgid "Old instance name missing" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:100 +msgid "Old monitoring backend name missing" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:536 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:98 msgid "One day" msgstr "Ein Tag" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:94 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:538 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:100 msgid "One month" msgstr "Ein Monat" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:93 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:537 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:99 msgid "One week" msgstr "Eine Woche" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:95 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:539 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:101 msgid "One year" msgstr "Ein Jahr" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:50 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:72 +#, fuzzy +msgid "Output" +msgstr "Check Ausgabe" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:131 msgid "Overview" msgstr "Übersicht" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/contacts.phtml:33 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/contact.phtml:26 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:76 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:76 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Host.php:182 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:190 +#, fuzzy +msgid "PENDING" +msgstr "%d Hosts UNGEPRÜFT" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:32 msgid "Pager" msgstr "Pager" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:30 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:62 -msgid "Passive" -msgstr "Passiv" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:379 +msgid "Pager Address / Number" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/flags.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:48 msgid "Passive Checks" msgstr "Passive Checks" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:357 -msgid "Passive check result has been submitted" -msgstr "Passives Check-Ergebnis wurde gesendet" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:162 +msgid "Passive Host Checks Being Accepted" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:476 -msgid "Passive checks for this object will be accepted." -msgstr "Passives Check-Ergebnis für dieses Objekt werden akzeptiert." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:171 +msgid "Passive Service Checks Being Accepted" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:442 -msgid "Passive checks for this object will be omitted." -msgstr "Passives Check-Ergebnis für dieses Objekt werden ignoriert." +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:150 +#, fuzzy +msgid "Passive checks" +msgstr "Passive Checks" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:152 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:112 +msgid "Password" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:66 +msgid "Path to the Icinga command file on the remote Icinga instance" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php:32 +msgid "Path to the local Icinga command file" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:81 msgid "Performance Data" msgstr "Performancedaten" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:122 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:180 +#, fuzzy +msgid "Performance Data Being Processed" +msgstr "Performancedaten" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:70 msgid "Performance Info" msgstr "Performance-Info" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/perfdata.phtml:3 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/perfdata.phtml:3 msgid "Performance data" msgstr "Performancedaten" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CommentForm.php:46 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:55 msgid "Persistent" msgstr "Persisten" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:52 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:61 msgid "Persistent Comment" msgstr "Persistenter Kommentar" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CommentForm.php:55 -msgid "Post Comment" -msgstr "Kommentar absenden" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:16 +msgid "" +"Please configure below how Icinga Web 2 should retrieve monitoring " +"information." +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:25 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/InstancePage.php:16 +msgid "Please define the settings specific to your monitoring instance below." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/IdoResourcePage.php:16 +msgid "" +"Please fill out the connection details below to access the IDO database of " +"your monitoring environment." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php:16 +msgid "" +"Please fill out the connection details below to access the Livestatus socket " +"interface for your monitoring environment." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/output.phtml:2 +msgid "Plugin Output" +msgstr "Ausgabe des Plugins" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:42 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:100 +msgid "Port" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml:2 +msgctxt "multiselection" +msgid "" +"Press and hold the Ctrl key while clicking on rows to select multiple rows " +"or press and hold the Shift key to select a range of rows." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:96 msgid "Problems" msgstr "Probleme" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:118 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:19 msgid "Process Info" msgstr "Prozess Info" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:148 -msgid "Process performance data" -msgstr "Performancedaten verarbeiten" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/command.phtml:16 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/command.phtml:27 +#, fuzzy +msgid "Process check result" +msgstr "Check-Ergebnis" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:25 -msgid "Remove Acknowledgement" -msgstr "Bestätigung entfernen" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:111 +msgid "Processing check result.." +msgid_plural "Processing check results.." +msgstr[0] "" +msgstr[1] "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:104 -msgid "Remove Comment" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:23 +#, fuzzy +msgid "Program Start Time" +msgstr "Anfangszeit" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/SecurityConfigForm.php:56 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:52 +msgid "Protected Custom Variables" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checksource.phtml:14 +#, fuzzy +msgid "Reachable" +msgstr "Nicht erreichbar" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:223 +msgid "Recently Recovered Services" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:76 +msgid "Remote" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:203 +#, fuzzy +msgid "Remote Command File" msgstr "Kommentar entfernen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/comments.phtml:10 -msgid "Remove Comments" -msgstr "Kommentare entfernen" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:633 -msgid "Remove Downtime(s)" -msgstr "Downtime(s) entfernen" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:960 -msgid "Remove Problem Acknowledgement" -msgstr "Problembestätigung entfernen" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:903 -msgid "Remove comment" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:58 +#, fuzzy +msgid "Remote Host" msgstr "Kommentar entfernen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:634 -msgid "Remove downtime(s) from this host and its services." -msgstr "Downtime(s) für diesen Host und seine Services entfernen." +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:62 +msgid "Remote SSH Port" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:26 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:66 +msgid "Remote SSH User" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:15 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:59 +#, fuzzy +msgid "Remove" +msgstr "Kommentar entfernen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:86 +msgid "Remove Existing Backend" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:122 +msgid "Remove Existing Instance" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:42 +#, fuzzy, php-format +msgid "Remove monitoring backend %s" +msgstr "Monitoring-Prozess neu starten" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:86 +#, fuzzy, php-format +msgid "Remove monitoring instance %s" +msgstr "Monitoring-Prozess neu starten" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php:30 +#, fuzzy msgid "Remove problem acknowledgement" -msgstr "Problembestätigung entfernen" +msgid_plural "Remove problem acknowledgements" +msgstr[0] "Problembestätigung entfernen" +msgstr[1] "Problembestätigung entfernen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:961 -msgid "Remove problem acknowledgement for this object." -msgstr "Problembestätigungen für diesen Host und seine Services entfernen." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php:48 +#, fuzzy +msgid "Removing problem acknowledgement.." +msgid_plural "Removing problem acknowledgements.." +msgstr[0] "Problembestätigung entfernen" +msgstr[1] "Problembestätigung entfernen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:28 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:541 +msgid "Report interval" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:196 +msgid "Reporting" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:32 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:45 msgid "Reschedule" msgstr "Neu planen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/RescheduleNextCheckForm.php:64 -msgid "Reschedule Check" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:95 +#, fuzzy +msgid "Reschedule Host Check" msgstr "Check neu planen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:9 -msgid "Reschedule next check immediately" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:196 +#, fuzzy +msgid "Reschedule Host Checks" +msgstr "Check neu planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:72 +#, fuzzy +msgid "Reschedule Service Check" +msgstr "Check neu planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:245 +#, fuzzy +msgid "Reschedule Service Checks" +msgstr "Check neu planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:17 +#, fuzzy, php-format +msgid "Reschedule the next check for all %u hosts" msgstr "Nächsten Check sofort einplanen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:921 -msgid "Reset Attributes" -msgstr "Attribute zurücksetzen" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:17 +#, fuzzy, php-format +msgid "Reschedule the next check for all %u services" +msgstr "Nächsten Check sofort einplanen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:922 -msgid "Reset modified attributes to its default." -msgstr "Veränderte Attribute zurücksetzen." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:240 +#, fuzzy +msgid "Resource" +msgstr "Check-Quelle" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:161 -msgid "Restart" -msgstr "Neustarten" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:88 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:123 +msgid "Resource Name" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1039 -msgid "Restart monitoring process" -msgstr "Monitoring-Prozess neu starten" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:158 +#, php-format +msgid "Resource configuration could not be udpated: %s; An error occured:" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:162 -msgid "Restart the monitoring process" -msgstr "Monitoring-Prozess neu starten" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:155 +#, php-format +msgid "Resource configuration has been successfully updated: %s" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1040 -msgid "Restart the monitoring process." -msgstr "Monitoring-Prozess neu starten." +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:65 +msgid "Restrict hosts view to the hosts that match the filter" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:278 -msgid "Schedule Downtime" +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:70 +msgid "Restrict services view to the services that match the filter" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:43 +msgid "SSH port to connect to on the remote Icinga instance" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:30 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:27 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/SecurityConfigForm.php:20 +#, fuzzy +msgid "Save Changes" +msgstr "Passive Checks" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:107 +#, fuzzy +msgid "Schedule Host Downtime" msgstr "Downtime planen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/downtime.phtml:57 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:208 +#, fuzzy +msgid "Schedule Host Downtimes" +msgstr "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:84 +#, fuzzy +msgid "Schedule Service Downtime" +msgstr "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:257 +#, fuzzy +msgid "Schedule Service Downtimes" +msgstr "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:55 +#, php-format +msgid "Schedule a downtime for %u unhandled host problem" +msgid_plural "Schedule a downtime for %u unhandled host problems" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:55 +#, php-format +msgid "Schedule a downtime for %u unhandled service problem" +msgid_plural "Schedule a downtime for %u unhandled service problems" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:23 +#, fuzzy, php-format +msgid "Schedule a downtime for all %u hosts" +msgstr "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:23 +#, fuzzy, php-format +msgid "Schedule a downtime for all %u services" +msgstr "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:15 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:28 +msgid "" +"Schedule a downtime to suppress all problem notifications within a specific " +"period of time" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:34 +#, fuzzy +msgid "Schedule check" +msgid_plural "Schedule checks" +msgstr[0] "Check neu planen" +msgstr[1] "Check neu planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php:28 +msgid "Schedule check for all services on the hosts and the hosts themselves." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:47 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:8 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:21 +#, fuzzy msgid "Schedule downtime" +msgid_plural "Schedule downtimes" +msgstr[0] "Downtime planen" +msgstr[1] "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:30 +msgid "" +"Schedule downtime for all services on the hosts and the hosts themselves." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:43 +msgid "Schedule non-triggered downtime for all child hosts" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:52 +msgid "Schedule the next active check at a different time than the current one" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:43 +#, fuzzy +msgid "Schedule the next active check to run immediately" +msgstr "Nächsten Check sofort einplanen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:42 +msgid "Schedule triggered downtime for all child hosts" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:304 +#, fuzzy +msgid "Scheduled End" msgstr "Downtime planen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:258 -msgid "Schedule non-triggered downtime for all child objects" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:303 +#, fuzzy +msgid "Scheduled Start" +msgstr "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:72 +#, fuzzy +msgid "Scheduling check.." +msgid_plural "Scheduling checks.." +msgstr[0] "Check neu planen" +msgstr[1] "Check neu planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php:51 +#, fuzzy +msgid "Scheduling host check.." +msgid_plural "Scheduling host checks.." +msgstr[0] "Host-Checks verfolgen" +msgstr[1] "Host-Checks verfolgen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:86 +#, fuzzy +msgid "Scheduling host downtime.." +msgid_plural "Scheduling host downtimes.." +msgstr[0] "Downtime planen" +msgstr[1] "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:100 +#, fuzzy +msgid "Scheduling service check.." +msgid_plural "Scheduling service checks.." +msgstr[0] "Service-Checks verfolgen" +msgstr[1] "Service-Checks verfolgen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:207 +#, fuzzy +msgid "Scheduling service downtime.." +msgid_plural "Scheduling service downtimes.." +msgstr[0] "Downtime planen" +msgstr[1] "Downtime planen" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:80 +msgid "Security" msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:257 -msgid "Schedule triggered downtime for all child objects" -msgstr "" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CustomNotificationForm.php:65 -msgid "Selecting this option causes the notification to be sent out to all normal (non-escalated) and escalated contacts. These options allow you to override the normal notification logic if you need to get an important message out." -msgstr "Das Aktivieren diese Option verursacht, dass die Benachrichtigung an alle normalen (nicht-eskalierten) sowie eskalierten Kontakte gesendet wird. Die erlaubt es, die normale Benachrichtigungslogik zu übergehen, wenn eine dringende Nachricht versendet werden muss." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CustomNotificationForm.php:72 -msgid "Send Custom Notification" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:131 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:232 +#, fuzzy +msgid "Send Custom Host Notification" msgstr "Benutzerdefinierte Benachrichtigung senden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:107 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:108 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:281 +#, fuzzy +msgid "Send Custom Service Notification" +msgstr "Benutzerdefinierte Benachrichtigung senden" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:123 msgid "Send Notification" msgstr "Benachrichtigung senden" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/header.phtml:23 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:36 +#, fuzzy, php-format +msgid "Send a custom notification for all %u hosts" +msgstr "Benutzerdefinierte Benachrichtigung senden" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:36 +#, fuzzy, php-format +msgid "Send a custom notification for all %u services" +msgstr "Benutzerdefinierte Benachrichtigung senden" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:25 +msgid "" +"Send a custom notification, share information about the object to contacts." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:28 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:24 +#, php-format +msgid "Send a mail to %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:34 +#, fuzzy +msgid "Send custom notification" +msgid_plural "Send custom notifications" +msgstr[0] "Benutzerdefinierte Benachrichtigung senden" +msgstr[1] "Benutzerdefinierte Benachrichtigung senden" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:106 +msgid "Send custom notification.." +msgid_plural "Send custom notifications.." +msgstr[0] "Benutzerdefinierte Benachrichtigung senden" +msgstr[1] "Benutzerdefinierte Benachrichtigungen senden" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:18 +msgid "Send notification" +msgstr "Benachrichtigung senden" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:62 +#, php-format +msgid "Sent to %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:298 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:491 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:226 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:45 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/object-header.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:201 msgid "Service" msgstr "Service" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:41 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:55 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml:7 +#, fuzzy, php-format +msgid "Service (%u)" +msgid_plural "Services (%u)" +msgstr[0] "Service" +msgstr[1] "Service" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:141 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:161 +#, fuzzy +msgid "Service Checks" +msgstr "Services" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:119 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:646 +#, fuzzy +msgid "Service Grid" +msgstr "Service" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:25 +#, fuzzy +msgid "Service Group" +msgstr "Servicegruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:239 +#, fuzzy +msgid "Service Group Chart" +msgstr "Servicegruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:546 +#, fuzzy +msgid "Service Group Name" +msgstr "Servicegruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:509 +#, fuzzy +msgid "Service Groups" +msgstr "Servicegruppen" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:224 +#, fuzzy +msgid "Service Name" +msgstr "Service-Zustand" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:380 +#, fuzzy +msgid "Service Notification Timeperiod" +msgstr "Benachrichtigungzeitraum des Services" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:115 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:219 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:77 msgid "Service Problems" msgstr "Service-Probleme" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/MultiController.php:123 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:222 +#, fuzzy +msgid "Service Severity" +msgstr "Service-Zustand" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:80 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:202 msgid "Service State" msgstr "Service-Zustand" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/contacts.phtml:40 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/contact.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:27 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:27 +#, fuzzy +msgid "Service States" +msgstr "Service-Zustand" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:658 +msgid "Service description" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/service/show.phtml:3 +#, fuzzy +msgid "Service detail information" +msgstr "Benachrichtigungzeitraum des Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:33 +#, fuzzy +msgid "Service not found" +msgstr "Benachrichtigungzeitraum des Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:42 msgid "Service notification period" msgstr "Benachrichtigungzeitraum des Services" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:70 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml:14 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:91 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:147 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml:17 msgid "Servicegroups" msgstr "Servicegruppen" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:66 -msgid "Servicematrix" -msgstr "Service-Matrix" - -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:62 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:142 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicegroups.phtml:29 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hostgroups.phtml:26 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:8 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:28 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:22 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml:30 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/summary.phtml:5 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:89 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:143 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:243 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:171 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:101 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:240 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:100 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:96 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:42 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:8 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:50 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:48 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:215 msgid "Services" msgstr "Services" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:8 -#, php-format -msgid "Services with state %s" -msgstr "Service im Zustand %s" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:550 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:600 +#, fuzzy +msgid "Services CRITICAL" +msgstr "KRITISCH" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/RescheduleNextCheckForm.php:37 -msgid "Set the date/time when this check should be executed." +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:548 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:598 +#, fuzzy +msgid "Services OK" +msgstr "Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:552 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:602 +#, fuzzy +msgid "Services PENDING" +msgstr "Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:549 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:599 +#, fuzzy +msgid "Services UNKNOWN" +msgstr "Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:551 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:601 +#, fuzzy +msgid "Services WARNING" +msgstr "Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:53 +#, fuzzy +msgid "Set the date and time when the check should be scheduled." msgstr "Setze den Zeitpunkt zu welchem dieser Check ausgeführt werden soll." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:176 -msgid "Set the end date/time for the downtime." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:89 +#, fuzzy +msgid "Set the end date and time for the downtime." msgstr "Setze den Endzeitpunkt für diese Downtime." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:165 -msgid "Set the start date/time for the downtime." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:44 +#, fuzzy +msgid "Set the expire time." +msgstr "Verfallszeitpunkt nutzen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:79 +#, fuzzy +msgid "Set the start date and time for the downtime." msgstr "Setze den Startzeitpunkt für diese Downtime." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:132 -msgid "Set the state which should be send to Icinga for this objects." -msgstr "Setze den Status welcher für dieses Objekt an Icinga gesendet werden soll." +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:97 +msgid "Setup the monitoring module for Icinga Web 2" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1019 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:168 -msgid "Shutdown monitoring process" -msgstr "Monitoring-Prozess herunterefahren" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:132 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:545 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:595 +msgid "Severity" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:169 -msgid "Shutdown the monitoring process" -msgstr "Monitoring-Prozess herunterfahren" +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:256 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:231 +#, php-format +msgid "Show all event records of host %s" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/notifications.phtml:6 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/servicematrix.phtml:5 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:11 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:9 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:5 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/hosts.phtml:10 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/services.phtml:10 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:252 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:227 +#, php-format +msgid "Show all event records of service %s on host %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TacticalController.php:15 +msgid "" +"Show an overview of all hosts and services, their current states and " +"monitoring feature utilisation" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contactgroups.phtml:29 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:23 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:36 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/contacts.phtml:11 +#, php-format +msgid "Show detailed information about %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:208 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Link.php:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:104 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:120 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:183 +#, php-format +msgid "Show detailed information for host %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:222 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Link.php:60 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:93 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:112 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:151 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:197 +#, php-format +msgid "Show detailed information for service %s on host %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ProcessController.php:26 +msgid "" +"Show information about the current monitoring instance's process and it's " +"performance as well as available features" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:41 +msgid "" +"Show recent alerts and visualize notifications and problems based on their " +"amount and chronological distribution" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:259 +msgid "Show resource configuration" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:77 +#, php-format +msgid "Show summarized information for %u hosts" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:98 +#, php-format +msgid "Show summarized information for %u services" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:390 +msgid "Show the Event Grid" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:646 +msgid "Show the Service Grid" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:258 +#, php-format +msgid "Show the configuration of the %s resource" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:21 +msgid "Show the number of historical event records grouped by time and type" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/IdoResourcePage.php:75 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php:76 +msgid "Skip Validation" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:127 +msgid "Socket" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:5 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:7 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:10 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:7 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:14 msgid "Sort by" msgstr "Sortiere nach" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:471 -msgid "Start Accepting Passive Checks" -msgstr "Passive Checks akzepzieren" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:94 +#, fuzzy +msgctxt "setup.welcome.btn.next" +msgid "Start" +msgstr "Beginnt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:162 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:301 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:78 msgid "Start Time" msgstr "Anfangszeit" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:403 -msgid "Start obsessing" -msgstr "Verfolgung beginnen" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:408 -msgid "Start obsessing over this object." -msgstr "Verfolgung dieses Objekts beginnen" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TimelineController.php:58 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:64 msgid "Started downtimes" msgstr "Begonnene Downtimes" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:43 msgid "Starts" msgstr "Beginnt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:94 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:115 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:135 +#, fuzzy +msgid "State" +msgstr "Host-Status" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:46 +#, fuzzy +msgid "State Changes" +msgstr "Harte Status-Änderungen" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:53 +msgid "Status" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:111 msgid "Sticky Acknowledgement" msgstr "Klebrige Bestätigung" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:437 -msgid "Stop Accepting Passive Checks" -msgstr "Passive Checks nicht mehr akzeptieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:1020 -msgid "Stop monitoring instance. You have to start it again from command line." -msgstr "Überwachungsinstanz stoppen. Muss anschließen von der Kommandozeile aus neu gestartet werden." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:369 -msgid "Stop obsessing" -msgstr "Verfolgung deaktivieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:374 -msgid "Stop obsessing over this object." -msgstr "Verfolgung für dieses Objekt deaktivieren" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:159 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:31 +#, fuzzy msgid "Submit Passive Check Result" +msgid_plural "Submit Passive Check Results" +msgstr[0] "Passives Check-Ergebnis übermitteln" +msgstr[1] "Passives Check-Ergebnis übermitteln" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:119 +#, fuzzy +msgid "Submit Passive Host Check Result" msgstr "Passives Check-Ergebnis übermitteln" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:117 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:220 +#, fuzzy +msgid "Submit Passive Host Check Results" +msgstr "Passives Check-Ergebnis übermitteln" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:96 +#, fuzzy +msgid "Submit Passive Service Check Result" +msgstr "Passives Check-Ergebnis übermitteln" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:269 +#, fuzzy +msgid "Submit Passive Service Check Results" +msgstr "Passives Check-Ergebnis übermitteln" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/command.phtml:13 +#, php-format +msgid "Submit a one time or so called passive result for the %s check" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:29 +#, fuzzy, php-format +msgid "Submit a passive check result for all %u hosts" +msgstr "Passives Check-Ergebnis übermitteln" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:29 +#, fuzzy, php-format +msgid "Submit a passive check result for all %u services" +msgstr "Passives Check-Ergebnis übermitteln" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:208 msgid "System" msgstr "System" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/RescheduleNextCheckForm.php:61 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:275 -msgid "TODO: Help message when with children is disabled" -msgstr "" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/RescheduleNextCheckForm.php:59 -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:247 -msgid "TODO: Help message when with children is enabled" -msgstr "" - -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:54 -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/TacticalController.php:15 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:135 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TacticalController.php:18 msgid "Tactical Overview" msgstr "Taktische Übersicht" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:51 -msgid "Temporarily disable" -msgstr "Temporär deaktivieren" +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:169 +msgid "" +"The Zend database adapter for MySQL is required to access a MySQL database." +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/components/notifications.phtml:25 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:189 +msgid "" +"The Zend database adapter for PostgreSQL is required to access a PostgreSQL " +"database." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:227 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:46 +msgid "The data source used for retrieving monitoring information" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/DataView/DataView.php:338 +#, php-format +msgid "The filter column \"%s\" is not allowed here." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:60 +msgid "The given resource name is already in use." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:217 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:29 +msgid "The identifier of this backend" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:46 #, php-format msgid "The last one occured %s ago" msgstr "Die letzte geschah vor %s" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/DelayNotificationForm.php:45 -msgid "The notification delay will be disregarded if the host/service changes state before the next notification is scheduled to be sent out." -msgstr "Die Benachrichtigungsverzögerung wird ignoriert wenn der Host/Service vor der nächsten zu versendenden Benachrichtigung seinen Status ändert." - -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ShowController.php:111 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:122 msgid "The parameter `contact' is required" msgstr "Der Parameter `contact' ist erforderlich" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:3 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:83 +msgid "" +"The performance data of this check result. Leave empty if this check result " +"has no performance data" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:73 +msgid "The plugin output of this check result" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:241 +msgid "The resource to use" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/DataView/DataView.php:247 +#, php-format +msgid "The sort column \"%s\" is not allowed in \"%s\"." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:54 +msgid "The state this check result should report" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:91 +#, php-format +msgid "There is no \"%s\" monitoring backend" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:17 msgid "This Object's Event History" msgstr "Historie dieses Objekts" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:24 -msgid "This command is used to acknowledge host or service problems. When a problem is acknowledged, future notifications about problems are temporarily disabled until the host/service changes from its current state." -msgstr "Dieses Kommando wird benutzt um Host- oder Service-Probleme zu bestätigen. Wenn ein Problem bestätigt ist, werden zukünftige Benachrichtigungen temporär deaktiviert, bis der Host/Service seinen Zustand wieder ändert." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:22 +#, fuzzy +msgid "" +"This command is used to acknowledge host or service problems. When a problem " +"is acknowledged, future notifications about problems are temporarily " +"disabled until the host or service recovers." +msgstr "" +"Dieses Kommando wird benutzt um Host- oder Service-Probleme zu bestätigen. " +"Wenn ein Problem bestätigt ist, werden zukünftige Benachrichtigungen " +"temporär deaktiviert, bis der Host/Service seinen Zustand wieder ändert." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CommentForm.php:22 -msgid "This command is used to add a comment to hosts or services." -msgstr "Dieses Kommando wird benutzt um Kommentare zu Hosts oder Services hinzuzufügen." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:19 +#, fuzzy +msgid "This command is used to add host or service comments." +msgstr "" +"Dieses Kommando wird benutzt um Kommentare zu Hosts oder Services " +"hinzuzufügen." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/DelayNotificationForm.php:24 -msgid "This command is used to delay the next problem notification that is sent out." -msgstr "Dieses Kommando wird benutzt um den Versand der nächsten Benachrichtigung zu diesem Problem zu verzögern." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:26 +#, fuzzy +msgid "" +"This command is used to disable host and service notifications for a " +"specific time." +msgstr "" +"Dieses Kommando wird benutzt um den Versand der nächsten Benachrichtigung zu " +"diesem Problem zu verzögern." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:121 -msgid "This command is used to schedule downtime for hosts/services. During the specified downtime, Icinga will not send notifications out about the affected objects. When the scheduled downtime expires, Icinga will send out notifications as it normally would. Scheduled downtimes are preserved across program shutdowns and restarts." -msgstr "Dieses Kommando wird benutzt um Downtimes für Hosts/Services einzuplanen. Während der angegebenen Downtime wird Icinga keine Benachrichtigungen zum entsprechenden Objekt versenden. Wenn die eingeplante Downtime vorüber ist, wird Icinga mit dem Versand der Benachrichtigungen wie gewohnt fortfahren. Eingeplante Downtimes übersteh das Herunterfahren und Neustarten der Applikation." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:33 +#, fuzzy +msgid "" +"This command is used to schedule host and service downtimes. During the " +"specified downtime, Icinga will not send notifications out about the hosts " +"and services. When the scheduled downtime expires, Icinga will send out " +"notifications for the hosts and services as it normally would. Scheduled " +"downtimes are preserved across program shutdowns and restarts." +msgstr "" +"Dieses Kommando wird benutzt um Downtimes für Hosts/Services einzuplanen. " +"Während der angegebenen Downtime wird Icinga keine Benachrichtigungen zum " +"entsprechenden Objekt versenden. Wenn die eingeplante Downtime vorüber ist, " +"wird Icinga mit dem Versand der Benachrichtigungen wie gewohnt fortfahren. " +"Eingeplante Downtimes übersteh das Herunterfahren und Neustarten der " +"Applikation." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/RescheduleNextCheckForm.php:24 -msgid "This command is used to schedule the next check of hosts/services. Icinga will re-queue the check at the time you specify." -msgstr "Dieses Kommando wird benutzt, um den nächsten Check des Hosts/Services einzuplanen. Icinga wird den Check für den von dir gewählten Zeitpunkt vormerken." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:23 +#, fuzzy +msgid "" +"This command is used to schedule the next check of hosts or services. Icinga " +"will re-queue the hosts or services to be checked at the time you specify." +msgstr "" +"Dieses Kommando wird benutzt, um den nächsten Check des Hosts/Services " +"einzuplanen. Icinga wird den Check für den von dir gewählten Zeitpunkt " +"vormerken." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/CustomNotificationForm.php:22 -msgid "This command is used to send a custom notification about hosts or services. Useful in emergencies when you need to notify admins of an issue regarding a monitored system or service." -msgstr "Dieses Kommando ermöglicht es, personalisierte Benachrichtigungen für Hosts oder Services zu versenden. Nützlich ist das vor allem im Notfall, wenn man andere Administratoren über Probleme zu einem bestimmten System oder Dienst informieren möchte." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:21 +#, fuzzy +msgid "" +"This command is used to send custom notifications for hosts or services." +msgstr "" +"Dieses Kommando wird benutzt um Kommentare zu Hosts oder Services " +"hinzuzufügen." -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:106 -msgid "This command is used to submit a passive check result for particular hosts/services. It is particularly useful for resetting security-related objects to OK states once they have been dealt with." -msgstr "Dieses Kommando erlaubt es, ein passives Check-Ergebnis für einen bestimmten Host oder Service einzuliefern. Das ist for allem dann nützlich, wenn man sicherheitsrelevante Objekte zurück auf einen OK-Status setzen möchte, sobald man sich darum gekümmert hat." +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:20 +#, fuzzy +msgid "This command is used to submit passive host or service check results." +msgstr "" +"Dieses Kommando wird benutzt um Kommentare zu Hosts oder Services " +"hinzuzufügen." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:90 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:76 msgid "This comment does not expire." msgstr "Dieser Kommentar verfällt nicht." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:87 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:73 #, php-format msgid "This comment expires on %s at %s." msgstr "Dieser Kommentar verfällt am %s um %s." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:83 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:69 msgid "This comment is not persistent." msgstr "Dieser Kommentar ist nicht persistent." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:82 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:68 msgid "This comment is persistent." msgstr "Dieser Kommentar ist persistent." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:89 -#, php-format -msgid "This fixed downtime has been scheduled to start on %s at %s and to end on %s at %s." -msgstr "Diese fixe Downtime wurde eingeplant um am %s um %s zu starten und am %s um %s zu enden." +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:107 +#, fuzzy, php-format +msgid "" +"This fixed host downtime has been scheduled to start on %s at %s and to end " +"on %s at %s." +msgstr "" +"Diese fixe Downtime wurde eingeplant um am %s um %s zu starten und am %s um " +"%s zu enden." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:81 -#, php-format -msgid "This fixed downtime was started on %s at %s and expires on %s at %s." +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:97 +#, fuzzy, php-format +msgid "" +"This fixed host downtime was started on %s at %s and expires on %s at %s." msgstr "Diese fixe Downtime begann am %s um %s und endet am %s um %s." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:72 -#, php-format -msgid "This flexible downtime has been scheduled to start between %s - %s and to last for %s." -msgstr "Diese flexible Downtime wurde für den Zeitraum von %s - %s eingeplant und wird %s dauern." +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:106 +#, fuzzy, php-format +msgid "" +"This fixed service downtime has been scheduled to start on %s at %s and to " +"end on %s at %s." +msgstr "" +"Diese fixe Downtime wurde eingeplant um am %s um %s zu starten und am %s um " +"%s zu enden." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:63 -#, php-format -msgid "This flexible downtime was started on %s at %s and lasts for %s until %s at %s." +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:96 +#, fuzzy, php-format +msgid "" +"This fixed service downtime was started on %s at %s and expires on %s at %s." +msgstr "Diese fixe Downtime begann am %s um %s und endet am %s um %s." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:86 +#, fuzzy, php-format +msgid "" +"This flexible host downtime has been scheduled to start between %s - %s and " +"to last for %s." +msgstr "" +"Diese flexible Downtime wurde für den Zeitraum von %s - %s eingeplant und " +"wird %s dauern." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:75 +#, fuzzy, php-format +msgid "" +"This flexible host downtime was started on %s at %s and lasts for %s until " +"%s at %s." msgstr "Diese flexible Downtime begann am %s um %s und dauert %s bis %s am %s." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/host.phtml:4 -msgid "This host's current state" -msgstr "Aktueller Zustand dieses Host" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:85 +#, fuzzy, php-format +msgid "" +"This flexible service downtime has been scheduled to start between %s - %s " +"and to last for %s." +msgstr "" +"Diese flexible Downtime wurde für den Zeitraum von %s - %s eingeplant und " +"wird %s dauern." -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/service.phtml:4 -msgid "This service's current state" -msgstr "Aktueller Zustand dieses Services" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:74 +#, fuzzy, php-format +msgid "" +"This flexible service downtime was started on %s at %s and lasts for %s " +"until %s at %s." +msgstr "Diese flexible Downtime begann am %s um %s und dauert %s bis %s am %s." -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:112 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/WelcomePage.php:33 +msgid "This is the core module for Icinga Web 2." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:22 +msgid "Time to Reaction (Ack, Recover)" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:103 +msgid "TimeLine interval" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:191 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:22 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:26 msgid "Timeline" msgstr "Zeitleiste" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/ScheduleDowntimeForm.php:150 -msgid "Triggered by" -msgstr "Ausgelöst von" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:83 +msgid "To" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:54 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:160 +msgid "" +"To access the IDO stored in a MySQL database the PDO-MySQL module for PHP is " +"required." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:180 +msgid "" +"To access the IDO stored in a PostgreSQL database the PDO-PostgreSQL module " +"for PHP is required." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/SecurityPage.php:16 +msgid "" +"To protect your monitoring environment against prying eyes please fill out " +"the settings below." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:86 +msgid "Today" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:219 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:133 +msgid "Toggling feature.." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:56 +msgid "Top 5 Recent Alerts" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:547 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:597 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:26 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:26 +#, fuzzy +msgid "Total Services" +msgstr "Services" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:29 +msgid "Trend" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:39 +msgid "Trend for the last 24h" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:99 +msgid "Type" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:31 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:75 +#, php-format +msgid "Type: %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:40 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:40 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:187 msgid "UNKNOWN" msgstr "UNBEKANNT" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:48 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:63 +#, fuzzy +msgctxt "icinga.state" +msgid "UNKNOWN" +msgstr "UNBEKANNT" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Host.php:179 msgid "UNREACHABLE" msgstr "UNERREICHBAR" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:46 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:58 +#, fuzzy +msgctxt "icinga.state" +msgid "UNREACHABLE" +msgstr "UNERREICHBAR" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Host.php:173 msgid "UP" msgstr "UP" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:23 -#, php-format -msgid "Unhandled services with state %s" -msgstr "Unbestätigte Services mit Status %s" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:56 +#, fuzzy +msgctxt "icinga.state" +msgid "UP" +msgstr "UP" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:29 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:49 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:71 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:5 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:5 +#, fuzzy +msgid "Unhandled" +msgstr "Unbestätigte Hosts" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:101 msgid "Unhandled Hosts" msgstr "Unbestätigte Hosts" -#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:33 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:106 msgid "Unhandled Services" msgstr "Unbestätigte Services" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:166 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:271 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:120 msgid "Unknown" msgstr "Unbekannt" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:211 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:141 +msgid "Unknown instance name given" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:99 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:121 +msgid "Unknown instance name provided" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:104 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:126 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:172 +#, fuzzy +msgid "Unknown monitoring backend provided" +msgstr "Monitoring-Prozess herunterefahren" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:321 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:140 msgid "Unreachable" msgstr "Nicht erreichbar" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:199 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:307 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:138 msgid "Up" msgstr "Up" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/AcknowledgeForm.php:65 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:72 msgid "Use Expire Time" msgstr "Verfallszeitpunkt nutzen" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:36 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:52 +msgid "User" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:30 msgid "User Comment" msgstr "Benutzerkommentar" -#: /usr/local/src/bugfix.master/modules/monitoring/application/forms/Command/SubmitPassiveCheckResultForm.php:52 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:54 +msgid "" +"User to log in as on the remote Icinga instance. Please note that key-based " +"SSH login must be possible for this user" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:108 +#, fuzzy +msgid "Username" +msgstr "Benutzerkommentar" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Value" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:624 +msgid "Value for interval not valid" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:46 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:64 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:46 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:64 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:181 msgid "WARNING" msgstr "WARNUNG" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/ChartController.php:154 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:61 +#, fuzzy +msgctxt "icinga.state" +msgid "WARNING" +msgstr "WARNUNG" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:257 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:119 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 msgid "Warning" msgstr "Warnung" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:54 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/WelcomePage.php:21 +msgid "Welcome to the configuration of the monitoring module for Icinga Web 2!" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checksource.phtml:17 +msgid "Yes" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:166 +msgid "Zend database adapter for MySQL" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:186 +msgid "Zend database adapter for PostgreSQL" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:51 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:39 #, php-format -msgid "Will be re-enabled in %s" -msgstr "Wird in %s wieder aktiviert" +msgctxt "time" +msgid "at %s" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/controllers/CommandController.php:888 -msgid "Your new comment has been submitted" -msgstr "Dein neuer Kommentar wurde gesendet" - -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml:60 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:59 #, php-format -msgid "and %d more" -msgstr "und %d weitere" +msgctxt "timeline.link.title.datetime.twice" +msgid "between %s and %s" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml:18 -msgid "critical events on " -msgstr "kritische Ereignisse auf" +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:107 +msgid "changed" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:15 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:182 +msgid "down" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:65 +#, fuzzy +msgid "hard state" +msgstr "Harte Status-Änderungen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:47 #, php-format -msgid "has been running with PID %d " -msgstr "läuft mit PID %d" +msgctxt "timeline.link.title.month.and.year" +msgid "in %s" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/process/info.phtml:18 -msgid "is not running" -msgstr "läuft nicht" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:53 +#, php-format +msgctxt "timeline.link.title.year" +msgid "in %s" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:8 -msgid "ok" -msgstr "ok" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:52 +#, php-format +msgctxt "timespan" +msgid "in %s" +msgstr "" -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/comments.phtml:66 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/downtimes.phtml:47 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/eventhistory.phtml:97 -#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/show/history.phtml:114 -msgid "on" -msgstr "auf" +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:37 +msgid "in the last hour" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:40 +#, php-format +msgctxt "timeline.link.title.week.and.year" +msgid "in week %s of %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:35 +#, fuzzy +msgid "notifications per hour" +msgstr "Host-Benachrichtigungszeitraum" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:50 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:38 +#, php-format +msgctxt "datetime" +msgid "on %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:34 +#, php-format +msgctxt "timeline.link.title.time" +msgid "on %s" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:77 +msgid "overall" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml:5 +msgctxt "multiselection" +msgid "row(s) selected" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:78 +#, fuzzy +msgid "scheduled" +msgstr "Neu planen" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:63 +#, fuzzy +msgid "soft state" +msgstr "Host-Status" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:50 +#, fuzzy +msgid "the monitoring module" +msgstr "Monitoring-Prozess neu starten" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:180 +msgid "unchanged" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:184 +msgid "up" +msgstr "" + +#~ msgid "" +#~ " If you work with other administrators you may find it useful to share " +#~ "information about a host or service that is having problems if more than " +#~ "one of you may be working on it. Make sure you enter a brief description " +#~ "of what you are doing." +#~ msgstr "" +#~ "Wenn du mit anderen Administratoren zusammenarbeitest wirst du es als " +#~ "nützlich empfinden, Informationen über Probleme auf Hosts oder Services " +#~ "an denen du arbeitest zu teilen. Stell sicher eine kurze Beschreibung von " +#~ "dem was du gerade machst zu hinterlassen." + +#~ msgid "%d are not checked at all" +#~ msgstr "%d werden nicht überwacht" + +#~ msgid "%d are passively checked" +#~ msgstr "%d werden passiv überwacht" + +#~ msgid "Accept passive host checks" +#~ msgstr "Akzeptiere passive Host Checks" + +#~ msgid "Accept passive service checks" +#~ msgstr "Akzeptiere passive Service-Checks" + +#~ msgid "Acknowledge all problems on the selected hosts or services" +#~ msgstr "Bestätige alle Probleme auf den ausgewählten Hosts oder Services" + +#~ msgid "Acknowledgement has been sent" +#~ msgstr "Bestätigung wurde gesendet" + +#~ msgid "Acknowledgement removal has been requested" +#~ msgstr "Löschung der Bestätigung wurde angefragt" + +#~ msgid "All Events" +#~ msgstr "Alle Ereignisse" + +#~ msgid "Author (Your Name)" +#~ msgstr "Autor (Dein Name)" + +#~ msgid "Child Objects" +#~ msgstr "Kind-Objekte" + +#~ msgid "Command has been sent, active checks will be disabled" +#~ msgstr "Befehl wurde gesendet, aktive Checks werden deaktiviert" + +#~ msgid "Command has been sent, active checks will be enabled" +#~ msgstr "Befehl wurde gesendet, aktive Checks werden aktiviert" + +#~ msgid "Command has been sent, check will be rescheduled" +#~ msgstr "Befehl wurde gesendet, Check wird neu geplant" + +#~ msgid "Command has been sent, checks will be rescheduled" +#~ msgstr "Befehl wurde gesendet, Checks werden neu geplant" + +#~ msgid "Command has been sent, event handlers will be disabled" +#~ msgstr "Befehl wurde gesendet, Eventhandler werden deaktiviert" + +#~ msgid "Command has been sent, event handlers will be enabled" +#~ msgstr "Befehl wurde gesendet, Eventhandler werden aktiviert" + +#~ msgid "Command has been sent, flap detection will be enabled" +#~ msgstr "Befehl wurde gesendet, Flap-Erkennung wird aktiviert" + +#~ msgid "Command has been sent, monitoring process will restart now" +#~ msgstr "Befehl wurde gesendet, der Monitoring-Prozess wird jetzt neustarten" + +#~ msgid "Command has been sent, notifications will be enabled" +#~ msgstr "Befehl wurde gesendet, Benachrichtigungen werden aktiviert" + +#~ msgid "Command has been sent, obsessing will be disabled" +#~ msgstr "Befehl wurde gesendet, Verfolgung wird deaktiviert" + +#~ msgid "Command has been sent, obsessing will be enabled" +#~ msgstr "Befehl wurde gesendet, Verfolgung wird aktiviert" + +#~ msgid "Command has been sent, passive check results will be accepted" +#~ msgstr "Befehl wurde gesendet, passive Checks werden akzeptiert werden" + +#~ msgid "Command has been sent, passive check results will be refused" +#~ msgstr "Befehl wurde gesendet, passive Checks werden abgewiesen werden" + +#~ msgid "Command has been sent, performance data processing will be disabled" +#~ msgstr "" +#~ "Befehl wurde gesendet, Performancedatenverarbeitung wird deaktiviert " +#~ "werden" + +#~ msgid "Command has been sent, performance data processing will be enabled" +#~ msgstr "" +#~ "Befehl wurde gesendet, Performancedatenverarbeitung wird aktiviert werden" + +#~ msgid "Command has been sent, process will shut down" +#~ msgstr "Befehl wurde gesendet, der Monitoringprozess wird herunterfahren" + +#~ msgid "Comment removal has been requested" +#~ msgstr "Löschung des Kommentars wurde angefordert" + +#~ msgid "Critical Events" +#~ msgstr "Kritische Ereignisse" + +#~ msgid "Custom notification has been sent" +#~ msgstr "Benutzerdefinierte Bestätigung wurde gesendet" + +#~ msgid "" +#~ "Custom notifications normally follow the regular notification logic in " +#~ "Icinga. Selecting this option will force the notification to be sent out, " +#~ "regardless of time restrictions, whether or not notifications are " +#~ "enabled, etc." +#~ msgstr "" +#~ "Benutzeredefinierte Benachrichtigungen verhalten sich gemäß der " +#~ "gewöhnlichen Benachrichtigungslogik in Icinga. Wenn du diese Option " +#~ "wählst wird eine Benachrichtigung erzwungen. Einschränkungen durch " +#~ "Zeitspannen und durch eventuell deaktivierte Benachrichtigungen werden " +#~ "dabei ignoriert." + +#~ msgid "Delay Notification" +#~ msgstr "Benachrichtigung verzögern" + +#~ msgid "Delay Notifications" +#~ msgstr "Benachrichtigungen verzögern" + +#~ msgid "Delete a single downtime with the id shown above" +#~ msgstr "Lösche eine einzelne Downtime mit der oben gezeigten ID." + +#~ msgid "Disable Active Checks" +#~ msgstr "Aktive Checks deaktivieren" + +#~ msgid "Disable Event Handler" +#~ msgstr "Event-Handler deaktivieren" + +#~ msgid "Disable Flapping Detection" +#~ msgstr "Flap-Erkennung deaktivieren" + +#~ msgid "Disable Performance Data" +#~ msgstr "Performancedaten deaktivieren" + +#~ msgid "Disable active checks for this host and its services." +#~ msgstr "" +#~ "Deaktiviere aktive Service-Checks für diesen Host und seine Service-" +#~ "Checks." + +#~ msgid "Disable active checks for this object." +#~ msgstr "Deaktiviere aktive Service-Checks für dieses Objekt." + +#~ msgid "Disable active checks on a program-wide basis." +#~ msgstr "Deaktiviere applikationsweit sämtliche aktiven Service-Checks." + +#~ msgid "Disable event handler for the whole system." +#~ msgstr "Dektiviere Event-Handler für das ganze System." + +#~ msgid "Disable event handler for this object." +#~ msgstr "Dektiviere Event-Handler für dieses Objekt." + +#~ msgid "Disable flapping detection for this object." +#~ msgstr "Deaktiviere die Flap-Erkennnung für dieses Objekt." + +#~ msgid "Disable flapping detection on a program-wide basis." +#~ msgstr "Deaktiviere die Flap-Erkennnung applikationsweit." + +#~ msgid "Disable notifications on a program-wide basis." +#~ msgstr "Deaktiviere die Flap-Erkennnung applikationsweit." + +#~ msgid "Disable obsessing on a program-wide basis." +#~ msgstr "Deaktiviere die Verfolgung applikationsweit." + +#~ msgid "Disable passive checks on a program-wide basis." +#~ msgstr "Deaktiviere Passive Checks applikationsweit." + +#~ msgid "Disable processing of performance data on a program-wide basis." +#~ msgstr "" +#~ "Deaktiviere die Verarbeitung von Performance-Daten applikationsweit." + +#~ msgid "Downtime removal has been requested" +#~ msgstr "Entfernung dieser Downtime wurde angefordert" + +#~ msgid "Downtime removal requested" +#~ msgstr "Entfernung dieser Downtime wurde angefordert" + +#~ msgid "Downtime scheduling requested" +#~ msgstr "Die Planung dieser Downtime wurde angefordert" + +#~ msgid "Enable Active Checks" +#~ msgstr "Aktive Checks aktivieren" + +#~ msgid "Enable Event Handler" +#~ msgstr "Eventhandler aktivieren" + +#~ msgid "Enable Flapping Detection" +#~ msgstr "Flap-Erkennung aktivieren" + +#~ msgid "Enable Notifications" +#~ msgstr "Benachrichtigungen aktivieren" + +#~ msgid "Enable Performance Data" +#~ msgstr "Performancedaten aktivieren" + +#~ msgid "Enable active checks for this host and its services." +#~ msgstr "Aktive Checks für diesen Host und seine Services aktivieren." + +#~ msgid "Enable active checks for this object." +#~ msgstr "Aktive Checks für dieses Objekt aktivieren." + +#~ msgid "Enable active checks on a program-wide basis." +#~ msgstr "Aktive Checks anwendungsweit aktivieren." + +#~ msgid "Enable event handler for this object." +#~ msgstr "Event-Handler für dieses Objekt aktivieren." + +#~ msgid "Enable event handlers on the whole system." +#~ msgstr "Event-Handler für das gesamte System aktivieren." + +#~ msgid "Enable flapping detection for this object." +#~ msgstr "Flap-Erkennung für dieses Objekt aktivieren." + +#~ msgid "Enable flapping detection on a program-wide basis." +#~ msgstr "Flap-Erkennung anwendungsweit aktivieren." + +#~ msgid "Enable notifications on a program-wide basis." +#~ msgstr "Benachrichtigungen anwendungsweit aktivieren." + +#~ msgid "Enable obsessing on a program-wide basis." +#~ msgstr "Verfolgung anwendungsweit aktivieren." + +#~ msgid "Enable passive checks on a program-wide basis." +#~ msgstr "Passive Checks anwendungsweit aktivieren." + +#~ msgid "Enable processing of performance data on a program-wide basis." +#~ msgstr "Performancedaten-Verarbeitung anwendungsweit aktivieren." + +#~ msgid "Event handlers" +#~ msgstr "Eventhandler" + +#~ msgid "Execute active host checks" +#~ msgstr "Aktiven Host Check ausführen" + +#~ msgid "Execute active service checks" +#~ msgstr "Aktive Service-Checks ausführen" + +#~ msgid "Fill in the check output string which should be send to Icinga." +#~ msgstr "" +#~ "Ausgabetext des Checkergebnisses welches an Icinga gesendet werden soll " +#~ "hier eintragen." + +#~ msgid "Fill in the performance data string which should be send to Icinga." +#~ msgstr "" +#~ "Performancedaten des Checkergebnisses welches an Icinga gesendet werden " +#~ "soll hier eintragen." + +#~ msgid "Flags (host)" +#~ msgstr "Flags (Host)" + +#~ msgid "Flags (service)" +#~ msgstr "Flags (Service)" + +#~ msgid "Flap detection" +#~ msgstr "Flap-Erkennung" + +#~ msgid "Handled services with state %s" +#~ msgstr "Bestätigte Services mit Status %s" + +#~ msgid "Last check command" +#~ msgstr "Letztes Check-Kommando" + +#~ msgid "No downtimes matching the filter" +#~ msgstr "Zu diesem Filter wurden keine Downtimes gefunden" + +#~ msgid "No history entry matching the filter" +#~ msgstr "Zu diesem Filter wurden keine historischen Ereignisse gefunden" + +#~ msgid "Not set" +#~ msgstr "Nicht gesetzt" + +#~ msgid "Notification Delay (Minutes From Now)" +#~ msgstr "Benachrichtigungsverzögerung (Minuten ab jetzt)" + +#~ msgid "Notification delay has been requested" +#~ msgstr "Benachrichtigungsverzögerung wurde angefragt" + +#~ msgid "Notifications for this host and its services will be disabled." +#~ msgstr "" +#~ "Benachrichtigungen für diesen Host und seine Services werden deaktiviert." + +#~ msgid "Notifications for this host and its services will be enabled." +#~ msgstr "" +#~ "Benachrichtigungen für diesen Host und seine Services werden aktiviert." + +#~ msgid "Notifications for this object will be disabled." +#~ msgstr "Benachrichtigungen für dieses Objekt werden deaktiviert." + +#~ msgid "Notifications for this object will be enabled." +#~ msgstr "Benachrichtigungen für dieses Objekt werden aktiviert." + +#~ msgid "Passive check result has been submitted" +#~ msgstr "Passives Check-Ergebnis wurde gesendet" + +#~ msgid "Passive checks for this object will be accepted." +#~ msgstr "Passives Check-Ergebnis für dieses Objekt werden akzeptiert." + +#~ msgid "Passive checks for this object will be omitted." +#~ msgstr "Passives Check-Ergebnis für dieses Objekt werden ignoriert." + +#~ msgid "Process performance data" +#~ msgstr "Performancedaten verarbeiten" + +#~ msgid "Remove Acknowledgement" +#~ msgstr "Bestätigung entfernen" + +#~ msgid "Remove Comments" +#~ msgstr "Kommentare entfernen" + +#~ msgid "Remove Downtime(s)" +#~ msgstr "Downtime(s) entfernen" + +#~ msgid "Remove Problem Acknowledgement" +#~ msgstr "Problembestätigung entfernen" + +#~ msgid "Remove downtime(s) from this host and its services." +#~ msgstr "Downtime(s) für diesen Host und seine Services entfernen." + +#~ msgid "Remove problem acknowledgement for this object." +#~ msgstr "Problembestätigungen für diesen Host und seine Services entfernen." + +#~ msgid "Reset Attributes" +#~ msgstr "Attribute zurücksetzen" + +#~ msgid "Reset modified attributes to its default." +#~ msgstr "Veränderte Attribute zurücksetzen." + +#~ msgid "Restart" +#~ msgstr "Neustarten" + +#~ msgid "Restart the monitoring process." +#~ msgstr "Monitoring-Prozess neu starten." + +#~ msgid "" +#~ "Selecting this option causes the notification to be sent out to all " +#~ "normal (non-escalated) and escalated contacts. These options allow you " +#~ "to override the normal notification logic if you need to get an important " +#~ "message out." +#~ msgstr "" +#~ "Das Aktivieren diese Option verursacht, dass die Benachrichtigung an alle " +#~ "normalen (nicht-eskalierten) sowie eskalierten Kontakte gesendet wird. " +#~ "Die erlaubt es, die normale Benachrichtigungslogik zu übergehen, wenn " +#~ "eine dringende Nachricht versendet werden muss." + +#~ msgid "Servicematrix" +#~ msgstr "Service-Matrix" + +#~ msgid "Services with state %s" +#~ msgstr "Service im Zustand %s" + +#~ msgid "Set the state which should be send to Icinga for this objects." +#~ msgstr "" +#~ "Setze den Status welcher für dieses Objekt an Icinga gesendet werden soll." + +#~ msgid "Shutdown the monitoring process" +#~ msgstr "Monitoring-Prozess herunterfahren" + +#~ msgid "Start Accepting Passive Checks" +#~ msgstr "Passive Checks akzepzieren" + +#~ msgid "Start obsessing" +#~ msgstr "Verfolgung beginnen" + +#~ msgid "Start obsessing over this object." +#~ msgstr "Verfolgung dieses Objekts beginnen" + +#~ msgid "" +#~ "Stop monitoring instance. You have to start it again from command line." +#~ msgstr "" +#~ "Überwachungsinstanz stoppen. Muss anschließen von der Kommandozeile aus " +#~ "neu gestartet werden." + +#~ msgid "Stop obsessing" +#~ msgstr "Verfolgung deaktivieren" + +#~ msgid "Stop obsessing over this object." +#~ msgstr "Verfolgung für dieses Objekt deaktivieren" + +#~ msgid "Temporarily disable" +#~ msgstr "Temporär deaktivieren" + +#~ msgid "" +#~ "The notification delay will be disregarded if the host/service changes " +#~ "state before the next notification is scheduled to be sent out." +#~ msgstr "" +#~ "Die Benachrichtigungsverzögerung wird ignoriert wenn der Host/Service vor " +#~ "der nächsten zu versendenden Benachrichtigung seinen Status ändert." + +#~ msgid "" +#~ "This command is used to send a custom notification about hosts or " +#~ "services. Useful in emergencies when you need to notify admins of an " +#~ "issue regarding a monitored system or service." +#~ msgstr "" +#~ "Dieses Kommando ermöglicht es, personalisierte Benachrichtigungen für " +#~ "Hosts oder Services zu versenden. Nützlich ist das vor allem im Notfall, " +#~ "wenn man andere Administratoren über Probleme zu einem bestimmten System " +#~ "oder Dienst informieren möchte." + +#~ msgid "" +#~ "This command is used to submit a passive check result for particular " +#~ "hosts/services. It is particularly useful for resetting security-related " +#~ "objects to OK states once they have been dealt with." +#~ msgstr "" +#~ "Dieses Kommando erlaubt es, ein passives Check-Ergebnis für einen " +#~ "bestimmten Host oder Service einzuliefern. Das ist for allem dann " +#~ "nützlich, wenn man sicherheitsrelevante Objekte zurück auf einen OK-" +#~ "Status setzen möchte, sobald man sich darum gekümmert hat." + +#~ msgid "Triggered by" +#~ msgstr "Ausgelöst von" + +#~ msgid "Unhandled services with state %s" +#~ msgstr "Unbestätigte Services mit Status %s" + +#~ msgid "Will be re-enabled in %s" +#~ msgstr "Wird in %s wieder aktiviert" + +#~ msgid "Your new comment has been submitted" +#~ msgstr "Dein neuer Kommentar wurde gesendet" + +#~ msgid "critical events on " +#~ msgstr "kritische Ereignisse auf" + +#~ msgid "ok" +#~ msgstr "ok" + +#~ msgid "on" +#~ msgstr "auf" #~ msgid "No entries found" #~ msgstr "Keine Einträge gefunden" -#~ msgid "No host found" -#~ msgstr "Kein Host gefunden" - #~ msgid "Since" #~ msgstr "Seit" diff --git a/modules/monitoring/application/locale/it_IT/LC_MESSAGES/monitoring.mo b/modules/monitoring/application/locale/it_IT/LC_MESSAGES/monitoring.mo new file mode 100644 index 000000000..996008522 Binary files /dev/null and b/modules/monitoring/application/locale/it_IT/LC_MESSAGES/monitoring.mo differ diff --git a/modules/monitoring/application/locale/it_IT/LC_MESSAGES/monitoring.po b/modules/monitoring/application/locale/it_IT/LC_MESSAGES/monitoring.po new file mode 100644 index 000000000..bfff260f1 --- /dev/null +++ b/modules/monitoring/application/locale/it_IT/LC_MESSAGES/monitoring.po @@ -0,0 +1,4256 @@ +# Icinga Web 2 - Head for multiple monitoring backends. +# Copyright (C) 2015 Icinga Development Team +# This file is distributed under the same license as Monitoring Module. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Monitoring Module (2.0.0~alpha4)\n" +"Report-Msgid-Bugs-To: dev@icinga.org\n" +"POT-Creation-Date: 2015-03-13 17:04+0100\n" +"PO-Revision-Date: 2015-03-13 17:05+0100\n" +"Last-Translator: Thomas Gelf \n" +"Language: it_IT\n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.5.4\n" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:382 +msgid " Down Hosts (Handled)" +msgstr "Host Down (Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:383 +msgid " Down Hosts (Unhandled)" +msgstr "Host Down (Non Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:350 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:404 +msgid " Down Services (Handled)" +msgstr "Servizi Down (Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:351 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:405 +msgid " Down Services (Unhandled)" +msgstr "Servizi Down (Non Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:386 +msgid " Pending Hosts" +msgstr "Host in Pending" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:354 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:408 +msgid " Pending Services" +msgstr "Servizi in Pending" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:384 +msgid " Unreachable Hosts (Handled)" +msgstr "Host Unreachable (Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:385 +msgid " Unreachable Hosts (Unhandled)" +msgstr "Host Unreachable (Non Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:352 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:406 +msgid " Unreachable Services (Handled)" +msgstr "Servizi Unreachable (Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:353 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:407 +msgid " Unreachable Services (Unhandled)" +msgstr "Servizi Unreachable (Non Gestiti) " + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:381 +msgid " Up Hosts" +msgstr "Host Up" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:401 +msgid " Up Services" +msgstr "Servizi Up" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:348 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:402 +msgid " Warning Services (Handled)" +msgstr "Servizi in Warning (Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:349 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:403 +msgid " Warning Services (Unhandled)" +msgstr "Servizi in Warning (Non Gestiti)" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:73 +#, php-format +msgid "%d Active" +msgid_plural "%d Active" +msgstr[0] "%d Attivo" +msgstr[1] "%d Attivi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:109 +#, php-format +msgid "%d Disabled" +msgid_plural "%d Disabled" +msgstr[0] "%d Disabilitato" +msgstr[1] "%d Disabilitati" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:35 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:91 +#, php-format +msgid "%d Passive" +msgid_plural "%d Passive" +msgstr[0] "%d Passivo" +msgstr[1] "%d Passivi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:43 +#, php-format +msgid "%d hosts down on %s" +msgstr "%d host down in %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:21 +#, php-format +msgid "%d hosts ok on %s" +msgstr "%d host ok in %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:26 +#, php-format +msgid "%d hosts unreachable on %s" +msgstr "%d host unreachable in %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:64 +#, php-format +msgid "%d more ..." +msgstr "%d più ..." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:31 +#, php-format +msgid "%d services critical on %s" +msgstr "%d servizi critical in %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:53 +#, php-format +msgid "%d services ok on %s" +msgstr "%d servizi ok in %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:48 +#, php-format +msgid "%d services unknown on %s" +msgstr "%d servizi unknown in %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:37 +#, php-format +msgid "%d services warning on %s" +msgstr "%d servizi warning in %s" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Plugin/Perfdata.php:437 +#, php-format +msgid "%s %s (%s%%)" +msgstr "%s %s (%s%%)" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:28 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:32 +#, php-format +msgid "%s ago" +msgstr "%s fa" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:40 +#, php-format +msgctxt "timespan" +msgid "%s ago" +msgstr "%s fa" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:57 +#, php-format +msgid "%s has been up and running with PID %d since %s" +msgstr "%s è attivo e funzionante con il PID %d da %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:8 +#, php-format +msgid "%s hosts:" +msgstr "%s host:" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/not-running.phtml:5 +#, php-format +msgid "%s is currently not up and running" +msgstr "%s non è in esecuzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:64 +#, php-format +msgid "%s is not running" +msgstr "%s non è in esecuzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:43 +#, php-format +msgid "%s notications have been sent for this issue" +msgstr "%s notifiche inviate per questo problema" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Link.php:54 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:142 +#, php-format +msgctxt "Service running on host" +msgid "%s on %s" +msgstr "%s di %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:9 +#, php-format +msgid "%s services:" +msgstr "%s servizi:" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:84 +#, php-format +msgid "%u Acknowledged Host Problem" +msgid_plural "%u Acknowledged Host Problems" +msgstr[0] "%u Conferma al Problema dell'Host" +msgstr[1] "%u Conferme ai Problemi dell'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:84 +#, php-format +msgid "%u Acknowledged Service Problem" +msgid_plural "%u Acknowledged Service Problems" +msgstr[0] "%u Conferma al Problema del Servizio" +msgstr[1] "%u Conferma ai Problemi del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:17 +#, fuzzy, php-format +msgid "%u Active" +msgid_plural "%u Active" +msgstr[0] "%d Attivo" +msgstr[1] "%d Attivi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:8 +#, php-format +msgid "%u Host" +msgid_plural "%u Hosts" +msgstr[0] "%u Host" +msgstr[1] "%u Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:10 +#, fuzzy, php-format +msgid "%u Host DOWN" +msgid_plural "%u Hosts DOWN" +msgstr[0] "%d Host DOWN" +msgstr[1] "%d Host DOWN" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:18 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:139 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:218 +#, fuzzy, php-format +msgid "%u Host Disabled" +msgid_plural "%u Hosts Disabled" +msgstr[0] "%d Host Disabilitato" +msgstr[1] "%d Host Disabilitati" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:48 +#, fuzzy, php-format +msgid "%u Host Flapping" +msgid_plural "%u Hosts Flapping" +msgstr[0] "%d Host Instabile" +msgstr[1] "%d Host Instabili" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:33 +#, fuzzy, php-format +msgid "%u Host PENDING" +msgid_plural "%u Hosts PENDING" +msgstr[0] "%d Hosts PENDING" +msgstr[1] "%d Host PENDING" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:29 +#, fuzzy, php-format +msgid "%u Host UNREACHABLE" +msgid_plural "%u Hosts UNREACHABLE" +msgstr[0] "%d Host UNREACHABLE" +msgstr[1] "%d Host UNREACHABLE" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:15 +#, fuzzy, php-format +msgid "%u Host UP" +msgid_plural "%u Hosts UP" +msgstr[0] "%d Host UP" +msgstr[1] "%d Host UP" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:8 +#, fuzzy, php-format +msgid "%u Service" +msgid_plural "%u Services" +msgstr[0] "Servizio" +msgstr[1] "Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:73 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:173 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:252 +#, fuzzy, php-format +msgid "%u Service Disabled" +msgid_plural "%u Services Disabled" +msgstr[0] "%d Servizio Disabilitato" +msgstr[1] "%d Servizi Disabilitati" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:103 +#, fuzzy, php-format +msgid "%u Service Flapping" +msgid_plural "%u Services Flapping" +msgstr[0] "%d Servizio Instabile" +msgstr[1] "%d Servizi Instabili" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:46 +#, php-format +msgid "%u Unhandled Host Problem" +msgid_plural "%u Unhandled Host Problems" +msgstr[0] "%u Problema all'Host Non Gestito" +msgstr[1] "%u Problemi all'Host Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:46 +#, php-format +msgid "%u Unhandled Service Problem" +msgid_plural "%u Unhandled Service Problems" +msgstr[0] "%u Problema al Servizio Non Gestito" +msgstr[1] "%u Problemi al Servizio Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:13 +#, fuzzy, php-format +msgid "%u configured service:" +msgid_plural "%u configured services:" +msgstr[0] "%d servizio configurato:" +msgstr[1] "%d servizi configurati:" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:80 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:179 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:278 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:325 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:372 +#, fuzzy, php-format +msgid "%u is not checked at all" +msgid_plural "%u are not checked at all" +msgstr[0] "%d è stato controllato" +msgstr[1] "%d è stato controllato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:152 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:251 +#, fuzzy, php-format +msgid "%u is passively checked" +msgid_plural "%u are passively checked" +msgstr[0] "%d è controllato passivamente" +msgstr[1] "%d è controllato passivamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:110 +#, fuzzy, php-format +msgid "%u unhandled service" +msgid_plural "%u unhandled services" +msgstr[0] "%d servizio non gestito" +msgstr[1] "%d servizi non gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:73 +msgid "1 Year" +msgstr "1 Anno" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:74 +msgid "2 Years" +msgstr "2 Anni" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:70 +msgid "3 Months" +msgstr "3 Mesi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:97 +msgid "4 Hours" +msgstr "4 Ore" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:71 +msgid "4 Months" +msgstr "4 Mesi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:72 +msgid "8 Months" +msgstr "8 Mesi" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Plugin/Perfdata.php:437 +#, php-format +msgid "%s %s (%s%%)" +msgstr "%s %s (%s%%)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:453 +msgid "{title}: {value}m max. reaction time" +msgstr "{title}: {value} max. tempo di reazione" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:443 +msgid "{title}: {value}m min. reaction time" +msgstr "{title}: {value} min. tempo di reazione" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:296 +msgid "{title}:
    {value} of {sum} hosts are {label}" +msgstr "{title}:
    {value} di {sum} host sono {label}" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:247 +msgid "{title}:
    {value} of {sum} services are {label}" +msgstr "{title}:
    {value} di {sum} servizi sono {label}" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:38 +#, php-format +msgid "A notication has been sent for this issue %s ago" +msgstr "Una notifica è stata inviata per questo problema %s fa" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:78 +msgid "Ack removed" +msgstr "Conferma rimossa" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:73 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:39 +msgid "Acknowledge" +msgstr "Conferma" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:68 +#, php-format +msgid "Acknowledge %u unhandled host problem" +msgid_plural "Acknowledge %u unhandled host problems" +msgstr[0] "Conferma %u Problema dell'Host Non Gestito" +msgstr[1] "Conferma %u Problemi dell'Host Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:68 +#, php-format +msgid "Acknowledge %u unhandled service problem" +msgid_plural "Acknowledge %u unhandled service problems" +msgstr[0] "Conferma %u Problema del Servizio Non Gestito" +msgstr[1] "Conferma %u Problemi del Servizio Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:71 +msgid "Acknowledge Host Problem" +msgstr "Conferma il Problema dell'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:184 +msgid "Acknowledge Host Problems" +msgstr "Conferma i Problemi dell'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:48 +msgid "Acknowledge Service Problem" +msgstr "Conferma il Problema del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:233 +msgid "Acknowledge Service Problems" +msgstr "Conferma i Problemi del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:34 +msgid "Acknowledge problem" +msgid_plural "Acknowledge problems" +msgstr[0] "Conferma Problema" +msgstr[1] "Conferma Problemi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:46 +msgid "" +"Acknowledge this problem, suppress all future notifications for it and tag " +"it as being handled" +msgstr "" +"Conferma questo problema, disabilita tutte le notifiche future e marca come " +"gestito" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:75 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:31 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:130 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:229 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:9 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:9 +msgid "Acknowledged" +msgstr "Confermato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:51 +msgid "Acknowledgement" +msgstr "Conferma" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:59 +msgid "Acknowledgements" +msgstr "Conferme" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:157 +msgid "Acknowledging problem.." +msgid_plural "Acknowledging problems.." +msgstr[0] "Conferma in corso.." +msgstr[1] "Conferme in corso.." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/actions.phtml:44 +msgid "Actions" +msgstr "Azioni" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:70 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:98 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:24 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:24 +msgid "Active And Passive Checks Disabled" +msgstr "Controlli Attivi e Passivi Disabilitati" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:39 +msgid "Active Checks" +msgstr "Controlli Attivi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:72 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:100 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:22 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:22 +msgid "Active Checks Disabled" +msgstr "Controlli Attivi Disabilitati" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:88 +msgid "Active Host Checks Being Executed" +msgstr "Esegui Controlli Attivi sugli Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:97 +msgid "Active Service Checks Being Executed" +msgstr "Esegui Controlli Attivi sui Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:120 +msgid "Active checks" +msgstr "Controlli Attivi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:83 +msgid "Add Host Comment" +msgstr "Aggiungi commento all'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:48 +msgid "Add New Backend" +msgstr "Aggiungi Nuovo Backend" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:164 +msgid "Add New Instance" +msgstr "Aggiungi Nuova Istanza" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:60 +msgid "Add Service Comment" +msgstr "Aggiungi Commento al Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:14 +msgid "Add a new comment to this host" +msgstr "Aggiungi un nuovo commento all'host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:25 +msgid "Add a new comment to this service" +msgstr "Aggiungi un nuovo commento al servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:28 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:8 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:19 +#, fuzzy +msgid "Add comment" +msgid_plural "Add comments" +msgstr[0] "Aggiungi commento" +msgstr[1] "Aggiungi commento" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:83 +msgid "Adding comment.." +msgid_plural "Adding comments.." +msgstr[0] "Aggiunta commento.." +msgstr[1] "Aggiunta commenti.." + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:135 +msgid "Address" +msgstr "Indirizzo" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:201 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:44 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:48 +msgid "Alert Summary" +msgstr "Storico Allarmi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:11 +msgid "Alert summary" +msgstr "Storico Allarmi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:377 +msgid "Alias" +msgstr "Alias" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:158 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:237 +msgid "All Hosts Enabled" +msgstr "Tutti gli Host abilitati" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php:26 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:28 +msgid "All Services" +msgstr "Tutti i Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:92 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:192 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:271 +msgid "All Services Enabled" +msgstr "Tutti i Servizi Abilitati" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:178 +msgid "All backends are disabled" +msgstr "Tutti i backend sono disabilitati" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:16 +msgid "Allow acknowledging host and service problems" +msgstr "Consenti la Conferma dei problemi di Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:24 +msgid "Allow adding and deleting host and service comments" +msgstr "Consenti la rimozione di Commenti da Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:8 +msgid "Allow all commands" +msgstr "Consenti tutti i Comandi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:28 +msgid "Allow commenting on hosts and services" +msgstr "Consenti l'aggiunta di Commenti su Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:32 +msgid "Allow deleting host and service comments" +msgstr "Consenti la rimozione di Commenti da Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:44 +msgid "Allow deleting host and service downtimes" +msgstr "Consenti la cancellazione di Manutenzioni da Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:52 +msgid "" +"Allow processing commands for toggling features on an instance-wide basis" +msgstr "Consenti di abilitare/disabilitare funzionalità globali" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:56 +msgid "" +"Allow processing commands for toggling features on host and service objects" +msgstr "Consenti di abilitare/disabilitare funzionalità di host e servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:48 +msgid "Allow processing host and service check results" +msgstr "Consenti l'invio di risultati passivi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:20 +msgid "Allow removing problem acknowledgements" +msgstr "Consenti la rimozione delle Conferme" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:36 +msgid "Allow scheduling and deleting host and service downtimes" +msgstr "Consenti la pianificazione di Manutenzioni su Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:12 +msgid "Allow scheduling host and service checks" +msgstr "Consenti la schedulazione di Controlli su Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:40 +msgid "Allow scheduling host and service downtimes" +msgstr "Consenti la pianificazione di Manutenzioni su Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:60 +msgid "Allow sending custom notifications for hosts and services" +msgstr "Consenti l'invio di notifiche personalizzate per host e servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:21 +msgid "Apply" +msgstr "Applica" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:131 +msgid "Are you sure you want to remove this instance?" +msgstr "Sicuro di voler rimuovere l'istanza?" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:300 +msgid "Author" +msgstr "Autore" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:33 +msgid "Average" +msgstr "Media" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:108 +msgid "Average services per host" +msgstr "Media di Servizi per Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:439 +msgid "Avg (min)" +msgstr "Media (min)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:78 +#, php-format +msgid "Backend \"%s\" successfully removed." +msgstr "Backend \"%s\" rimosso correttamente." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:216 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:28 +msgid "Backend Name" +msgstr "Nome Backend" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:226 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:44 +msgid "Backend Type" +msgstr "Tipo Backend" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:75 +msgid "Backends" +msgstr "Backend" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:77 +msgid "Broadcast" +msgstr "Trasmetti a tutti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:52 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:52 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:184 +msgid "CRITICAL" +msgstr "CRITICAL" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:62 +msgctxt "icinga.state" +msgid "CRITICAL" +msgstr "CRITICAL" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checksource.phtml:4 +msgid "Check Source" +msgstr "Eseguito da" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:51 +msgid "Check Time" +msgstr "Ora del Controllo" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:61 +msgid "Check attempts" +msgstr "Tentativi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:70 +msgid "Check execution time" +msgstr "Durata " + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:76 +msgid "Check latency" +msgstr "Latenza" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:37 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:39 +msgid "Check now" +msgstr "Controlla ora" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php:78 +msgid "" +"Check this to not to validate connectivity with the given Livestatus socket" +msgstr "" +"Spunta questa casella per non verificare la connettività con il socket " +"Livestatus fornito" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/IdoResourcePage.php:77 +msgid "" +"Check this to not to validate connectivity with the given database server" +msgstr "" +"Spunta questa casella per non verificare la connettività con il database " +"fornito" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:38 +msgid "Child Hosts" +msgstr "Host Figli" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/SecurityConfigForm.php:58 +msgid "" +"Comma separated case insensitive list of protected custom variables. Use * " +"as a placeholder for zero or more wildcard characters. Existance of those " +"custom variables will be shown, but their values will be masked." +msgstr "" +"Lista delle variabili riservate (case insensitive) separate da virgola. " +"Usare * come carattere jolly. Le variabili fornite verranno mostrate ma il " +"loro contenuto sarà mascherato." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/command.phtml:9 +msgid "Command" +msgstr "Comando" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php:30 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:64 +msgid "Command File" +msgstr "File dei comandi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:49 +msgid "Commands" +msgstr "Comandi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:49 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:43 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:65 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:52 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:62 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:67 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:46 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:63 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:54 +msgid "Comment" +msgstr "Commento" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:489 +msgid "Comment Timestamp" +msgstr "Timestamp Commento" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:492 +msgid "Comment Type" +msgstr "Tipo Commento" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:68 +msgid "Comment deleted" +msgstr "Commento rimosso" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:36 +msgid "Comment was caused by a downtime." +msgstr "Commento generato da una manutenzione." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:26 +msgid "Comment was caused by a flapping host or service." +msgstr "Commento generato da un host o servizio instabile." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:40 +msgid "Comment was caused by an acknowledgement." +msgstr "Commento generato dalla conferma di un problema" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:31 +msgid "Comment was created by an user." +msgstr "Commento creato da un utente." + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:163 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:468 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:54 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:66 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/comments.phtml:2 +msgid "Comments" +msgstr "Commenti" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:196 +#, php-format +msgid "Configuration for backend %s is disabled" +msgstr "Configurazione peril backend %s disabilitata" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:79 +msgid "" +"Configure how to protect your monitoring environment against prying eyes" +msgstr "" +"Configura come proteggere l'ambiente di monitoraggio da occhi indiscreti" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:74 +msgid "Configure how to retrieve monitoring information" +msgstr "Configura come recuperare le informazioni di monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:434 +msgid "Contact Groups" +msgstr "Gruppi di Contatti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:4 +msgid "Contact details" +msgstr "Dettagli del Contatto" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:159 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/contacts.phtml:37 +msgid "Contactgroups" +msgstr "Gruppi di Contatti" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:155 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:351 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/contacts.phtml:17 +msgid "Contacts" +msgstr "Contatti" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:299 +msgid "Contains host states of each service group." +msgstr "Contiene gli stati degli host per ogni gruppo di servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:240 +msgid "Contains service states for each service group." +msgstr "Contiene gli stati dei servizi per ogni gruppo di servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:55 +msgid "Could not find any valid monitoring backend resources" +msgstr "Non è stata trovata nessun backend di monitoraggio valido" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:53 +msgid "Create New Instance" +msgstr "Crea una Nuova Istanza" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:9 +msgid "Create New Monitoring Backend" +msgstr "Crea un Nuovo Backend di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:264 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:118 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Critical" +msgstr "Critical" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:123 +msgid "Current Downtimes" +msgstr "Manutenzioni in corso" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:227 +msgid "Current Host State" +msgstr "Stato Corrente degli Host" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:217 +msgid "Current Incidents" +msgstr "Problemi Correnti" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:223 +msgid "Current Service State" +msgstr "Stato Corrente dei Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:133 +msgid "Current State" +msgstr "Stato Corrente" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Host.php:176 +msgid "DOWN" +msgstr "DOWN" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:57 +msgctxt "icinga.state" +msgid "DOWN" +msgstr "DOWN" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:104 +msgid "Database Name" +msgstr "Nome Database" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:83 +msgid "Database Resource" +msgstr "Risorsa Database" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:92 +msgid "Database Type" +msgstr "Tipo Database" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:643 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:646 +msgid "Day" +msgstr "Giorno" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:468 +msgid "Defect Chart" +msgstr "Grafico dei Problemi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:489 +msgid "Defects" +msgstr "Problemi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:46 +msgid "Define what should be done with the child hosts of the hosts." +msgstr "Definire che azione compiere con gli Host figli" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php:64 +msgid "Delete this comment" +msgstr "Cancella questo commento" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php:64 +msgid "Delete this downtime" +msgstr "Cancella questa manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php:89 +msgid "Deleting comment.." +msgstr "Rimozione commento..." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php:89 +msgid "Deleting downtime.." +msgstr "Cancellazione Manutenzione..." + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ProcessController.php:99 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:24 +msgid "Disable Notifications" +msgstr "Disabilita le Notifiche" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:208 +msgid "Disable This Backend" +msgstr "Disabilita Backend" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:67 +msgid "Disable notifications for a specific time on a program-wide basis" +msgstr "Disabilita le notifiche per un periodo specifico sull'intero sistema" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:69 +msgid "Disable temporarily" +msgstr "Disabilita Temporaneamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:61 +msgid "Disabling host and service notifications.." +msgstr "Disabilitazione Notifiche di Host e Servizi in corso..." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:41 +msgid "Do nothing with child hosts" +msgstr "Non applicare agli Host figli" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:314 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:139 +msgid "Down" +msgstr "Down" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:35 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:68 +msgid "Downtime" +msgstr "Manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:88 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:120 +msgid "Downtime End" +msgstr "Fine Manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:83 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:115 +msgid "Downtime Start" +msgstr "Inizio Manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:88 +msgid "Downtime removed" +msgstr "Manutenzione cancellata" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:167 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:269 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:56 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:2 +msgid "Downtimes" +msgstr "Manutenzioni" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:305 +msgid "Duration" +msgstr "Durata" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:33 +msgid "Edit Existing Backend" +msgstr "Modifica Backend Esistente" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:150 +msgid "Edit Existing Instance" +msgstr "Modifica Istanza Esistente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:27 +#, php-format +msgid "Edit monitoring backend %s" +msgstr "Modifica backend di monitoraggio %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:71 +#, php-format +msgid "Edit monitoring instance %s" +msgstr "Modifica istanza di monitoraggio %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:378 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:28 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:22 +msgid "Email" +msgstr "Email" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:302 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:88 +msgid "End Time" +msgstr "Ora Fine" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:69 +msgid "Ended downtimes" +msgstr "Manutenzioni Completate" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:159 +msgid "" +"Enter here the duration of the downtime. The downtime will be automatically " +"deleted after this time expired." +msgstr "" +"Inserire qui la durata della manutenzione. La manutenzione sarà cancellata " +"automaticamente allo scadere del tempo." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:90 +msgid "" +"Enter the expire date and time for this acknowledgement here. Icinga will " +"delete the acknowledgement after this time expired." +msgstr "" +"Inserire qui la durata della conferma del problema. Icinga cancellerà " +"automaticamente la conferma allo scadere del tempo." + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:299 +msgid "Entry Time" +msgstr "Ora Inserimento" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:183 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:390 +msgid "Event Grid" +msgstr "Griglia Eventi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:75 +msgid "Event Handler" +msgstr "Gestore Eventi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:206 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:208 +msgid "Event Handlers" +msgstr "Gestori Eventi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:106 +msgid "Event Handlers Enabled" +msgstr "Gestore Eventi Abilitato" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:188 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:613 +msgid "Event Overview" +msgstr "Panoramica Eventi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:187 +msgid "Events" +msgstr "Eventi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:127 +msgid "Execution time" +msgstr "Tempo di Esecuzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:493 +msgid "Expiration" +msgstr "Scadenza" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:43 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:87 +msgid "Expire Time" +msgstr "Ora Scadenza" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:43 +msgid "Expires" +msgstr "Scade" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:29 +msgid "Feature Commands" +msgstr "Controllo Funzionalità" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:107 +msgid "Fixed" +msgstr "Rigido" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:84 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:6 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:8 +msgid "Flap Detection" +msgstr "Controllo Instabilità" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:115 +msgid "Flap Detection Enabled" +msgstr "Controllo Instabilità Attivato" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:86 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:25 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:61 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:57 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:81 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:93 +msgid "Flapping" +msgstr "Instabile" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:66 +msgid "Flapping Stopped" +msgstr "Instabilità Terminata" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:98 +msgid "Flapping stopped" +msgstr "Instabilità Terminata" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:108 +msgid "Flexible" +msgstr "Flessibile" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:157 +msgid "Flexible Duration" +msgstr "Durata Flessibile" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:62 +msgid "Force Check" +msgstr "Forza Controllo" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:64 +msgid "Forced" +msgstr "Forzato" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:67 +msgid "From" +msgstr "Da" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:47 +msgid "Global Host Event Handler" +msgstr "Gestore Eventi Host Globale" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:41 +msgid "Global Service Event Handler" +msgstr "Gestore Eventi Servizi Globale" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:49 +msgid "Hard state changes" +msgstr "Cambi di Stato HARD" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:327 +msgid "Healing Chart" +msgstr "Grafico Ripristini" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:180 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:258 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:69 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:233 +msgid "History" +msgstr "Storico" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:297 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:490 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:211 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:31 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:63 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:96 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:186 +msgid "Host" +msgstr "Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml:6 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml:21 +#, php-format +msgid "Host (%u)" +msgid_plural "Hosts (%u)" +msgstr[0] "Host (%u)" +msgstr[1] "Host (%u)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:229 +msgid "Host Address" +msgstr "Indirizzo Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:133 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:155 +msgid "Host Checks" +msgstr "Controlli Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:25 +msgid "Host Group" +msgstr "Gruppo di Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:298 +msgid "Host Group Chart" +msgstr "Grafico dei Gruppi di Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:596 +msgid "Host Group Name" +msgstr "Nome del Gruppo di Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:561 +msgid "Host Groups" +msgstr "Gruppi di Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:381 +msgid "Host Notification Timeperiod" +msgstr "Periodo delle Notifiche per l'Host" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:111 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:227 +msgid "Host Problems" +msgstr "Host con Problemi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:226 +msgid "Host Severity" +msgstr "Impatto Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:64 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:158 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:85 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:207 +msgid "Host State" +msgstr "Stato Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:2 +msgid "Host and Service Checks" +msgstr "Controlli di Host e Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/disable-notifications.phtml:8 +msgid "Host and service notifications are already disabled." +msgstr "Notifiche per Host e Servizi sono già disabilitate." + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:34 +msgid "Host not found" +msgstr "Host non trovato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:48 +msgid "Host notification period" +msgstr "Periodo delle Notifiche per l'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:40 +msgid "Host or service not found" +msgstr "Host o Servizio non trovato" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:90 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:151 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml:16 +msgid "Hostgroups" +msgstr "Gruppi di Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:134 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:228 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:657 +msgid "Hostname" +msgstr "Nome Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:33 +msgid "Hostname or address of the remote Icinga instance" +msgstr "Nome Host o Indirizzo dell'istanza remota di Icinga" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:88 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:139 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:302 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:80 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:97 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:101 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:84 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:7 +msgid "Hosts" +msgstr "Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:640 +msgid "Hour" +msgstr "Ora" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:137 +msgid "Hours" +msgstr "Ore" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:44 +msgid "" +"Icinga Web 2 will protect your monitoring environment against prying eyes " +"using the configuration specified below:" +msgstr "" +"Icinga Web 2 proteggerà il tuo ambiente di monitoraggio da occhi indiscreti " +"con la configurazione seguente:" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:76 +#, php-format +msgid "" +"Icinga Web 2 will retrieve information from your monitoring environment " +"using a backend called \"%s\" and the specified resource below:" +msgstr "" +"Icinga Web 2 recupererà le informazioni dal tuo ambiente di monitoraggio " +"usando il backend chiamato \"%s\" e le seguenti risorse:" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:75 +#, php-format +msgid "" +"Icinga Web 2 will use the named pipe located at \"%s\" to send commands to " +"your monitoring instance." +msgstr "" +"Icinga Web 2 utilizzerà la pipe situata in \"%s\" per inviare comandi alla " +"vostra istanza di monitoraggio." + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:48 +#, php-format +msgid "" +"Icinga Web 2 will use the named pipe located on a remote machine at \"%s\" " +"to send commands to your monitoring instance by using the connection details " +"listed below:" +msgstr "" +"Icinga Web 2 userà la pipe situata nella macchina remota in \"%s\" per " +"inviare comandi alla vostra istanza di monitoraggio utilizzando i seguenti " +"dettagli:" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:74 +msgid "If the acknowledgement should expire, check this option." +msgstr "" +"Spuntare questa casella se si vuole dare una scadenza alla Conferma del " +"Problema." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:80 +msgid "" +"If you check this option, a notification is sent to all normal and escalated " +"contacts." +msgstr "" +"Selezionando questa opzione verrà inviata una notifica ai contatti " +"configurati." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:67 +msgid "" +"If you check this option, a notification is sentregardless of the current " +"time and whether notifications are enabled." +msgstr "" +"Selezionando questa opzione verrà inviata una notifica anche se le notifiche " +"sono disabilitate e ignorando eventuali restrizioni di orario." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:126 +msgid "" +"If you do not want an acknowledgement notification to be sent out to the " +"appropriate contacts, uncheck this option." +msgstr "" +"Deselezionare questa opzione in caso non si voglia inviare una Notifica di " +"Conferma ai contatti configurati." + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:124 +msgid "" +"If you have still any environments or views referring to this instance, you " +"won't be able to send commands anymore after deletion." +msgstr "" +"Nel caso qualche altro ambiente o vista faccia riferimento a questa istanza, " +"non sarà più possibile inviare comandi dopo la cancellazione." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:101 +msgid "" +"If you select the fixed option, the downtime will be in effect between the " +"start and end times you specify whereas a flexible downtime starts when the " +"host or service enters a problem state sometime between the start and end " +"times you specified and lasts as long as the duration time you enter. The " +"duration fields do not apply for fixed downtimes." +msgstr "" +"Con l'opzione Rigido la manutenzione avrà effetto dall'ora di inizio all'ora " +"di fine specificate. Se si seleziona l'opzione Flessibile la manutenzione " +"partirà quando il servizio raggiungerà uno stato non OK e finirà in base " +"alla durata inserita (anche all'esterno dell'ora di inizio e fine " +"specificata). Il campi della Durata non avranno effetto sulle Manutenzioni " +"Rigide." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:64 +msgid "" +"If you select this option, Icinga will force a check regardless of both what " +"time the scheduled check occurs and whether or not checks are enabled." +msgstr "" +"Selezionando questa opzione, Icinga forzerà l'esecuzione del controllo " +"ignorando se controlli attivi sono disabilitati e gli orari di schedulazione." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:58 +msgid "" +"If you uncheck this option, the comment will automatically be deleted the " +"next time Icinga is restarted." +msgstr "" +"Selezionando questa opzione il commento verrà automaticamente rimosso al " +"prossimo riavvio di Icinga." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:114 +msgid "" +"If you want the acknowledgement to disable notifications until the host or " +"service recovers, check this option." +msgstr "" +"Selezionando questa opzione le notifiche verranno disabilitate fino al " +"ripristino dell'host o servizio." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:51 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:45 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:67 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:54 +msgid "" +"If you work with other administrators, you may find it useful to share " +"information about the the host or service that is having problems. Make sure " +"you enter a brief description of what you are doing." +msgstr "" +"Se lavori con altri amministratori potresti trovare utile condividere le " +"informazioni riguardo l'host e i servizi che stanno avendo problemi. " +"Assicurati di inserire una breve descrizione delle azioni che stai svolgendo." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:63 +msgid "" +"If you would like the comment to remain even when the acknowledgement is " +"removed, check this option." +msgstr "" +"Selezionando questa opzione il commento rimarrà anche dopo la fine Problema." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:56 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:65 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:89 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:83 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:17 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:17 +msgid "In Downtime" +msgstr "In Manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:147 +msgid "" +"In case it's desired that a TCP connection is being used by Icinga Web 2 to " +"access a Livestatus interface, the Sockets module for PHP is required." +msgstr "" +"Il modulo PHP Sockets è richiesto nel caso sia necessaria stabilire una " +"connessione TCP per accedere all'interfaccia Livestatus tramite Icinga Web 2." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:58 +msgid "Instance" +msgstr "Istanza" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:160 +#, php-format +msgid "Instance \"%s\" created successfully." +msgstr "Istanza \"%s\" creata correttamente." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:163 +#, php-format +msgid "Instance \"%s\" edited successfully." +msgstr "Istanza \"%s\" modificata correttamente." + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:114 +#, php-format +msgid "Instance \"%s\" successfully removed." +msgstr "Istanza \"%s\" modificata correttamente." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:191 +msgid "Instance Name" +msgstr "Nome Istanza" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:200 +msgid "Instance Type" +msgstr "Tipo Istanza" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:74 +msgid "Instance already exists" +msgstr "Istanza già esistente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:71 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:119 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:138 +msgid "Instance name missing" +msgstr "Nome istanza mancante" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:50 +#, php-format +msgid "Invalid instance type \"%s\" given" +msgstr "Tipo \"%s\" di Istanza non valido" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:296 +msgid "Is In Effect" +msgstr "In Corso" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/WelcomePage.php:42 +msgid "" +"It offers various status and reporting views with powerful filter " +"capabilities that allow you to keep track of the most important events in " +"your monitoring environment." +msgstr "" +"Offre varie viste di reportistica con la capacità di configurare filtri " +"specifici per permettere di tenere traccia degli eventi più importanti degli " +"eventi di monitoraggio." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Label" +msgstr "Etichetta" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:136 +msgid "Last Check" +msgstr "Ultimo Controllo" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:77 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:93 +msgid "Last Comment: " +msgstr "Ultimo Commento: " + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:31 +msgid "Last External Command Check" +msgstr "Ultimo Comando Esterno" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:230 +msgid "Last Host Check" +msgstr "Ultimo Controllo dell'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:35 +msgid "Last Log File Rotation" +msgstr "Ultima Rotazione del File di Log" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:24 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:24 +msgid "Last Problem" +msgstr "Ultimo Problema" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:225 +msgid "Last Service Check" +msgstr "Ultimo Controllo del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:27 +msgid "Last Status Update" +msgstr "Ultimo Aggiornamento di Stato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:18 +msgid "Last check" +msgstr "Ultimo Controllo" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:126 +msgid "Latency" +msgstr "Latenza" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:16 +msgid "Legend" +msgstr "Legenda" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:292 +#, fuzzy, php-format +msgid "" +"List %s service that is currenlty in state PENDING in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state PENDING in service group \"%s\"" +msgstr[0] "" +"Mostra %s servizio in stato warning gestito nel gruppo di servizi %s" +msgstr[1] "Mostra %s servizi in stato warning gestiti nel gruppo di servizi %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:157 +#, fuzzy, php-format +msgid "" +"List %s service that is currently in state CRITICAL (Acknowledged) in " +"service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state CRITICAL (Acknowledged) in " +"service group \"%s\"" +msgstr[0] "" +"Mostra %s servizio in stato warning gestito nel gruppo di servizi %s" +msgstr[1] "Mostra %s servizi in stato warning gestiti nel gruppo di servizi %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:133 +#, fuzzy, php-format +msgid "" +"List %s service that is currently in state CRITICAL in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state CRITICAL in service group \"%s" +"\"" +msgstr[0] "" +"Mostra %s servizio in stato warning gestito nel gruppo di servizi %s" +msgstr[1] "Mostra %s servizi in stato warning gestiti nel gruppo di servizi %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:106 +#, fuzzy, php-format +msgid "List %s service that is currently in state OK in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state OK in service group \"%s\"" +msgstr[0] "Mostra %s servizio in stato pending nel gruppo di servizi %s" +msgstr[1] "Mostra %s servizi in stato pending nel gruppo di servizi %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:211 +#, fuzzy, php-format +msgid "" +"List %s service that is currently in state UNKNOWN (Acknowledged) in service " +"group \"%s\"" +msgid_plural "" +"List %s services which are currently in state UNKNOWN (Acknowledged) in " +"service group \"%s\"" +msgstr[0] "" +"Mostra %s servizio in stato warning gestito nel gruppo di servizi %s" +msgstr[1] "Mostra %s servizi in stato warning gestiti nel gruppo di servizi %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:187 +#, fuzzy, php-format +msgid "" +"List %s service that is currently in state UNKNOWN in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state UNKNOWN in service group \"%s\"" +msgstr[0] "" +"Mostra %s servizio in stato warning gestito nel gruppo di servizi %s" +msgstr[1] "Mostra %s servizi in stato warning gestiti nel gruppo di servizi %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:265 +#, fuzzy, php-format +msgid "" +"List %s service that is currently in state WARNING (Acknowledged) in service " +"group \"%s\"" +msgid_plural "" +"List %s services which are currently in state WARNING (Acknowledged) in " +"service group \"%s\"" +msgstr[0] "" +"Mostra %s servizio in stato warning gestito nel gruppo di servizi %s" +msgstr[1] "Mostra %s servizi in stato warning gestiti nel gruppo di servizi %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:241 +#, fuzzy, php-format +msgid "" +"List %s service that is currently in state WARNING in service group \"%s\"" +msgid_plural "" +"List %s services which are currently in state WARNING in service group \"%s\"" +msgstr[0] "" +"Mostra %s servizio in stato warning gestito nel gruppo di servizi %s" +msgstr[1] "Mostra %s servizi in stato warning gestiti nel gruppo di servizi %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:123 +#, fuzzy, php-format +msgid "List %s unhandled service problem on host %s" +msgid_plural "List %s unhandled service problems on host %s" +msgstr[0] "Mostra %s problema ai servizi dell'host %s" +msgstr[1] "Mostra %s problemi ai servizi dell'host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:111 +#, fuzzy, php-format +msgctxt "timeline.link.title" +msgid "List %u %s registered %s" +msgstr "Mostra %u %s salvati %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:24 +#, fuzzy, php-format +msgid "List %u actively checked host" +msgid_plural "List %u actively checked hosts" +msgstr[0] "%d è controllato passivamente" +msgstr[1] "%d è controllato passivamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:80 +#, fuzzy, php-format +msgid "List %u actively checked service" +msgid_plural "List %u actively checked services" +msgstr[0] "%d è controllato passivamente" +msgstr[1] "%d è controllato passivamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:112 +#, php-format +msgid "List %u host comment" +msgid_plural "List %u host comments" +msgstr[0] "Mostra %u commento dell' host" +msgstr[1] "Mostra %u commenti dell' host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:97 +#, fuzzy, php-format +msgid "List %u host currently in downtime" +msgid_plural "List %u hosts currently in downtime" +msgstr[0] "Mostra %u host in manutenzione" +msgstr[1] "Mostra %u host in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:27 +#, fuzzy, php-format +msgid "List %u host for which flap detection has been disabled" +msgid_plural "List %u hosts for which flap detection has been disabled" +msgstr[0] "Mostra tutti gli host dove il controllo di instabilità è abilitato" +msgstr[1] "Mostra tutti gli host dove il controllo di instabilità è abilitato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:148 +#, fuzzy, php-format +msgid "List %u host for which notifications are suppressed" +msgid_plural "List %u hosts for which notifications are suppressed" +msgstr[0] "Mostra tutti gli host dove le notifiche sono abilitate" +msgstr[1] "Mostra tutti gli host dove le notifiche sono abilitate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:57 +#, fuzzy, php-format +msgid "List %u host that is currently flapping" +msgid_plural "List %u hosts which are currently flapping" +msgstr[0] "Mostra %u host in manutenzione" +msgstr[1] "Mostra %u host in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:17 +#, fuzzy, php-format +msgid "List %u host that is currently in state DOWN" +msgid_plural "List %u hosts which are currently in state DOWN" +msgstr[0] "Mostra %u host in manutenzione" +msgstr[1] "Mostra %u host in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:59 +#, fuzzy, php-format +msgid "List %u host that is currently in state DOWN (Acknowledged)" +msgid_plural "List %u hosts which are currently in state DOWN (Acknowledged)" +msgstr[0] "Mostra %u host in manutenzione" +msgstr[1] "Mostra %u host in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:126 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:40 +#, fuzzy, php-format +msgid "List %u host that is currently in state PENDING" +msgid_plural "List %u hosts which are currently in state PENDING" +msgstr[0] "Mostra %u host in manutenzione" +msgstr[1] "Mostra %u host in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:84 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:39 +#, fuzzy, php-format +msgid "List %u host that is currently in state UNREACHABLE" +msgid_plural "List %u hosts which are currently in state UNREACHABLE" +msgstr[0] "Mostra %u host in manutenzione" +msgstr[1] "Mostra %u host in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:104 +#, php-format +msgid "List %u host that is currently in state UNREACHABLE (Acknowledged)" +msgid_plural "" +"List %u hosts which are currently in state UNREACHABLE (Acknowledged)" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml:18 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:22 +#, fuzzy, php-format +msgid "List %u host that is currently in state UP" +msgid_plural "List %u hosts which are currently in state UP" +msgstr[0] "Mostra %u host in manutenzione" +msgstr[1] "Mostra %u host in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:60 +#, fuzzy, php-format +msgid "List %u host that is not being checked at all" +msgid_plural "List %u hosts which are not being checked at all" +msgstr[0] "%d è stato controllato" +msgstr[1] "%d è stato controllato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:227 +#, fuzzy, php-format +msgid "List %u host that is not processing any event handlers" +msgid_plural "List %u hosts which are not processing any event handlers" +msgstr[0] "Mostra tutti gli host dove il gestore eventi è abilitato" +msgstr[1] "Mostra tutti gli host dove il gestore eventi è abilitato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:42 +#, fuzzy, php-format +msgid "List %u passively checked host" +msgid_plural "List %u passively checked hosts" +msgstr[0] "%d è controllato passivamente" +msgstr[1] "%d è controllato passivamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:98 +#, fuzzy, php-format +msgid "List %u passively checked service" +msgid_plural "List %u passively checked services" +msgstr[0] "%d è controllato passivamente" +msgstr[1] "%d è controllato passivamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:112 +#, php-format +msgid "List %u service comment" +msgid_plural "List %u service comments" +msgstr[0] "Mostra %u commento del servizio" +msgstr[1] "Mostra %u commenti del servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:97 +#, php-format +msgid "List %u service currently in downtime" +msgid_plural "List %u services currently in downtime" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:82 +#, fuzzy, php-format +msgid "List %u service for which flap detection has been disabled" +msgid_plural "List %u services for which flap detection has been disabled" +msgstr[0] "" +"Mostra tutti i servizi dove il controllo di instabilità è disbilitato" +msgstr[1] "" +"Mostra tutti i servizi dove il controllo di instabilità è disbilitato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:182 +#, fuzzy, php-format +msgid "List %u service for which notifications are suppressed" +msgid_plural "List %u services for which notifications are suppressed" +msgstr[0] "Mostra tutti i servizi dove le notifiche sono disabilitate" +msgstr[1] "Mostra tutti i servizi dove le notifiche sono disabilitate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:112 +#, fuzzy, php-format +msgid "List %u service that is currently flapping" +msgid_plural "List %u services which are currently flapping" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:57 +#, fuzzy, php-format +msgid "List %u service that is currently in state %s" +msgid_plural "List %u services which are currently in state %s" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:82 +#, fuzzy, php-format +msgid "List %u service that is currently in state %s (Acknowledged)" +msgid_plural "List %u services which are currently in state %s (Acknowledged)" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:108 +#, fuzzy, php-format +msgid "List %u service that is currently in state %s (Acknowledged) on host %s" +msgid_plural "" +"List %u services which are currently in state %s (Acknowledged) on host %s" +msgstr[0] "" +"Mostra %u servizio in stato critical non gestito nel gruppo di host %s" +msgstr[1] "" +"Mostra %u servizi in stato critical non gestiti nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:82 +#, fuzzy, php-format +msgid "List %u service that is currently in state %s on host %s" +msgid_plural "List %u services which are currently in state %s on host %s" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:20 +#, fuzzy, php-format +msgid "List %u service that is currently in state CRITICAL" +msgid_plural "List %u services which are currently in state CRITICAL" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:41 +#, fuzzy, php-format +msgid "List %u service that is currently in state CRITICAL (Acknowledged)" +msgid_plural "" +"List %u services which are currently in state CRITICAL (Acknowledged)" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:165 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state CRITICAL (Acknowledged) on hosts " +"in the host group \"%s\"" +msgid_plural "" +"List %u services which are currently in state CRITICAL (Acknowledged) on " +"hosts in the host group \"%s\"" +msgstr[0] "" +"Mostra %u servizio in stato critical non gestito nel gruppo di host %s" +msgstr[1] "" +"Mostra %u servizi in stato critical non gestiti nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:95 +#, php-format +msgid "" +"List %u service that is currently in state CRITICAL and not checked at all" +msgid_plural "" +"List %u services which are currently in state CRITICAL and not checked at all" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:68 +#, php-format +msgid "" +"List %u service that is currently in state CRITICAL and passively checked" +msgid_plural "" +"List %u services which are currently in state CRITICAL and passively checked" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:141 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state CRITICAL on hosts in the host " +"group \"%s\"" +msgid_plural "" +"List %u services which are currently in state CRITICAL on hosts in the host " +"group \"%s\"" +msgstr[0] "Mostra %u servizio in stato ok nel gruppo di host %s" +msgstr[1] "Mostra %u servizi in stato ok nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:19 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:314 +#, fuzzy, php-format +msgid "List %u service that is currently in state OK" +msgid_plural "List %u services which are currently in state OK" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:340 +#, fuzzy, php-format +msgid "List %u service that is currently in state OK and not checked at all" +msgid_plural "" +"List %u services which are currently in state OK and not checked at all" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:43 +#, fuzzy, php-format +msgid "List %u service that is currently in state OK on host %s" +msgid_plural "List %u services which are currently in state OK on host %s" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:114 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state OK on hosts in the host group \"%s" +"\"" +msgid_plural "" +"List %u services which are currently in state OK on hosts in the host group " +"\"%s\"" +msgstr[0] "Mostra %u servizio in stato ok nel gruppo di host %s" +msgstr[1] "Mostra %u servizi in stato ok nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:106 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:361 +#, fuzzy, php-format +msgid "List %u service that is currently in state PENDING" +msgid_plural "List %u services which are currently in state PENDING" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:387 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state PENDING and not checked at all" +msgid_plural "" +"List %u services which are currently in state PENDING and not checked at all" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:133 +#, fuzzy, php-format +msgid "List %u service that is currently in state PENDING on host %s" +msgid_plural "List %u services which are currently in state PENDING on host %s" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:300 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state PENDING on hosts in the host " +"group \"%s\"" +msgid_plural "" +"List %u services which are currently in state PENDING on hosts in the host " +"group \"%s\"" +msgstr[0] "Mostra %u servizio in stato ok nel gruppo di host %s" +msgstr[1] "Mostra %u servizi in stato ok nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:218 +#, fuzzy, php-format +msgid "List %u service that is currently in state UNKNOWN" +msgid_plural "List %u services which are currently in state UNKNOWN" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:239 +#, fuzzy, php-format +msgid "List %u service that is currently in state UNKNOWN (Acknowledged)" +msgid_plural "" +"List %u services which are currently in state UNKNOWN (Acknowledged)" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:219 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state UNKNOWN (Acknowledged) on hosts " +"in the host group \"%s\"" +msgid_plural "" +"List %u services which are currently in state UNKNOWN (Acknowledged) on " +"hosts in the host group \"%s\"" +msgstr[0] "" +"Mostra %u servizio in stato critical non gestito nel gruppo di host %s" +msgstr[1] "" +"Mostra %u servizi in stato critical non gestiti nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:293 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state UNKNOWN and not checked at all" +msgid_plural "" +"List %u services which are currently in state UNKNOWN and not checked at all" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:266 +#, php-format +msgid "" +"List %u service that is currently in state UNKNOWN and passively checked" +msgid_plural "" +"List %u services which are currently in state UNKNOWN and passively checked" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:195 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state UNKNOWN on hosts in the host " +"group \"%s\"" +msgid_plural "" +"List %u services which are currently in state UNKNOWN on hosts in the host " +"group \"%s\"" +msgstr[0] "Mostra %u servizio in stato ok nel gruppo di host %s" +msgstr[1] "Mostra %u servizi in stato ok nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:119 +#, fuzzy, php-format +msgid "List %u service that is currently in state WARNING" +msgid_plural "List %u services which are currently in state WARNING" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:140 +#, fuzzy, php-format +msgid "List %u service that is currently in state WARNING (Acknowledged)" +msgid_plural "" +"List %u services which are currently in state WARNING (Acknowledged)" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:273 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state WARNING (Acknowledged) on hosts " +"in the host group \"%s\"" +msgid_plural "" +"List %u services which are currently in state WARNING (Acknowledged) on " +"hosts in the host group \"%s\"" +msgstr[0] "" +"Mostra %u servizio in stato critical non gestito nel gruppo di host %s" +msgstr[1] "" +"Mostra %u servizi in stato critical non gestiti nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:194 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state WARNING and not checked at all" +msgid_plural "" +"List %u services which are currently in state WARNING and not checked at all" +msgstr[0] "Mostra %u servizio in manutenzione" +msgstr[1] "Mostra %u servizi in manutenzione" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:167 +#, php-format +msgid "" +"List %u service that is currently in state WARNING and passively checked" +msgid_plural "" +"List %u services which are currently in state WARNING and passively checked" +msgstr[0] "" +msgstr[1] "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:249 +#, fuzzy, php-format +msgid "" +"List %u service that is currently in state WARNING on hosts in the host " +"group \"%s\"" +msgid_plural "" +"List %u services which are currently in state WARNING on hosts in the host " +"group \"%s\"" +msgstr[0] "Mostra %u servizio in stato ok nel gruppo di host %s" +msgstr[1] "Mostra %u servizi in stato ok nel gruppo di host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:116 +#, fuzzy, php-format +msgid "List %u service that is not being checked at all" +msgid_plural "List %u services which are not being checked at all" +msgstr[0] "%d è stato controllato" +msgstr[1] "%d è stato controllato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:261 +#, fuzzy, php-format +msgid "List %u service that is not processing any event handlers" +msgid_plural "List %u services which are not processing any event handlers" +msgstr[0] "Mostra tutti i servizi dove il gestore eventi è abilitato" +msgstr[1] "Mostra tutti i servizi dove il gestore eventi è abilitato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:10 +#, php-format +msgid "List all %u hosts" +msgstr "Mostra tutti %u host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:23 +#, fuzzy, php-format +msgid "List all %u service on host %s" +msgid_plural "List all %u services on host %s" +msgstr[0] "Mostra tutti i servizi dell'host %s" +msgstr[1] "Mostra tutti i servizi dell'host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:10 +#, php-format +msgid "List all %u services" +msgstr "Mostra tutti %u servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:73 +#, fuzzy, php-format +msgctxt "timeline.link.title" +msgid "List all event records registered %s" +msgstr "Mostra tutti gli eventi dello storico %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:86 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml:11 +#, php-format +msgid "List all hosts in the group \"%s\"" +msgstr "Mostra tutti gli host del gruppo \"%s\"" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:41 +msgid "List all hosts, for which flap detection is enabled entirely" +msgstr "Mostra tutti gli host dove il controllo di instabilità è abilitato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:162 +msgid "List all hosts, for which notifications are enabled entirely" +msgstr "Mostra tutti gli host dove le notifiche sono abilitate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:241 +msgid "List all hosts, which are processing event handlers entirely" +msgstr "Mostra tutti gli host dove il gestore eventi è abilitato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:72 +#, php-format +msgid "List all reported services on host %s" +msgstr "Mostra tutti i servizi riportati nell'host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:86 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml:11 +#, php-format +msgid "List all services in the group \"%s\"" +msgstr "Mostra tutti i servizi del gruppo \"%s\"" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:95 +#, php-format +msgid "List all services of all hosts in host group \"%s\"" +msgstr "Mostra tutti i servizi di tutti gli host del gruppo \"%s\"" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:237 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:212 +#, php-format +msgid "List all services on host %s" +msgstr "Mostra tutti i servizi dell'host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:53 +#, php-format +msgid "List all services with the name \"%s\" on all reported hosts" +msgstr "Mostra tutti i servizi con il nome \"%s\" negli host riportati" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:96 +msgid "List all services, for which flap detection is enabled entirely" +msgstr "Mostra tutti i servizi dove il controllo di instabilità è disbilitato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:196 +msgid "List all services, for which notifications are enabled entirely" +msgstr "Mostra tutti i servizi dove le notifiche sono disabilitate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:275 +msgid "List all services, which are processing event handlers entirely" +msgstr "Mostra tutti i servizi dove il gestore eventi è abilitato" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:468 +msgid "List comments" +msgstr "Lista commenti" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:435 +msgid "List contact groups" +msgstr "Lista gruppi di contatti" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:351 +msgid "List contacts" +msgstr "Lista contatti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/contacts.phtml:31 +#, php-format +msgid "List contacts in contact-group \"%s\"" +msgstr "Mostra contatti nel gruppo \"%s\"" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:269 +msgid "List downtimes" +msgstr "Lista manutenzioni" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:614 +msgid "List event records" +msgstr "Lista eventi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:561 +msgid "List host groups" +msgstr "Lista gruppi di host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:97 +msgid "List hosts" +msgstr "LIsta host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:326 +msgid "List notifications" +msgstr "Lista notifiche" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:510 +msgid "List service groups" +msgstr "Lista gruppi di servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:171 +msgid "List services" +msgstr "Lista servizi" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:118 +msgid "Livestatus Resource" +msgstr "Risorsa Livestatus" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:76 +msgid "Local" +msgstr "Locale" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:202 +msgid "Local Command File" +msgstr "Command File locale" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Max" +msgstr "Max" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:449 +msgid "Max (min)" +msgstr "Massimo (min)" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Min" +msgstr "Min" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:147 +msgid "Minutes" +msgstr "Minuti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:14 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:72 +msgid "Monitoring Backend" +msgstr "Backend di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:14 +msgctxt "setup.page.title" +msgid "Monitoring Backend" +msgstr "Backend di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:3 +msgid "Monitoring Backends" +msgstr "Backend di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:2 +msgid "Monitoring Features" +msgstr "Funzionalità di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:209 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ProcessController.php:29 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ProcessController.php:40 +msgid "Monitoring Health" +msgstr "Impostazioni Globali" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/IdoResourcePage.php:14 +msgctxt "setup.page.title" +msgid "Monitoring IDO Resource" +msgstr "Risorsa IDO del Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:42 +msgid "Monitoring Instance" +msgstr "Istanza di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/InstancePage.php:14 +msgctxt "setup.page.title" +msgid "Monitoring Instance" +msgstr "Istanza di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:50 +msgid "Monitoring Instances" +msgstr "Istanze di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php:14 +msgctxt "setup.page.title" +msgid "Monitoring Livestatus Resource" +msgstr "Risorsa Livestatus di Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:41 +msgid "Monitoring Security" +msgstr "Sicurezza del Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/SecurityPage.php:14 +msgctxt "setup.page.title" +msgid "Monitoring Security" +msgstr "Sicurezza del Monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:146 +#, php-format +msgid "Monitoring backend \"%s\" has been successfully changed" +msgstr "Backend di monitoraggio \"%s\" modificato correttamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:143 +#, php-format +msgid "Monitoring backend \"%s\" has been successfully created" +msgstr "Backend di monitoraggio \"%s\" creato correttamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:79 +msgid "Monitoring backend already exists" +msgstr "Backend di monitoraggio già esistente" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:146 +#, php-format +msgid "" +"Monitoring backend configuration could not be written to: %s; An error " +"occured:" +msgstr "" +"Impossibile scrivere la configurazione del vba del monitoraggio in: %s; " +"Errore:" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:141 +#, php-format +msgid "Monitoring backend configuration has been successfully written to: %s" +msgstr "Configurazione del Backend di monitoraggio scritta correttmente in: %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:77 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:124 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:170 +msgid "Monitoring backend name missing" +msgstr "Nome del backend di monitoraggio mancante" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:93 +#, php-format +msgid "" +"Monitoring instance configuration could not be written to: %s; An error " +"occured:" +msgstr "" +"Impossibile scrivere la configurazione dell'istanza di monitoraggio in: %s; " +"Errore:" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:88 +#, php-format +msgid "Monitoring instance configuration has been successfully created: %s" +msgstr "Configuarzione dell'Istanza di monitoraggio creata correttamente: %s" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:71 +#, php-format +msgid "" +"Monitoring security configuration could not be written to: %s; An error " +"occured:" +msgstr "" +"Impossibile scrivere la configurazione di sicurezza del monitoraggio in: %s; " +"Errore:" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:66 +#, php-format +msgid "Monitoring security configuration has been successfully created: %s" +msgstr "Configurazione di sicurezza creata correttamente: %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:649 +msgid "Month" +msgstr "Mese" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:38 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:44 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:50 +msgid "N/A" +msgstr "N/A" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:376 +msgid "Name" +msgstr "Nome" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:97 +msgid "New instance name missing" +msgstr "Nome nuova istanza mancante" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:102 +msgid "New monitoring backend name missing" +msgstr "Nome del nuovo backend di monitoraggio mancante" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/SecurityConfigForm.php:31 +msgid "New security configuration has successfully been stored" +msgstr "" +"La nuova configurazione di sicurezza del monitoraggio è stata salvata " +"correttamente." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:27 +msgid "Next check" +msgstr "Prossimo Controllo" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checksource.phtml:17 +msgid "No" +msgstr "No" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:23 +msgid "No Services matching the filter" +msgstr "Nessun Servizio soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:25 +msgid "No active downtimes" +msgstr "Nessuna Manutenzione in corso" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:176 +msgid "No backend has been configured" +msgstr "Nessun Backend configurato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:14 +msgid "No comments matching the filter" +msgstr "Nessun commento soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:189 +#, php-format +msgid "No configuration for backend %s" +msgstr "Nessuna configurazione per il backend %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contactgroups.phtml:11 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:12 +msgid "No contacts matching the filter" +msgstr "Nessun contatto soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:24 +msgid "No history available for this object" +msgstr "Storico non disponibile per questo oggetto" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:26 +msgid "No history events matching the filter" +msgstr "Nessun evento dello storico soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:17 +msgid "No host groups matching the filter" +msgstr "Nessun gruppo di host soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:6 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:27 +msgid "No hosts matching the filter" +msgstr "Nessun host soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:53 +msgid "No notification has been sent for this issue" +msgstr "Nessuna notifica è stata inviata per questo problema" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:62 +msgid "No notifications have been sent for this contact" +msgstr "Nessuna notifica è stata inviata a questo contatto" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:21 +msgid "No notifications matching the filter" +msgstr "Nessuna notifica soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:17 +msgid "No service groups matching the filter" +msgstr "Nessun gruppo di servizi soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml:32 +msgid "No services configured on this host" +msgstr "Nessun servizio configurato per questo host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:6 +msgid "No services matching the filter" +msgstr "Nessun Servizio soddisfa i criteri di ricerca" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventgrid.phtml:69 +msgid "No state changes in the selected time period." +msgstr "Nessun cambio di stato nel intervallo di tempo selezionato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:10 +msgid "No such contact" +msgstr "Nessun contatto trovato" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:55 +msgid "None" +msgstr "Niente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:23 +msgid "Not acknowledged" +msgstr "Non Confermato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:41 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:52 +msgid "Notification" +msgstr "Notifica" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:342 +msgid "Notification Start" +msgstr "Partenza Notifica" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:171 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:331 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:429 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:472 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:479 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:325 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:44 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:76 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:66 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:2 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:127 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml:129 +msgid "Notifications" +msgstr "Notifiche" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:61 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:85 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:13 +msgid "Notifications Disabled" +msgstr "Notifiche Disabilitate" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:124 +msgid "Notifications Enabled" +msgstr "Notifiche Abilitate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:15 +msgid "Notifications and Problems" +msgstr "Notifiche e Problemi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:328 +#, fuzzy +msgid "Notifications and average reaction time per hour." +msgstr "Notifiche e problemi per ora" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:469 +msgid "Notifications and defects per hour" +msgstr "Notifiche e problemi per ora" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:56 +msgid "Notifications sent to this contact" +msgstr "Notifiche inviate a questo contatto" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:76 +#, php-format +msgid "Notifications will be re-enabled in %s" +msgstr "Le notifiche saranno riabilitate in %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/disable-notifications.phtml:11 +#, php-format +msgid "Notifications will be re-enabled in %s." +msgstr "Le notifiche saranno riabilitate in %s." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:70 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:70 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:178 +msgid "OK" +msgstr "OK" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:60 +msgctxt "icinga.state" +msgid "OK" +msgstr "OK" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:72 +msgid "Object summaries" +msgstr "Riepilogo Oggetti" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:97 +msgid "Object type" +msgstr "Tipo Oggetto" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:57 +msgid "Obsessing" +msgstr "Modalità Ossessiva" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:144 +msgid "Obsessing Over Hosts" +msgstr "Modalità Ossessiva sugli Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:153 +msgid "Obsessing Over Services" +msgstr "Modalità Ossessiva sui Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:636 +msgid "Occurence" +msgstr "Occorrenze" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:250 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:121 +msgid "Ok" +msgstr "Ok" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:95 +msgid "Old instance name missing" +msgstr "Nome vecchia istanza mancante" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:100 +msgid "Old monitoring backend name missing" +msgstr "Nome del vecchio backend di monitoraggio mancante" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:536 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:98 +msgid "One day" +msgstr "Un giorno" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:538 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:100 +msgid "One month" +msgstr "Un mese" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:537 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:99 +msgid "One week" +msgstr "Una settimana" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:539 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:101 +msgid "One year" +msgstr "Un anno" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:72 +msgid "Output" +msgstr "Output" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:131 +msgid "Overview" +msgstr "Panoramica" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:76 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:76 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Host.php:182 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:190 +msgid "PENDING" +msgstr "PENDING" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:32 +msgid "Pager" +msgstr "Recapito" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:379 +msgid "Pager Address / Number" +msgstr "Numero di telefono" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:48 +msgid "Passive Checks" +msgstr "Controlli Passivi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:162 +msgid "Passive Host Checks Being Accepted" +msgstr "Accetta Controlli Passivi sugli Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:171 +msgid "Passive Service Checks Being Accepted" +msgstr "Accetta Controlli Passivi sui Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:150 +msgid "Passive checks" +msgstr "Controlli passivi" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:112 +msgid "Password" +msgstr "Password" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:66 +msgid "Path to the Icinga command file on the remote Icinga instance" +msgstr "Percorso del command file di Icinga nell'istanza remota" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php:32 +msgid "Path to the local Icinga command file" +msgstr "Percorso del command file locale di Icinga" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:81 +msgid "Performance Data" +msgstr "Dati di Performance" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:180 +msgid "Performance Data Being Processed" +msgstr "Accetta i Dati di Performance" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:70 +msgid "Performance Info" +msgstr "Informazioni sulle Performance" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/perfdata.phtml:3 +msgid "Performance data" +msgstr "Dati di Performance" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:55 +msgid "Persistent" +msgstr "Persistente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:61 +msgid "Persistent Comment" +msgstr "Commento Persistente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:16 +msgid "" +"Please configure below how Icinga Web 2 should retrieve monitoring " +"information." +msgstr "" +"Definire di seguito come Icinga Web 2 recupererà le informazioni del " +"monitoraggio." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/InstancePage.php:16 +msgid "Please define the settings specific to your monitoring instance below." +msgstr "" +"Definire di seguito le impostazioni specifiche della vostra istanza di " +"monitoraggio." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/IdoResourcePage.php:16 +msgid "" +"Please fill out the connection details below to access the IDO database of " +"your monitoring environment." +msgstr "" +"Compilare di seguito i dettagli della connessione per accedere al database " +"IDO del vostro ambiente di monitoraggio." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php:16 +msgid "" +"Please fill out the connection details below to access the Livestatus socket " +"interface for your monitoring environment." +msgstr "" +"Compilare di seguito i dettagli della connessione per accedere al socket " +"Livestatus del vostro ambiente di monitoraggio." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/output.phtml:2 +msgid "Plugin Output" +msgstr "Output del Plugin" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:42 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:100 +msgid "Port" +msgstr "Porta" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml:2 +msgctxt "multiselection" +msgid "" +"Press and hold the Ctrl key while clicking on rows to select multiple rows " +"or press and hold the Shift key to select a range of rows." +msgstr "" +"Tenere premuto il tasto CTRL durante i Click per selezionare più righe o " +"tenere premuto SHIFT per selezionare righe contigue." + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:96 +msgid "Problems" +msgstr "Problemi " + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:19 +msgid "Process Info" +msgstr "Informazioni sul Processo" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/command.phtml:16 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/command.phtml:27 +msgid "Process check result" +msgstr "Output del risultato" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:111 +msgid "Processing check result.." +msgid_plural "Processing check results.." +msgstr[0] "Invio risultato.." +msgstr[1] "Invio risultati" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:23 +msgid "Program Start Time" +msgstr "Ora Avvio del Programma" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/SecurityConfigForm.php:56 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/SecurityStep.php:52 +msgid "Protected Custom Variables" +msgstr "Variabili Riservate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checksource.phtml:14 +msgid "Reachable" +msgstr "Raggiungibile" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:223 +msgid "Recently Recovered Services" +msgstr "Servizi Ripristinati Recentemente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:76 +msgid "Remote" +msgstr "Remoto" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:203 +msgid "Remote Command File" +msgstr "Command File Remoto" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:58 +msgid "Remote Host" +msgstr "Host Remoto" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:62 +msgid "Remote SSH Port" +msgstr "Porta SSH Remota" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/InstanceStep.php:66 +msgid "Remote SSH User" +msgstr "Utente SSH Remoto" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:15 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:59 +msgid "Remove" +msgstr "Rimuovi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:86 +msgid "Remove Existing Backend" +msgstr "Rimuovi Backend Esistente" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ConfigController.php:122 +msgid "Remove Existing Instance" +msgstr "Rimuovi Istanza Esistente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:42 +#, php-format +msgid "Remove monitoring backend %s" +msgstr "Rimuovi backend di monitoraggio %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:86 +#, php-format +msgid "Remove monitoring instance %s" +msgstr "Rimuovi istanza di monitoraggio %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php:30 +msgid "Remove problem acknowledgement" +msgid_plural "Remove problem acknowledgements" +msgstr[0] "Rimuovi la Conferma del Problema" +msgstr[1] "Rimuovi le Conferme del Problema" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php:48 +msgid "Removing problem acknowledgement.." +msgid_plural "Removing problem acknowledgements.." +msgstr[0] "Rimozione Conferma in corso.." +msgstr[1] "Rimozione Conferme in corso.." + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:541 +msgid "Report interval" +msgstr "Intervallo del Report" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:196 +msgid "Reporting" +msgstr "Reportistica" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:32 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:45 +msgid "Reschedule" +msgstr "Rischedula" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:95 +msgid "Reschedule Host Check" +msgstr "Rischedula il Controllo dell'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:196 +msgid "Reschedule Host Checks" +msgstr "Rischedula i Controlli dell'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:72 +msgid "Reschedule Service Check" +msgstr "Rischedula il Controllo del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:245 +msgid "Reschedule Service Checks" +msgstr "Rischedula i Controlli del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:17 +#, php-format +msgid "Reschedule the next check for all %u hosts" +msgstr "Rischedula il prossimo controllo per tutti i %u host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:17 +#, php-format +msgid "Reschedule the next check for all %u services" +msgstr "Rischedula il prossimo controllo per tutti i %u servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:240 +msgid "Resource" +msgstr "Risorsa" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:88 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:123 +msgid "Resource Name" +msgstr "Nome Risorsa" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:158 +#, php-format +msgid "Resource configuration could not be udpated: %s; An error occured:" +msgstr "La configurazione della risorsa non può essere aggiornata: %s; Errore:" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:155 +#, php-format +msgid "Resource configuration has been successfully updated: %s" +msgstr "Configurazione Risorsa aggiornata correttamente: %s" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:65 +msgid "Restrict hosts view to the hosts that match the filter" +msgstr "Restringi la vista degli host a quelli che soddisfano il filtro" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:70 +msgid "Restrict services view to the services that match the filter" +msgstr "Restringi la vista dei servizi a quelli che soddisfano il filtro" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:43 +msgid "SSH port to connect to on the remote Icinga instance" +msgstr "Porta SSH da usare per la connessione all'istanza Icinga remota" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:30 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:27 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/SecurityConfigForm.php:20 +msgid "Save Changes" +msgstr "Salva Modifiche" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:107 +msgid "Schedule Host Downtime" +msgstr "Pianifica Manutenzione all'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:208 +msgid "Schedule Host Downtimes" +msgstr "Pianifica Manutenzioni dell'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:84 +msgid "Schedule Service Downtime" +msgstr "Pianifica Manutenzione al Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:257 +msgid "Schedule Service Downtimes" +msgstr "Pianifica Manutenzioni del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:55 +#, fuzzy, php-format +msgid "Schedule a downtime for %u unhandled host problem" +msgid_plural "Schedule a downtime for %u unhandled host problems" +msgstr[0] "Pianifica Manutenzione per %u Problema dell'Host Non Gestito" +msgstr[1] "Pianifica Manutenzione per %u Problemi degli'Host Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:55 +#, fuzzy, php-format +msgid "Schedule a downtime for %u unhandled service problem" +msgid_plural "Schedule a downtime for %u unhandled service problems" +msgstr[0] "Pianifica Manutenzione per %u Problema del Servizio Non Gestito" +msgstr[1] "Pianifica Manutenzione per %u Problemi del Servizio Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:23 +#, php-format +msgid "Schedule a downtime for all %u hosts" +msgstr "Pianifica manutenzione per tutti i %u host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:23 +#, php-format +msgid "Schedule a downtime for all %u services" +msgstr "Pianifica manutenzione per tutti i %u servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:15 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:28 +msgid "" +"Schedule a downtime to suppress all problem notifications within a specific " +"period of time" +msgstr "" +"Pianifica una manutenzione per disabilitare le notifiche per un periodo di " +"tempo specifico" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:34 +msgid "Schedule check" +msgid_plural "Schedule checks" +msgstr[0] "Rischedula Controllo" +msgstr[1] "Rischedula Controlli" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php:28 +msgid "Schedule check for all services on the hosts and the hosts themselves." +msgstr "Schedula Controllo per l'host e tutti i suoi servizi." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:47 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:8 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/downtime.phtml:21 +msgid "Schedule downtime" +msgid_plural "Schedule downtimes" +msgstr[0] "Pianifica Manutenzione" +msgstr[1] "Pianifica Manutenzioni" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:30 +msgid "" +"Schedule downtime for all services on the hosts and the hosts themselves." +msgstr "PIanifica Manutenzione per l'host e tutti i suoi servizi." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:43 +msgid "Schedule non-triggered downtime for all child hosts" +msgstr "Pianifica Manutenzione non dipendente per tutti gli Host figli" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:39 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:52 +msgid "Schedule the next active check at a different time than the current one" +msgstr "Schedula il prossimo controllo ad un orario diverso da quello corrente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:43 +msgid "Schedule the next active check to run immediately" +msgstr "Schedula il prossimo controllo per essere eseguito immediatamente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:42 +msgid "Schedule triggered downtime for all child hosts" +msgstr "Pianifica Manutenzione dipendente per tutti gli Host figli" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:304 +msgid "Scheduled End" +msgstr "Fine" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:303 +msgid "Scheduled Start" +msgstr "Inizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:72 +msgid "Scheduling check.." +msgid_plural "Scheduling checks.." +msgstr[0] "Schedulando Controllo.." +msgstr[1] "Schedulando Controlli.." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php:51 +msgid "Scheduling host check.." +msgid_plural "Scheduling host checks.." +msgstr[0] "Schedulando Controllo dell'Host.." +msgstr[1] "Schedulando Controlli dell'Host.." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php:86 +msgid "Scheduling host downtime.." +msgid_plural "Scheduling host downtimes.." +msgstr[0] "Pianificando Manutenzione Host.." +msgstr[1] "Pianificando Manutenzioni Host.." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:100 +msgid "Scheduling service check.." +msgid_plural "Scheduling service checks.." +msgstr[0] "Schedulando Controllo del Servizio.." +msgstr[1] "Schedulando Controlli del Servizio.." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:207 +msgid "Scheduling service downtime.." +msgid_plural "Scheduling service downtimes.." +msgstr[0] "Pianifica Manutenzione Sevizio.." +msgstr[1] "Pianifica Manutenzione Sevizi.." + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:80 +msgid "Security" +msgstr "Sicurezza" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:131 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:232 +msgid "Send Custom Host Notification" +msgstr "Invia Notifica Personalizzata per l'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:108 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:281 +msgid "Send Custom Service Notification" +msgstr "Invia Notifica Personalizzata per il Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:123 +msgid "Send Notification" +msgstr "Invia Notifica" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:36 +#, php-format +msgid "Send a custom notification for all %u hosts" +msgstr "Invia notifica personalizzata per tutti i %u host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:36 +#, php-format +msgid "Send a custom notification for all %u services" +msgstr "Invia notifica personalizzata per tutti i %u servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:25 +msgid "" +"Send a custom notification, share information about the object to contacts." +msgstr "" +"Invia una notifica personalizzata, condividi le informazioni dell'oggetto " +"con i contatti." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:28 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:24 +#, php-format +msgid "Send a mail to %s" +msgstr "Invia e-mail a %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:34 +msgid "Send custom notification" +msgid_plural "Send custom notifications" +msgstr[0] "Invia Notifica Personalizzata" +msgstr[1] "Invia Notifica Personalizzata" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:106 +msgid "Send custom notification.." +msgid_plural "Send custom notifications.." +msgstr[0] "Invia Notifica Personalizzata.." +msgstr[1] "Invia Notifica Personalizzata.." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:18 +msgid "Send notification" +msgstr "Invia Notifica" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:62 +#, php-format +msgid "Sent to %s" +msgstr "Inviata a %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:298 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:491 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:226 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:53 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:45 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/object-header.phtml:34 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:201 +msgid "Service" +msgstr "Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml:7 +#, php-format +msgid "Service (%u)" +msgid_plural "Services (%u)" +msgstr[0] "Servizio (%u)" +msgstr[1] "Servizi (%u)" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:141 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:161 +msgid "Service Checks" +msgstr "Controlli Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:119 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:646 +msgid "Service Grid" +msgstr "Griglia Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:25 +msgid "Service Group" +msgstr "Gruppo di Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:239 +msgid "Service Group Chart" +msgstr "Grafico dei Gruppi di Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:546 +msgid "Service Group Name" +msgstr "Nome del Gruppo di Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:509 +msgid "Service Groups" +msgstr "Gruppi di Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:224 +msgid "Service Name" +msgstr "Nome Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:380 +msgid "Service Notification Timeperiod" +msgstr "Periodo per le Notifiche del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:115 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:219 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:77 +msgid "Service Problems" +msgstr "Servizi con Problemi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:222 +msgid "Service Severity" +msgstr "Impatto" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:80 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:202 +msgid "Service State" +msgstr "Stato Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:27 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:27 +msgid "Service States" +msgstr "Stati Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:658 +msgid "Service description" +msgstr "Descrizione Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/service/show.phtml:3 +msgid "Service detail information" +msgstr "Dettagli Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:33 +msgid "Service not found" +msgstr "Servizio non trovato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:42 +msgid "Service notification period" +msgstr "Periodo per le Notifiche del Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:91 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:147 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml:17 +msgid "Servicegroups" +msgstr "Gruppi di Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:89 +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:143 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:243 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:171 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:101 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:240 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:100 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:96 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/contact.phtml:42 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:8 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:50 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml:48 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:215 +msgid "Services" +msgstr "Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:550 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:600 +msgid "Services CRITICAL" +msgstr "Servizi CRITICAL" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:548 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:598 +msgid "Services OK" +msgstr "Servizi OK" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:552 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:602 +msgid "Services PENDING" +msgstr "Servizi in ATTESA" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:549 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:599 +msgid "Services UNKNOWN" +msgstr "Servizi UNKNOWN" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:551 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:601 +msgid "Services WARNING" +msgstr "Servizi WARNING" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:53 +msgid "Set the date and time when the check should be scheduled." +msgstr "Impostare la data e l'ora dell'esecuzione del controllo." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:89 +msgid "Set the end date and time for the downtime." +msgstr "Impostare la data e l'ora della fine della manutenzione." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:44 +msgid "Set the expire time." +msgstr "Impostare l'ora di scadenza." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:79 +msgid "Set the start date and time for the downtime." +msgstr "Impostare la date e l'ora d'inizio della manutenzione." + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:97 +msgid "Setup the monitoring module for Icinga Web 2" +msgstr "Configura il modulo di monitoraggio per Icinga Web 2!" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:132 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:545 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:595 +msgid "Severity" +msgstr "Impatto" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:256 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:231 +#, php-format +msgid "Show all event records of host %s" +msgstr "Mostra tutti gli eventi dell'host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:252 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:227 +#, php-format +msgid "Show all event records of service %s on host %s" +msgstr "Mostra tutti gli eventi del servizio %s dell'host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TacticalController.php:15 +msgid "" +"Show an overview of all hosts and services, their current states and " +"monitoring feature utilisation" +msgstr "" +"Mostra una panoramica di tutti gli host e servizi, il loro stato corrente e " +"le funzionalità utilizzate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contactgroups.phtml:29 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/contacts.phtml:23 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:36 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/contacts.phtml:11 +#, php-format +msgid "Show detailed information about %s" +msgstr "Mostra informazioni dettagliate per %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:208 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Link.php:37 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:104 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:120 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:183 +#, php-format +msgid "Show detailed information for host %s" +msgstr "Mostra informazioni dettagliate per l'host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:222 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Link.php:60 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:93 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:112 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:151 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:197 +#, php-format +msgid "Show detailed information for service %s on host %s" +msgstr "Mostra informazioni dettagliate per il servizio %s dell'host %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ProcessController.php:26 +msgid "" +"Show information about the current monitoring instance's process and it's " +"performance as well as available features" +msgstr "" +"Mostra le informazioni riguardo lo stato del processo dell'istanza attiva, " +"le sue performance e le funzionalità disponibili" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:41 +msgid "" +"Show recent alerts and visualize notifications and problems based on their " +"amount and chronological distribution" +msgstr "" +"Mostra allarmi recenti e visualizza le notifiche e i problemi rispetto alle " +"occorrenze e alla distribuzione cronologica" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:259 +msgid "Show resource configuration" +msgstr "Mostra la configurazione della risorsa" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:77 +#, php-format +msgid "Show summarized information for %u hosts" +msgstr "Mostra lista informazioni per %u host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:98 +#, php-format +msgid "Show summarized information for %u services" +msgstr "Mostra lista informazioni per %u servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:390 +msgid "Show the Event Grid" +msgstr "Mostra Griglia Eventi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:646 +msgid "Show the Service Grid" +msgstr "Mostra la Griglia Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:258 +#, php-format +msgid "Show the configuration of the %s resource" +msgstr "Mostra configurazione per la risorsa %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:21 +msgid "Show the number of historical event records grouped by time and type" +msgstr "Mostra il numero di eventi dello storico raggruppati per ora e tipo" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/IdoResourcePage.php:75 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/LivestatusResourcePage.php:76 +msgid "Skip Validation" +msgstr "Salta la Verifica" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:127 +msgid "Socket" +msgstr "Socket" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:5 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/eventhistory.phtml:13 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:7 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:12 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegrid.phtml:10 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:7 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:14 +msgid "Sort by" +msgstr "Ordina per" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:94 +msgctxt "setup.welcome.btn.next" +msgid "Start" +msgstr "Inizia" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:301 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:78 +msgid "Start Time" +msgstr "Ora Inizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:64 +msgid "Started downtimes" +msgstr "Manutenzione iniziate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:43 +msgid "Starts" +msgstr "Inizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:115 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:135 +msgid "State" +msgstr "Stato" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/EventOverviewForm.php:46 +msgid "State Changes" +msgstr "Cambi di Stato" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:53 +msgid "Status" +msgstr "Stato" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:111 +msgid "Sticky Acknowledgement" +msgstr "Commento Permanente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:31 +msgid "Submit Passive Check Result" +msgid_plural "Submit Passive Check Results" +msgstr[0] "Invia Risultato Passivo all'Host" +msgstr[1] "Invia Risultati Passivo all'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostController.php:119 +msgid "Submit Passive Host Check Result" +msgstr "Invia Risultato Passivo all'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/HostsController.php:220 +msgid "Submit Passive Host Check Results" +msgstr "Invia Risultato Passivo all'Host" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServiceController.php:96 +msgid "Submit Passive Service Check Result" +msgstr "Invia Risultato Passivo al Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ServicesController.php:269 +msgid "Submit Passive Service Check Results" +msgstr "Invia Risultato Passivo al Servizio" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/command.phtml:13 +#, php-format +msgid "Submit a one time or so called passive result for the %s check" +msgstr "Invia risultato passivo per %s controlli" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/hosts/show.phtml:29 +#, php-format +msgid "Submit a passive check result for all %u hosts" +msgstr "Invia risultato passivo per tutti i %u host" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/services/show.phtml:29 +#, php-format +msgid "Submit a passive check result for all %u services" +msgstr "Invia risultato passivo per tutti i %u servizi" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:208 +msgid "System" +msgstr "Sistema" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:135 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TacticalController.php:18 +msgid "Tactical Overview" +msgstr "Visuale Tattica" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:169 +msgid "" +"The Zend database adapter for MySQL is required to access a MySQL database." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:189 +msgid "" +"The Zend database adapter for PostgreSQL is required to access a PostgreSQL " +"database." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:227 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:46 +msgid "The data source used for retrieving monitoring information" +msgstr "" +"La sorgente dei dati usati per recuperare le informazioni di monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/DataView/DataView.php:338 +#, php-format +msgid "The filter column \"%s\" is not allowed here." +msgstr "Il filtro \"%s\" non è ammesso." + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:60 +msgid "The given resource name is already in use." +msgstr "Il nome risorsa fornito è già in uso." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:217 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/BackendPage.php:29 +msgid "The identifier of this backend" +msgstr "L'identificatore per questo backend" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/notifications.phtml:46 +#, php-format +msgid "The last one occured %s ago" +msgstr "L'ultimo si è verificato %s fa" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ShowController.php:122 +msgid "The parameter `contact' is required" +msgstr "Il parametro `contatto' è richiesto" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:83 +msgid "" +"The performance data of this check result. Leave empty if this check result " +"has no performance data" +msgstr "" +"Dati di performance per questo controllo. Lasciare vuoto se il controllo non " +"ha dati performance." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:73 +msgid "The plugin output of this check result" +msgstr "Output del risultato del controllo" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:241 +msgid "The resource to use" +msgstr "La risorsa da usare" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/DataView/DataView.php:247 +#, php-format +msgid "The sort column \"%s\" is not allowed in \"%s\"." +msgstr "Non è possibile oridinare per colonna \"%s\" in \"%s\"." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:54 +msgid "The state this check result should report" +msgstr "Stato da riportare nel risultato del controllo" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php:91 +#, php-format +msgid "There is no \"%s\" monitoring backend" +msgstr "Backend di Monitoraggio \"%s\" non trovato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/history.phtml:17 +msgid "This Object's Event History" +msgstr "Storico degli Eventi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:22 +msgid "" +"This command is used to acknowledge host or service problems. When a problem " +"is acknowledged, future notifications about problems are temporarily " +"disabled until the host or service recovers." +msgstr "" +"Questo comando serve per confermare i problemi di host o servizi. Quando un " +"problema è Confermato le notifiche saranno temporaneamente disabilitate fino " +"al ripristino dell'host o servizio." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:19 +msgid "This command is used to add host or service comments." +msgstr "Questo comando serve ad aggiungere commenti agli host o ai servizi." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:26 +msgid "" +"This command is used to disable host and service notifications for a " +"specific time." +msgstr "" +"Questo comando serve a disabilitare le notifiche di host e servizi per un " +"periodo di tempo specifico." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:33 +msgid "" +"This command is used to schedule host and service downtimes. During the " +"specified downtime, Icinga will not send notifications out about the hosts " +"and services. When the scheduled downtime expires, Icinga will send out " +"notifications for the hosts and services as it normally would. Scheduled " +"downtimes are preserved across program shutdowns and restarts." +msgstr "" +"Questo comando serve a pianificare la manutenzione di un host o servizio. " +"Durante la Manutenzione, Icinga sopprimerà eventuali notifiche. Allo scadere " +"della Manutenzione Icinga invierà le notifche per host e servizi come " +"farebbe normalmente. Le Manutenzioni schedulate saranno preservate da " +"eventuali arresti o riavvii dell'ambiente." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php:23 +msgid "" +"This command is used to schedule the next check of hosts or services. Icinga " +"will re-queue the hosts or services to be checked at the time you specify." +msgstr "" +"Questo comando serve a schedulare l'esecuzione del prossimo controllo " +"dell'host o servizio. Icinga eseguirà il controllo per l'host o servizio " +"all'orario specificato." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php:21 +msgid "" +"This command is used to send custom notifications for hosts or services." +msgstr "" +"Questo comando serve ad inviare notifiche personalizzate per host o servizi." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:20 +msgid "This command is used to submit passive host or service check results." +msgstr "" +"Questo comando serve ad inviare risultati passivi all'host o al servizio." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:76 +msgid "This comment does not expire." +msgstr "Questo commento ha scadenza." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:73 +#, php-format +msgid "This comment expires on %s at %s." +msgstr "Questo commento scade il %s alle %s." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:69 +msgid "This comment is not persistent." +msgstr "Questo Commento non è permanente." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:68 +msgid "This comment is persistent." +msgstr "Questo Commento è permanente." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:107 +#, php-format +msgid "" +"This fixed host downtime has been scheduled to start on %s at %s and to end " +"on %s at %s." +msgstr "" +"Questa Manutenzione Rigida dell'Host è stata pianificata per iniziare il %s " +"alle %s e finire il %s alle %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:97 +#, php-format +msgid "" +"This fixed host downtime was started on %s at %s and expires on %s at %s." +msgstr "" +"Questa Manutenzione Rigida dell'Host è iniziata il %s alle %s e finirà il %s " +"alle %s." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:106 +#, php-format +msgid "" +"This fixed service downtime has been scheduled to start on %s at %s and to " +"end on %s at %s." +msgstr "" +"Questa Manutenzione Rigida del Servizio è stata pianificata per iniziare il " +"%s alle %s e finire il %s alle %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:96 +#, php-format +msgid "" +"This fixed service downtime was started on %s at %s and expires on %s at %s." +msgstr "" +"Questa Manutenzione Rigida del Servizio è iniziata il %s alle %s e finirà il " +"%s alle %s." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:86 +#, php-format +msgid "" +"This flexible host downtime has been scheduled to start between %s - %s and " +"to last for %s." +msgstr "" +"Questa Manutenzione Flessibile dell'Host è stata pianificata per iniziare " +"tra %s -%s e durare per %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:75 +#, php-format +msgid "" +"This flexible host downtime was started on %s at %s and lasts for %s until " +"%s at %s." +msgstr "" +"Questa Manutenzione Flessibile dell'Host è iniziata il %s alle %s e durerà " +"per %s fino al %s alle %s." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:85 +#, php-format +msgid "" +"This flexible service downtime has been scheduled to start between %s - %s " +"and to last for %s." +msgstr "" +"Questa Manutenzione Flessibile del Servizio è stata pianificata per iniziare " +"tra %s -%s e durare per %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:74 +#, php-format +msgid "" +"This flexible service downtime was started on %s at %s and lasts for %s " +"until %s at %s." +msgstr "" +"Questa Manutenzione Flessibile del Servizio è iniziata il %s alle %s e " +"durerà per %s fino al %s alle %s." + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/WelcomePage.php:33 +msgid "This is the core module for Icinga Web 2." +msgstr "Questo è il modulo principale per Icinga Web 2." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:22 +msgid "Time to Reaction (Ack, Recover)" +msgstr "Tempo di Reazione (Conferma, Ripristino)" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:103 +msgid "TimeLine interval" +msgstr "Intervallo" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:191 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:22 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/TimelineController.php:26 +msgid "Timeline" +msgstr "Cronologia" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:83 +msgid "To" +msgstr "A" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:160 +msgid "" +"To access the IDO stored in a MySQL database the PDO-MySQL module for PHP is " +"required." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:180 +msgid "" +"To access the IDO stored in a PostgreSQL database the PDO-PostgreSQL module " +"for PHP is required." +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/SecurityPage.php:16 +msgid "" +"To protect your monitoring environment against prying eyes please fill out " +"the settings below." +msgstr "" +"Per proteggere il tuo ambiente di monitoraggio da occhi indiscreti si prega " +"di compilare i dettagli seguenti:" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:86 +msgid "Today" +msgstr "Oggi" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php:219 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:133 +msgid "Toggling feature.." +msgstr "Comando inviato..." + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:56 +msgid "Top 5 Recent Alerts" +msgstr "Ultimi 5 Allarmi" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:547 +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ListController.php:597 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:26 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:26 +msgid "Total Services" +msgstr "Totale dei Servizi" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:29 +msgid "Trend" +msgstr "Trend" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:39 +msgid "Trend for the last 24h" +msgstr "Trend nelle ultime 24 ore" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:99 +msgid "Type" +msgstr "Tipo" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:31 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/config/index.phtml:75 +#, php-format +msgid "Type: %s" +msgstr "Tipo: %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:40 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:40 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:58 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:187 +msgid "UNKNOWN" +msgstr "UNKNOWN" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:63 +msgctxt "icinga.state" +msgid "UNKNOWN" +msgstr "UNKNOWN" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Host.php:179 +msgid "UNREACHABLE" +msgstr "UNREACHABLE" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:58 +msgctxt "icinga.state" +msgid "UNREACHABLE" +msgstr "UNREACHABLE" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Host.php:173 +msgid "UP" +msgstr "UP" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:56 +msgctxt "icinga.state" +msgid "UP" +msgstr "UP" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hosts.phtml:49 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/services.phtml:71 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml:5 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml:5 +msgid "Unhandled" +msgstr "Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:101 +msgid "Unhandled Hosts" +msgstr "Host Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/configuration.php:106 +msgid "Unhandled Services" +msgstr "Servizi Non Gestiti" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:271 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:120 +msgid "Unknown" +msgstr "Unknown" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:141 +msgid "Unknown instance name given" +msgstr "Nome Istanza sconosciuto" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:99 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/InstanceConfigForm.php:121 +msgid "Unknown instance name provided" +msgstr "Nome istanza sconosciuto" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:104 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:126 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/BackendConfigForm.php:172 +msgid "Unknown monitoring backend provided" +msgstr "Backend di monitoraggio sconosciuto" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:321 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:140 +msgid "Unreachable" +msgstr "Unreachable" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:307 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:138 +msgid "Up" +msgstr "Up" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:72 +msgid "Use Expire Time" +msgstr "Scadenza" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:52 +msgid "User" +msgstr "Utente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/comments.phtml:30 +msgid "User Comment" +msgstr "Commento dell'Utente" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:54 +msgid "" +"User to log in as on the remote Icinga instance. Please note that key-based " +"SSH login must be possible for this user" +msgstr "" +"Utente per la connessione all'istanza remota di Icinga. Si fa notare che è " +"l'utente può effettuare una autenticazione con chiave SSH." + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/BackendStep.php:108 +msgid "Username" +msgstr "Utente" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Value" +msgstr "Valore" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:624 +msgid "Value for interval not valid" +msgstr "Intervallo non valido" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:46 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/hostgroups.phtml:64 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:46 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/servicegroups.phtml:64 +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/Object/Service.php:181 +msgid "WARNING" +msgstr "WARNING" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php:61 +msgctxt "icinga.state" +msgid "WARNING" +msgstr "WARNING" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/ChartController.php:257 +#: /usr/local/icingaweb2/modules/monitoring/application/forms/StatehistoryForm.php:119 +#: /usr/local/icingaweb2/modules/monitoring/application/views/helpers/Perfdata.php:29 +msgid "Warning" +msgstr "Warning" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Setup/WelcomePage.php:21 +msgid "Welcome to the configuration of the monitoring module for Icinga Web 2!" +msgstr "" +"Benvenuto nella configurazione del modulo di monitoraggio per Icinga Web 2!" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checksource.phtml:17 +msgid "Yes" +msgstr "Si" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:166 +msgid "Zend database adapter for MySQL" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:186 +msgid "Zend database adapter for PostgreSQL" +msgstr "" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:51 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:39 +#, php-format +msgctxt "time" +msgid "at %s" +msgstr "alle %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:59 +#, php-format +msgctxt "timeline.link.title.datetime.twice" +msgid "between %s and %s" +msgstr "tra %s e %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php:107 +msgid "changed" +msgstr "modificato" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:182 +msgid "down" +msgstr "down" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:65 +msgid "hard state" +msgstr "hard" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:47 +#, fuzzy, php-format +msgctxt "timeline.link.title.month.and.year" +msgid "in %s" +msgstr "il %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:53 +#, fuzzy, php-format +msgctxt "timeline.link.title.year" +msgid "in %s" +msgstr "il %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:52 +#, php-format +msgctxt "timespan" +msgid "in %s" +msgstr "in %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:37 +msgid "in the last hour" +msgstr "nell'ultima ora" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:40 +#, fuzzy, php-format +msgctxt "timeline.link.title.week.and.year" +msgid "in week %s of %s" +msgstr "nella settimana %s di %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/alertsummary/index.phtml:35 +msgid "notifications per hour" +msgstr "notifiche per ora" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/downtimes.phtml:50 +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/notifications.phtml:38 +#, php-format +msgctxt "datetime" +msgid "on %s" +msgstr "il %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/timeline/index.phtml:34 +#, fuzzy, php-format +msgctxt "timeline.link.title.time" +msgid "on %s" +msgstr "di %s" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:77 +msgid "overall" +msgstr "complessivo" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml:5 +msgctxt "multiselection" +msgid "row(s) selected" +msgstr "righe selezionate" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/process/info.phtml:78 +msgid "scheduled" +msgstr "schedulato" + +#: /usr/local/icingaweb2/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml:63 +msgid "soft state" +msgstr "soft" + +#: /usr/local/icingaweb2/modules/monitoring/library/Monitoring/MonitoringWizard.php:50 +msgid "the monitoring module" +msgstr "il modulo di monitoraggio" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:180 +msgid "unchanged" +msgstr "invariato" + +#: /usr/local/icingaweb2/modules/monitoring/application/controllers/AlertsummaryController.php:184 +msgid "up" +msgstr "up" + +#~ msgid "Send Custom Notification" +#~ msgstr "Invia Notifica Personalizzata" + +#~ msgid "Monitoring IDO Resource" +#~ msgstr "Risorsa IDO" + +#~ msgid "Monitoring Livestatus Resource" +#~ msgstr "Risorsa Livestatus" + +#~ msgid "%s on %s" +#~ msgstr "%s di %s" + +#~ msgid "" +#~ "Press and hold the Ctrl key while clicking on rows to select multiple " +#~ "rows or press and hold the Shift key to select a range of rows." +#~ msgstr "" +#~ "Tenere premuto il tasto CTRL durante i Click per selezionare più righe o " +#~ "tenere premuto SHIFT per selezionare righe contigue." + +#~ msgid "row(s) selected" +#~ msgstr "righe selezionate" + +#~ msgid "at %s" +#~ msgstr "alle %s" + +#~ msgid "Timeline Navigation" +#~ msgstr "Navigazione della Cronologia" + +#~ msgctxt "icinga.state" +#~ msgid "%d CRITICAL" +#~ msgid_plural "%d CRITICAL" +#~ msgstr[0] "%d CRITICAL" +#~ msgstr[1] "%d CRITICAL" + +#~ msgctxt "icinga.state" +#~ msgid "%d PENDING" +#~ msgid_plural "%d PENDING" +#~ msgstr[0] "%d Host PENDING" +#~ msgstr[1] "%d Host PENDING" + +#~ msgctxt "icinga.state" +#~ msgid "%d UNKNOWN" +#~ msgid_plural "%d UNKNOWN" +#~ msgstr[0] "%d UNKNOWN" +#~ msgstr[1] "%d UNKNOWN" + +#~ msgid "%d are not checked at all" +#~ msgstr "%d non sono stati controllati" + +#~ msgid "%d are passively checked" +#~ msgstr "%d sono controllati passivamente" + +#~ msgid "%d critical on %s" +#~ msgstr "%d critical il %s" + +#~ msgid "%d down on %s" +#~ msgstr "%d down di %s" + +#~ msgid "%d ok on %s" +#~ msgstr "%d ok di %s" + +#~ msgid "%d unknown on %s" +#~ msgstr "%d unknown di %s" + +#~ msgid "%d unreachable on %s" +#~ msgstr "%d unreachable di %s" + +#~ msgid "%d warning on %s" +#~ msgstr "%d warning di %s" + +#~ msgid "%s notifications have been sent for this issue" +#~ msgstr "%s notifiche inviate per questo problema" + +#~ msgid "A notification has been sent for this issue %s ago" +#~ msgstr "Una notifica è stata inviata per questo problema %s fa" + +#~ msgid "Acknowledge unhandled host problem" +#~ msgid_plural "Acknowledge unhandled host problems" +#~ msgstr[0] "Conferma il Problema dell'Host Non Gestito" +#~ msgstr[1] "Conferma i Problemi dell'Host Non Gestiti" + +#~ msgid "Acknowledge unhandled service problem" +#~ msgid_plural "Acknowledge unhandled service problems" +#~ msgstr[0] "Conferma il Problema del Servizio Non Gestito" +#~ msgstr[1] "Conferma i Problemi del Servizio Non Gestiti" + +#~ msgid "Delete comment" +#~ msgstr "Rimuovi Commento" + +#~ msgid "Delete downtime" +#~ msgstr "Cancella Manutenzione" + +#~ msgid "Handled hosts with state DOWN" +#~ msgstr "Host Gestiti in stato DOWN" + +#~ msgid "Handled hosts with state UNREACHABLE" +#~ msgstr "Host Gestiti con stato UNREACHABLE" + +#~ msgid "Handled services with state %s" +#~ msgstr "Servizi Gestiti con stato %s" + +#~ msgid "Hosts with state PENDING" +#~ msgstr "Host in stato PENDING" + +#~ msgid "Hosts with state UP" +#~ msgstr "Host in stato UP" + +#~ msgid "List %s service with status critical handled in service group %s" +#~ msgid_plural "" +#~ "List %s services with status critical handled in service group %s" +#~ msgstr[0] "" +#~ "Mostra %s servizio in stato critical gestito nel gruppo di servizi %s" +#~ msgstr[1] "" +#~ "Mostra %s servizi in stato critical gestiti nel gruppo di servizi %s" + +#~ msgid "List %s service with status critical unhandled in service group %s" +#~ msgid_plural "" +#~ "List %s Services with status critical unhandled in service group %s" +#~ msgstr[0] "" +#~ "Mostra %s servizio in stato critical non gestito nel gruppo di servizi %s" +#~ msgstr[1] "" +#~ "Mostra %s servizi in stato critical non gestiti nel gruppo di servizi %s" + +#~ msgid "List %s service with status ok in service group %s" +#~ msgid_plural "List %s Services with status ok in service group %s" +#~ msgstr[0] "Mostra %s servizio in stato ok nel gruppo di servizi %s" +#~ msgstr[1] "Mostra %s servizi in stato ok nel gruppo di servizi %s" + +#~ msgid "List %s service with status unknown handled in service group %s" +#~ msgid_plural "" +#~ "List %s services with status unknown handled in service group %s" +#~ msgstr[0] "" +#~ "Mostra %s servizio in stato unknown gestito nel gruppo di servizi %s" +#~ msgstr[1] "" +#~ "Mostra %s servizi in stato unknown gestiti nel gruppo di servizi %s" + +#~ msgid "List %s service with status unknown unhandled in service group %s" +#~ msgid_plural "" +#~ "List %s services with status unknown unhandled in service group %s" +#~ msgstr[0] "" +#~ "Mostra %s servizio in stato unknown non gestito nel gruppo di servizi %s" +#~ msgstr[1] "" +#~ "Mostra %s servizi in stato unknown non gestiti nel gruppo di servizi %s" + +#~ msgid "List %s service with status warning unhandled in service group %s" +#~ msgid_plural "" +#~ "List %s services with status warning unhandled in service group %s" +#~ msgstr[0] "" +#~ "Mostra %s servizio in stato warning non gestito nel gruppo di servizi %s" +#~ msgstr[1] "" +#~ "Mostra %s servizi in stato warning non gestiti nel gruppo di servizi %s" + +#~ msgid "List %u host which is in downtime" +#~ msgid_plural "List %u hosts which are in downtime" +#~ msgstr[0] "Mostra %u host in manutenzione" +#~ msgstr[1] "Mostra %u host in manutenzione" + +#~ msgid "List %u service with status critical handled in host group %s" +#~ msgid_plural "" +#~ "List %u services with status critical handled in host group %s" +#~ msgstr[0] "" +#~ "Mostra %u servizio in stato critical gestito nel gruppo di host %s" +#~ msgstr[1] "" +#~ "Mostra %u servizi in stato critical gestiti nel gruppo di host %s" + +#~ msgid "List %u service with status pending in host group %s" +#~ msgid_plural "List %u services with status pending in host group %s" +#~ msgstr[0] "Mostra %u servizio in stato pending nel gruppo di host %s" +#~ msgstr[1] "Mostra %u servizi in stato pending nel gruppo di host %s" + +#~ msgid "List %u service with status unknown handled in host group %s" +#~ msgid_plural "List %u services with status unknown handled in host group %s" +#~ msgstr[0] "" +#~ "Mostra %u servizio in stato unknown gestito nel gruppo di host %s" +#~ msgstr[1] "Mostra %u servizi in stato unknown gestiti nel gruppo di host %s" + +#~ msgid "List %u service with status unknown unhandled in host group %s" +#~ msgid_plural "" +#~ "List %u services with status unknown unhandled in host group %s" +#~ msgstr[0] "" +#~ "Mostra %u servizio in stato unknown non gestito nel gruppo di host %s" +#~ msgstr[1] "" +#~ "Mostra %u servizi in stato unknown non gestiti nel gruppo di host %s" + +#~ msgid "List %u service with status warning handled in host group %s" +#~ msgid_plural "List %u services with status warning handled in host group %s" +#~ msgstr[0] "" +#~ "Mostra %u servizio in stato warning gestito nel gruppo di host %s" +#~ msgstr[1] "Mostra %u servizi in stato warning gestiti nel gruppo di host %s" + +#~ msgid "List %u service with status warning unhandled in host group %s" +#~ msgid_plural "" +#~ "List %u services with status warning unhandled in host group %s" +#~ msgstr[0] "" +#~ "Mostra %u servizio in stato warning non gestito nel gruppo di host %s" +#~ msgstr[1] "" +#~ "Mostra %u servizi in stato warning non gestiti nel gruppo di host %s" + +#~ msgid "List all" +#~ msgstr "Mostra tutto" + +#~ msgid "PHP Module: Sockets" +#~ msgstr "Modulo PHP: Sockets" + +#~ msgid "Reschedule host checks" +#~ msgstr "Rischedula i Controlli dell'Host" + +#~ msgid "Reschedule service checks" +#~ msgstr "Rischedula i Controlli del Servizio" + +#~ msgid "Schedule downtime for unhandled host problem" +#~ msgid_plural "Schedule downtimes for unhandled host problems" +#~ msgstr[0] "Pianifica Manutenzione per il Problema dell'Host Non Gestito" +#~ msgstr[1] "Pianifica Manutenzione per i Problemi degli Host Non Gestiti" + +#~ msgid "Schedule downtime for unhandled service problem" +#~ msgid_plural "Schedule downtimes for unhandled service problems" +#~ msgstr[0] "Pianifica Manutenzione per il Problema del Servizio Non Gestito" +#~ msgstr[1] "Pianifica Manutenzione per i Problemi del Servizio Non Gestiti" + +#~ msgid "Schedule host downtimes" +#~ msgstr "Pianifica Manutenzione Host" + +#~ msgid "Schedule service downtimes" +#~ msgstr "Pianifica Manutenzione Sevizio" + +#~ msgid "Services with state %s" +#~ msgstr "Servizi in stato %s" + +#~ msgid "Submit passive check results" +#~ msgstr "Invia Risultato Passivo" + +#~ msgid "The PHP Module sockets is available." +#~ msgstr "Il modulo PHP Sockets è disponibile" + +#~ msgid "The PHP Module sockets is not available." +#~ msgstr "Il modulo PHP Sockets non è disponibile" + +#~ msgid "Unhandled hosts with state DOWN" +#~ msgstr "Host Non Gestiti in stato DOWN" + +#~ msgid "Unhandled hosts with state UNREACHABLE" +#~ msgstr "Host Non Gestiti in stato UNREACHABLE" + +#~ msgid "Unhandled services with state %s" +#~ msgstr "Servizi Non Gestiti in stato %s" + +#~ msgid "Comment: " +#~ msgstr "Commento: " diff --git a/modules/monitoring/application/locale/pt_BR/LC_MESSAGES/monitoring.mo b/modules/monitoring/application/locale/pt_BR/LC_MESSAGES/monitoring.mo index f21f1cfd0..e9b3ec712 100644 Binary files a/modules/monitoring/application/locale/pt_BR/LC_MESSAGES/monitoring.mo and b/modules/monitoring/application/locale/pt_BR/LC_MESSAGES/monitoring.mo differ diff --git a/modules/monitoring/application/locale/pt_BR/LC_MESSAGES/monitoring.po b/modules/monitoring/application/locale/pt_BR/LC_MESSAGES/monitoring.po index 70b7f726e..9edbc328c 100644 --- a/modules/monitoring/application/locale/pt_BR/LC_MESSAGES/monitoring.po +++ b/modules/monitoring/application/locale/pt_BR/LC_MESSAGES/monitoring.po @@ -5,10 +5,10 @@ # msgid "" msgstr "" -"Project-Id-Version: Monitoring Module (0.0.0)\n" +"Project-Id-Version: Monitoring Module (2.0.0~alpha4)\n" "Report-Msgid-Bugs-To: dev@icinga.org\n" -"POT-Creation-Date: 2014-11-18 16:04-0200\n" -"PO-Revision-Date: 2014-11-18 16:29-0300\n" +"POT-Creation-Date: 2014-12-03 09:10-0200\n" +"PO-Revision-Date: 2014-12-03 09:13-0300\n" "Last-Translator: Carlos Cesario \n" "Language: pt_BR\n" "Language-Team: LANGUAGE \n" @@ -206,7 +206,7 @@ msgstr "%s atrás" msgid "%s configured services:" msgstr "%s serviço(s) configurado(s):" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:9 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:58 #, php-format msgid "%s has been up and running with PID %d since %s" msgstr "%s está up e rodando com o PID %d desde %s" @@ -216,7 +216,12 @@ msgstr "%s está up e rodando com o PID %d desde %s" msgid "%s hosts:" msgstr "%s hosts:" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:16 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/not-running.phtml:5 +#, php-format +msgid "%s is currently not up and running" +msgstr "%s atualmente não está up e nem rodando" + +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:65 #, php-format msgid "%s is not running" msgstr "%s não está rodando" @@ -357,24 +362,14 @@ msgstr "Reconhecer problema dos serviços não tratados" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml:24 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml:30 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:53 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:78 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:56 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:86 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:152 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:186 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:220 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:59 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:89 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:155 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:189 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:223 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:75 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:16 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:86 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:156 msgid "Acknowledged" msgstr "Reconhecido(s)" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:40 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:44 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/comments.phtml:39 msgid "Acknowledgement" msgstr "Reconhecimento" @@ -404,7 +399,7 @@ msgstr "Ativo(s)" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml:45 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:70 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:101 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:98 msgid "Active And Passive Checks Disabled" msgstr "Checagens ativas e passivas desabilitadas" @@ -416,7 +411,7 @@ msgstr "Checagens ativas" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml:43 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:72 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:103 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:100 msgid "Active Checks Disabled" msgstr "Checagens ativas desabilitadas" @@ -428,6 +423,10 @@ msgstr "Checagens ativas de host sendo executadas" msgid "Active Service Checks Being Executed" msgstr "Checagens ativas de serviço sendo executadas" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:121 +msgid "Active checks" +msgstr "Checagens ativas" + #: /usr/local/icingaweb/modules/monitoring/application/controllers/HostController.php:58 msgid "Add Host Comment" msgstr "Adicionar comentário ao host" @@ -457,11 +456,12 @@ msgid_plural "Adding comments.." msgstr[0] "Adicionando comentário.." msgstr[1] "Adicionando comentários.." -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:127 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:145 msgid "Address" msgstr "Endereço" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:131 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:171 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/AlertsummaryController.php:67 msgid "Alert Summary" msgstr "Resumo de alertas" @@ -469,7 +469,7 @@ msgstr "Resumo de alertas" msgid "Alert summary" msgstr "Resumo de alertas" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:358 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:376 msgid "Alias" msgstr "Apelido" @@ -494,6 +494,50 @@ msgstr "Todos os hosts habilitados" msgid "All services enabled" msgstr "Todos os serviços habilitados" +#: /usr/local/icingaweb/modules/monitoring/configuration.php:25 +msgid "Allow acknowledging host and service problems" +msgstr "Permitir o reconhecimento de problemas de hosts e de serviços" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:9 +msgid "Allow all commands" +msgstr "Permitir todos os comandos" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:13 +msgid "Allow all scheduling checks and downtimes" +msgstr "Permitir todos os agendamentos de checagens e paradas" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:29 +msgid "Allow commenting on hosts and services" +msgstr "Permitir comentários nos hosts e serviços" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:41 +msgid "Allow removing host and service comments" +msgstr "Permitir a remoção de comentários de hosts e de serviços" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:45 +msgid "Allow removing host and service downtimes" +msgstr "Permitir a remoção de paradas de hosts e de serviços" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:37 +msgid "Allow removing problem acknowledgements" +msgstr "Permitir a remoção de reconhecimento de problemas" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:33 +msgid "" +"Allow removing problem acknowledgements, host and service comments and " +"downtimes" +msgstr "" +"Permitir a remoção de reconhecimento de problemas, comentários de hosts e de " +"serviços e paradas" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:17 +msgid "Allow scheduling host and service checks" +msgstr "Permitir o agendamento de checagens de hosts e serviços" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:21 +msgid "Allow scheduling host and service downtimes" +msgstr "Permitir o agendamento de paradas de hosts e serviços" + #: /usr/local/icingaweb/modules/monitoring/application/forms/StatehistoryForm.php:22 msgid "Apply" msgstr "Aplicar" @@ -502,7 +546,7 @@ msgstr "Aplicar" msgid "Are you sure you want to remove this instance?" msgstr "Tem certeza de que deseja remover esta instância?" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:290 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:308 msgid "Author" msgstr "Autor" @@ -510,6 +554,10 @@ msgstr "Autor" msgid "Average" msgstr "Média" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:109 +msgid "Average services per host" +msgstr "Média de serviços por host" + #: /usr/local/icingaweb/modules/monitoring/application/controllers/AlertsummaryController.php:444 msgid "Avg (min)" msgstr "Méd (min)" @@ -529,9 +577,13 @@ msgstr "Nome do backend" msgid "Backend Type" msgstr "Tipo do backend" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:139 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:152 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:142 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:34 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:52 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:34 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:52 +msgid "CRITICAL" +msgstr "CRÍTICO(S)" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:8 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:16 msgctxt "icinga.state" @@ -558,8 +610,8 @@ msgstr "Tempo de execução da checagem" msgid "Check latency" msgstr "Latência da checagem" -#: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:40 -#: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:41 +#: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:38 +#: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:39 msgid "Check now" msgstr "Checar agora" @@ -685,18 +737,18 @@ msgid "Commands" msgstr "Comandos" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/history.phtml:49 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:35 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:39 #: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php:54 #: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php:50 #: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:70 msgid "Comment" msgstr "Comentário" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:462 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:481 msgid "Comment Timestamp" msgstr "Data e hora do comentário" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:464 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:483 msgid "Comment Type" msgstr "Tipo do comentário" @@ -725,17 +777,18 @@ msgid "Comment was created by an user." msgstr "O comentário foi criado por um usuário." #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:77 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:96 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:93 msgid "Comment: " msgstr "Comentário:" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:98 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:138 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/comments.phtml:2 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/hosts/show.phtml:106 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/multi/components/comments.phtml:6 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/services/show.phtml:118 #: /usr/local/icingaweb/modules/monitoring/application/forms/EventOverviewForm.php:67 #: /usr/local/icingaweb/modules/monitoring/application/controllers/TimelineController.php:57 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:462 msgid "Comments" msgstr "Comentários" @@ -744,17 +797,22 @@ msgstr "Comentários" msgid "Configuration for backend %s is disabled" msgstr "A configuração para o backend %s está desabilitada" +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:430 +msgid "Contact Groups" +msgstr "Grupos de contatos" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/contact.phtml:4 msgid "Contact details" msgstr "Detalhes do contato" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:90 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:130 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/contacts.phtml:33 msgid "Contactgroups" -msgstr "Grupo de contatos" +msgstr "Grupos de contatos" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:102 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:142 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/contacts.phtml:14 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:350 msgid "Contacts" msgstr "Contatos" @@ -776,23 +834,23 @@ msgstr "Criar um novo backend de monitoramento" msgid "Critical" msgstr "Crítico" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:57 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:97 msgid "Current Downtimes" msgstr "Paradas atuais" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:219 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:237 msgid "Current Host State" msgstr "Estado atual do host" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:151 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:187 msgid "Current Incidents" msgstr "Incidentes atuais" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:215 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:233 msgid "Current Service State" msgstr "Estado atual do serviço" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:128 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:146 msgid "Current State" msgstr "Estado atual" @@ -800,13 +858,6 @@ msgstr "Estado atual" msgid "Custom notification has been sent" msgstr "A notificação personalizada foi enviada" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:44 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:56 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:47 -msgctxt "icinga.state" -msgid "DOWN" -msgstr "DOWN" - #: /usr/local/icingaweb/modules/monitoring/library/Monitoring/BackendStep.php:114 msgid "Database Name" msgstr "Nome do banco de dados" @@ -869,7 +920,7 @@ msgid "Disable Flapping Detection" msgstr "Desabilitar detecção de oscilação" #: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php:24 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ProcessController.php:90 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ProcessController.php:94 #: /usr/local/icingaweb/modules/monitoring/application/controllers/CommandController.php:527 #: /usr/local/icingaweb/modules/monitoring/application/controllers/CommandController.php:656 msgid "Disable Notifications" @@ -961,12 +1012,12 @@ msgid "Downtime" msgstr "Parada" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/history.phtml:114 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:80 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:89 msgid "Downtime End" msgstr "Término da parada" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/history.phtml:109 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:75 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:84 msgid "Downtime Start" msgstr "Início da parada" @@ -987,13 +1038,14 @@ msgstr "Parada removida" msgid "Downtime scheduling requested" msgstr "Agendamento de parada solicitado" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:94 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:134 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/downtime.phtml:2 #: /usr/local/icingaweb/modules/monitoring/application/forms/EventOverviewForm.php:57 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:279 msgid "Downtimes" msgstr "Paradas" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:295 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:313 msgid "Duration" msgstr "Duração" @@ -1007,7 +1059,7 @@ msgstr "Editar instância existente" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/contact.phtml:22 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/contacts.phtml:28 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:359 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:377 msgid "Email" msgstr "Email" @@ -1079,7 +1131,7 @@ msgstr "" "Habilitar processamento de dados de performance na base de todo o programa." #: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:94 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:292 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:310 msgid "End Time" msgstr "Horário de término" @@ -1103,12 +1155,12 @@ msgstr "" "Entre com a data e hora para este reconhecimento. O Icinga excluirá o " "reconhecimento após esse período terminar." -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:289 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:307 msgid "Entry Time" -msgstr "Entre com o período" +msgstr "Período" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:113 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:371 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:153 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:389 msgid "Event Grid" msgstr "Grade de eventos" @@ -1122,8 +1174,8 @@ msgstr "Manipulador de evento" msgid "Event Handlers Enabled" msgstr "Manipulador de eventos habilitado" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:118 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:538 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:158 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:589 msgid "Event Overview" msgstr "Visão geral dos eventos" @@ -1132,11 +1184,15 @@ msgstr "Visão geral dos eventos" msgid "Event handlers" msgstr "Manipulador de eventos" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:117 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:157 msgid "Events" msgstr "Eventos" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:465 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:128 +msgid "Execution time" +msgstr "Tempo de execução" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:484 msgid "Expiration" msgstr "Validade" @@ -1149,6 +1205,10 @@ msgstr "Tempo de validade" msgid "Expires" msgstr "Expira" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:15 +msgid "Feature Commands" +msgstr "Comandos dos recursos" + #: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:114 msgid "Fixed" msgstr "Permanente" @@ -1170,13 +1230,17 @@ msgstr "Detecção de oscilação" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/history.phtml:79 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:57 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:50 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:84 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:54 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:81 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/comments.phtml:25 #: /usr/local/icingaweb/modules/monitoring/application/forms/EventOverviewForm.php:87 msgid "Flapping" msgstr "Oscilando" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:59 +msgid "Flapping Stopped" +msgstr "Oscilação parada" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/history.phtml:84 msgid "Flapping stopped" msgstr "Oscilação parada" @@ -1197,11 +1261,11 @@ msgstr "Forçar checagem" msgid "From" msgstr "De" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:46 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:48 msgid "Global Host Event Handler" msgstr "Manipulador de eventos de host global" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:40 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:42 msgid "Global Service Event Handler" msgstr "Manipulador de eventos de serviço global" @@ -1214,13 +1278,13 @@ msgid "Handled hosts with state UNREACHABLE" msgstr "Hosts tratados com o estado INACESSÍVEL " #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml:68 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:63 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:64 #, php-format msgid "Handled services with state %s" msgstr "Serviços tratados com o estado %s" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:129 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:217 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:147 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:235 msgid "Hard State" msgstr "Estado hard" @@ -1228,40 +1292,61 @@ msgstr "Estado hard" msgid "Hard state changes" msgstr "Alterações no estado hard" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:110 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:150 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/alertsummary/index.phtml:72 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ShowController.php:235 +#: /usr/local/icingaweb/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:219 msgid "History" msgstr "Histórico" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/partials/command-form.phtml:9 #: /usr/local/icingaweb/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:32 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ShowController.php:205 #: /usr/local/icingaweb/modules/monitoring/library/Monitoring/BackendStep.php:106 +#: /usr/local/icingaweb/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:189 msgid "Host" msgstr "Host" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:288 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:463 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:306 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:482 msgid "Host / Service" msgstr "Host / Serviço" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:221 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:239 msgid "Host Address" msgstr "Endereço do host" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:220 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:134 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:156 +msgid "Host Checks" +msgstr "Checagens de hosts" + +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:25 +msgid "Host Group" +msgstr "Grupos de hosts" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:574 +msgid "Host Group Name" +msgstr "Nome do grupo de hosts" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:542 +msgid "Host Groups" +msgstr "Grupos de hosts" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:238 msgid "Host Name" msgstr "Nome do host" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:362 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:380 msgid "Host Notification Timeperiod" msgstr "Período de notificação do host" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:49 -#: /usr/local/icingaweb/modules/monitoring/configuration.php:161 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:89 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:197 msgid "Host Problems" msgstr "Hosts com problemas" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:218 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:236 msgid "Host Severity" msgstr "Gravidade do host" @@ -1292,18 +1377,14 @@ msgstr "Host ou serviço não encontrado" msgid "Host- and Servicechecks" msgstr "Checagem de hosts e serviços" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:529 -msgid "Hostgroup Name" -msgstr "Nome do grupo de hosts" - -#: /usr/local/icingaweb/modules/monitoring/configuration.php:28 -#: /usr/local/icingaweb/modules/monitoring/configuration.php:86 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:68 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:126 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml:13 msgid "Hostgroups" msgstr "Grupos de hosts" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:126 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:598 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:144 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:629 msgid "Hostname" msgstr "Nome do host" @@ -1311,16 +1392,16 @@ msgstr "Nome do host" msgid "Hostname or address of the remote Icinga instance" msgstr "Nome ou o endereço da instância remota do Icinga" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:26 -#: /usr/local/icingaweb/modules/monitoring/configuration.php:70 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:66 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:110 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/contact.phtml:33 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:25 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:28 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:85 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml:37 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/multi/components/summary.phtml:5 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:7 #: /usr/local/icingaweb/modules/monitoring/application/forms/StatehistoryForm.php:102 #: /usr/local/icingaweb/modules/monitoring/application/controllers/ChartController.php:205 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:110 #: /usr/local/icingaweb/modules/monitoring/application/controllers/HostsController.php:49 msgid "Hosts" msgstr "Hosts" @@ -1331,16 +1412,6 @@ msgstr "Hosts" msgid "Hosts (%u)" msgstr "Hosts (%u)" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:55 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:58 -msgid "Hosts DOWN Handled" -msgstr "Hosts DOWN tratados" - -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:43 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:46 -msgid "Hosts DOWN Unhandled" -msgstr "Hosts DOWN não tratados" - #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/layout/topbar.phtml:11 msgid "Hosts Down Handled" msgstr "Hosts down tratados" @@ -1349,30 +1420,10 @@ msgstr "Hosts down tratados" msgid "Hosts Down Unhandled" msgstr "Hosts down não tratados" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:116 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:119 -msgid "Hosts PENDING" -msgstr "Hosts PENDENTES" - #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/layout/topbar.phtml:24 msgid "Hosts Pending" msgstr "Hosts pendentes" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:85 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:88 -msgid "Hosts UNREACHABLE Handled" -msgstr "Hosts INACESSÍVEIS tratados" - -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:73 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:76 -msgid "Hosts UNREACHABLE Unhandled" -msgstr "Hosts INACESSÍVEIS não tratados" - -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:101 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:104 -msgid "Hosts UP" -msgstr "Hosts UP" - #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/layout/topbar.phtml:19 msgid "Hosts Unreachable Handled" msgstr "Hosts inacessíveis tratados" @@ -1520,8 +1571,8 @@ msgstr "" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/history.phtml:69 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml:38 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:65 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:45 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:92 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:49 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:89 msgid "In Downtime" msgstr "em parada" @@ -1572,7 +1623,7 @@ msgstr "Está faltando o nome da instância" msgid "Invalid instance type \"%s\" given" msgstr "Tipo de instância \"%s\" inválida" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:287 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:305 msgid "Is In Effect" msgstr "Está em vigor" @@ -1586,27 +1637,32 @@ msgstr "" "filtros poderosos que permitem que você mantenha o controle dos eventos mais " "importantes no seu ambiente de monitoramento." -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:124 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:142 msgid "Last Check" msgstr "Última checagem" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:30 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:32 msgid "Last External Command Check" msgstr "Última checagem de comando externo" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:222 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:240 msgid "Last Host Check" msgstr "Última checagem do host" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:34 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:36 msgid "Last Log File Rotation" msgstr "Última rotação do arquivo de log" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:213 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:24 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:24 +msgid "Last Problem" +msgstr "Último problema" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:231 msgid "Last Service Check" msgstr "Última checagem do serviço" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:26 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:28 msgid "Last Status Update" msgstr "Última atualização do status" @@ -1614,6 +1670,10 @@ msgstr "Última atualização do status" msgid "Last check" msgstr "Última checagem" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:127 +msgid "Latency" +msgstr "Latência" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/timeline/index.phtml:16 msgid "Legend" msgstr "Legenda" @@ -1668,6 +1728,12 @@ msgstr "Backends de monitoramento" msgid "Monitoring Features" msgstr "Recursos de monitoramento" +#: /usr/local/icingaweb/modules/monitoring/configuration.php:179 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ProcessController.php:26 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ProcessController.php:37 +msgid "Monitoring Health" +msgstr "Saúde do monitoramento" + #: /usr/local/icingaweb/modules/monitoring/application/forms/Setup/IdoResourcePage.php:31 msgid "Monitoring IDO Resource" msgstr "Recursos IDO de monitoramento" @@ -1752,13 +1818,13 @@ msgstr "" msgid "Monitoring security configuration has been successfully created: %s" msgstr "Configuração de segurança do monitoramento criada com sucesso: %s" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:37 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:43 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:49 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:39 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:45 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:51 msgid "N/A" msgstr "N/D" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:357 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:375 msgid "Name" msgstr "Nome" @@ -1808,11 +1874,11 @@ msgstr "Nenhum contato correspondente ao filtro" msgid "No history available for this object" msgstr "Nenhum histórico disponível para este objeto" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:17 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:19 msgid "No history events matching the filter" msgstr "Nenhum histórico de eventos correspondente ao filtro" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:11 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:17 msgid "No host groups matching the filter" msgstr "Nenhum grupo de hosts correspondente ao filtro" @@ -1838,7 +1904,7 @@ msgstr "Nenhuma notificação foi enviada para este contato" msgid "No notifications matching the filter" msgstr "Nenhuma notificação correspondente ao filtro" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:11 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:17 msgid "No service groups matching the filter" msgstr "Nenhum grupo de serviços correspondente ao filtro" @@ -1846,7 +1912,7 @@ msgstr "Nenhum grupo de serviços correspondente ao filtro" msgid "No services configured on this host" msgstr "Nenhum serviço configurado neste host" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:41 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:38 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/multi/service.phtml:17 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/services/show.phtml:6 msgid "No services matching the filter" @@ -1869,11 +1935,11 @@ msgid "Not acknowledged" msgstr "Não reconhecido" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/history.phtml:34 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:30 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:34 msgid "Notification" msgstr "Notificação" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:323 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:341 msgid "Notification Start" msgstr "Iniciar notificação" @@ -1891,6 +1957,7 @@ msgstr "Atraso da notificação foi solicitada" #: /usr/local/icingaweb/modules/monitoring/application/controllers/AlertsummaryController.php:473 #: /usr/local/icingaweb/modules/monitoring/application/controllers/AlertsummaryController.php:480 #: /usr/local/icingaweb/modules/monitoring/application/controllers/TimelineController.php:47 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:328 #: /usr/local/icingaweb/modules/monitoring/application/controllers/MultiController.php:64 #: /usr/local/icingaweb/modules/monitoring/application/controllers/MultiController.php:132 msgid "Notifications" @@ -1898,7 +1965,7 @@ msgstr "Notificações" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml:34 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:61 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:88 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:85 msgid "Notifications Disabled" msgstr "Notificações desativadas" @@ -1907,7 +1974,7 @@ msgid "Notifications Enabled" msgstr "Notificações habilitadas" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/alertsummary/index.phtml:18 -msgid "Notifications and problems" +msgid "Notifications and Problems" msgstr "Notificações e problemas" #: /usr/local/icingaweb/modules/monitoring/application/controllers/CommandController.php:657 @@ -1940,13 +2007,20 @@ msgstr "As notificações serão reativadas em %s" msgid "Notifications will be re-enabled in %s." msgstr "As notificações serão reativadas em %s." -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:237 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:240 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:70 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:70 +msgid "OK" +msgstr "OK" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:217 msgctxt "icinga.state" msgid "OK" msgstr "OK" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:73 +msgid "Object summaries" +msgstr "Resumo dos objetos" + #: /usr/local/icingaweb/modules/monitoring/application/forms/StatehistoryForm.php:98 msgid "Object type" msgstr "Tipo do objeto" @@ -1998,14 +2072,15 @@ msgstr "Uma semana" msgid "One year" msgstr "Um ano" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:62 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:102 msgid "Overview" msgstr "Visão geral" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:117 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:253 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:120 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:256 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:76 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:76 +msgid "PENDING" +msgstr "PENDENTE(S)" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:252 msgctxt "icinga.state" msgid "PENDING" @@ -2016,7 +2091,7 @@ msgstr "PENDENTE" msgid "Pager" msgstr "Pager" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:360 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:378 msgid "Pager Address / Number" msgstr "Endereço / Número do pager" @@ -2043,6 +2118,10 @@ msgstr "Checagens passivas de serviços sendo aceitas" msgid "Passive check result has been submitted" msgstr "O resultado da checagem passiva foi enviado" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:151 +msgid "Passive checks" +msgstr "Checagens passivas" + #: /usr/local/icingaweb/modules/monitoring/application/controllers/CommandController.php:480 msgid "Passive checks for this object will be accepted." msgstr "Checagens passivas serão aceitas para este objeto." @@ -2067,8 +2146,7 @@ msgstr "Caminho para o arquivo de comando local do icinga" msgid "Performance Data Being Processed" msgstr "Dados de performance sendo processados" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:143 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ProcessController.php:33 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:71 msgid "Performance Info" msgstr "Informação de performance" @@ -2114,6 +2192,10 @@ msgstr "" "Por favor, preencha os detalhes da conexão abaixo para acessar a interface " "do soquete Livestatus do seu ambiente de monitoramento." +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/output.phtml:2 +msgid "Plugin Output" +msgstr "Saída do plugin" + #: /usr/local/icingaweb/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:43 #: /usr/local/icingaweb/modules/monitoring/library/Monitoring/BackendStep.php:110 msgid "Port" @@ -2129,17 +2211,15 @@ msgstr "" "várias linhas ou pressione e segure a tecla Shift para selecionar um " "intervalo de linhas." -#: /usr/local/icingaweb/modules/monitoring/configuration.php:34 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:74 msgid "Problems" msgstr "Problemas" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:139 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ProcessController.php:26 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ProcessController.php:44 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:20 msgid "Process Info" msgstr "Informação do processo" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:22 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:24 msgid "Program Start Time" msgstr "Horário de início" @@ -2148,7 +2228,7 @@ msgstr "Horário de início" msgid "Protected Custom Variables" msgstr "Variáveis personalizadas protegidas" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:157 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:193 msgid "Recently Recovered Services" msgstr "Serviços recuperados recentemente" @@ -2225,7 +2305,7 @@ msgstr[1] "Removendo reconhecimento dos problemas.." msgid "Report interval" msgstr "Intervalo de relatório" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:126 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:166 msgid "Reporting" msgstr "Relatórios" @@ -2293,6 +2373,11 @@ msgstr "Reiniciar o processo de monitoramento" msgid "Restart the monitoring process." msgstr "Reiniciar o processo de monitoramento." +#: /usr/local/icingaweb/modules/monitoring/configuration.php:50 +msgid "Restrict views to the hosts and services that match the filter" +msgstr "" +"Restringir a visualização para os hosts e serviços que correspondam ao filtro" + #: /usr/local/icingaweb/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php:44 msgid "SSH port to connect to on the remote Icinga instance" msgstr "Porta SSH para conectar-se na instância remota do Icinga" @@ -2371,15 +2456,15 @@ msgstr "Agendar parada para serviços" msgid "Schedule triggered downtime for all child hosts" msgstr "Agendar parada acionada para todos os hosts filhos" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:294 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:312 msgid "Scheduled End" msgstr "Término do agendamento" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:293 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:311 msgid "Scheduled Start" msgstr "Início do agendamento" -#: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:73 +#: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php:71 msgid "Scheduling check.." msgid_plural "Scheduling checks.." msgstr[0] "Agendando checagem.." @@ -2415,24 +2500,43 @@ msgstr "Enviar notificação" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/header.phtml:23 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/partials/command-form.phtml:10 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ShowController.php:215 +#: /usr/local/icingaweb/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:199 msgid "Service" msgstr "Serviço" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:78 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:587 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:142 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:162 +msgid "Service Checks" +msgstr "Checagem de serviços" + +#: /usr/local/icingaweb/modules/monitoring/configuration.php:118 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:618 msgid "Service Grid" msgstr "Grade de serviços" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:216 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:25 +msgid "Service Group" +msgstr "Grupos de serviços" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:527 +msgid "Service Group Name" +msgstr "Nome do grupo de serviços" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:495 +msgid "Service Groups" +msgstr "Grupos de serviços" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:234 msgid "Service Name" msgstr "Nome do serviço" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:361 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:379 msgid "Service Notification Timeperiod" msgstr "Período de notificação do serviço" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:53 -#: /usr/local/icingaweb/modules/monitoring/configuration.php:153 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:93 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:189 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:55 msgid "Service Problems" msgstr "Serviços com problemas" @@ -2446,7 +2550,12 @@ msgstr "Serviços com problemas no host" msgid "Service State" msgstr "Estado do serviço" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:599 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:27 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:27 +msgid "Service States" +msgstr "Estado dos serviços" + +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:630 msgid "Service description" msgstr "Descrição do serviço" @@ -2463,21 +2572,16 @@ msgstr "Serviço não encontrado" msgid "Service notification period" msgstr "Período de notificação de serviço" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:498 -msgid "Servicegroup Name" -msgstr "Nome do grupo de serviços" - -#: /usr/local/icingaweb/modules/monitoring/configuration.php:29 -#: /usr/local/icingaweb/modules/monitoring/configuration.php:82 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:69 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:122 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml:14 msgid "Servicegroups" msgstr "Grupos de serviços" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:27 -#: /usr/local/icingaweb/modules/monitoring/configuration.php:74 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:67 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:114 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/contact.phtml:38 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:26 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:29 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:97 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml:30 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/multi/components/summary.phtml:5 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml:8 @@ -2485,7 +2589,10 @@ msgstr "Grupos de serviços" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml:28 #: /usr/local/icingaweb/modules/monitoring/application/forms/StatehistoryForm.php:101 #: /usr/local/icingaweb/modules/monitoring/application/controllers/ChartController.php:146 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ShowController.php:225 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:184 #: /usr/local/icingaweb/modules/monitoring/application/controllers/ServicesController.php:49 +#: /usr/local/icingaweb/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php:209 msgid "Services" msgstr "Serviços" @@ -2494,13 +2601,18 @@ msgstr "Serviços" msgid "Services (%u)" msgstr "Serviços (%u)" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:151 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:154 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:531 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:578 +msgid "Services CRITICAL" +msgstr "Serviços CRÍTICOS" + +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:130 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:130 msgid "Services CRITICAL Handled" msgstr "Serviços CRÍTICOS tratados" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:138 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:141 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:116 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:116 msgid "Services CRITICAL Unhandled" msgstr "Serviços CRÍTICOS não tratados" @@ -2512,8 +2624,10 @@ msgstr "Serviços críticos tratados" msgid "Services Critical Unhandled" msgstr "Serviços críticos não tratados" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:236 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:239 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:99 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:99 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:529 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:576 msgid "Services OK" msgstr "Serviços OK" @@ -2521,8 +2635,10 @@ msgstr "Serviços OK" msgid "Services Ok" msgstr "Serviços Ok" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:252 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:255 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:215 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:215 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:533 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:580 msgid "Services PENDING" msgstr "Serviços PENDENTES" @@ -2530,13 +2646,18 @@ msgstr "Serviços PENDENTES" msgid "Services Pending" msgstr "Serviços pendentes" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:219 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:222 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:530 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:577 +msgid "Services UNKNOWN" +msgstr "Serviços DESCONHECIDOS" + +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:164 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:164 msgid "Services UNKNOWN Handled" msgstr "Serviços DESCONHECIDOS tratados" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:206 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:209 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:150 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:150 msgid "Services UNKNOWN Unhandled" msgstr "Serviços DESCONHECIDOS não tratados" @@ -2548,13 +2669,18 @@ msgstr "Serviços desconhecidos tratados" msgid "Services Unknown Unhandled" msgstr "Serviços desconhecidos não tratados" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:185 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:188 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:532 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:579 +msgid "Services WARNING" +msgstr "Serviços em ATENÇÃO" + +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:198 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:198 msgid "Services WARNING Handled" msgstr "Serviços em ATENÇÃO tratados" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:172 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:175 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:184 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:184 msgid "Services WARNING Unhandled" msgstr "Serviços em ATENÇÃO não tratados" @@ -2572,8 +2698,8 @@ msgstr "Serviços em parada" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml:20 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml:84 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:14 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:79 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:15 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:80 #, php-format msgid "Services with state %s" msgstr "Serviços com o estado %s" @@ -2598,8 +2724,10 @@ msgstr "Defina a data e hora de início da parada." msgid "Setup the monitoring module for Icinga Web 2" msgstr "Configuração do módulo de monitoramento para Icinga Web 2" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:125 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:214 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:143 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:232 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:526 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:573 msgid "Severity" msgstr "Gravidade" @@ -2620,13 +2748,15 @@ msgstr "Pular validação" msgid "Socket" msgstr "Soquete" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:11 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:12 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/notifications.phtml:5 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:5 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:12 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:6 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:13 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/downtimes.phtml:5 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegrid.phtml:5 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:7 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/comments.phtml:5 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:7 msgid "Sort by" msgstr "Ordenar por" @@ -2640,7 +2770,7 @@ msgid "Start Accepting Passive Checks" msgstr "Iniciar a receber checagens passivas" #: /usr/local/icingaweb/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php:84 -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:291 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:309 msgid "Start Time" msgstr "Horário de início" @@ -2654,7 +2784,7 @@ msgstr "Iniciar obsessão sob este objeto." #: /usr/local/icingaweb/modules/monitoring/application/controllers/TimelineController.php:67 msgid "Started downtimes" -msgstr "Parada(s) iniciada(s)" +msgstr "Paradas iniciadas" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/downtimes.phtml:34 msgid "Starts" @@ -2691,11 +2821,11 @@ msgstr "Parar o modo obsessão" msgid "Stop obsessing over this object." msgstr "Parar obsessão sob este objeto." -#: /usr/local/icingaweb/modules/monitoring/configuration.php:138 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:178 msgid "System" msgstr "Sistema" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:66 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:106 #: /usr/local/icingaweb/modules/monitoring/application/controllers/TacticalController.php:15 msgid "Tactical Overview" msgstr "Visão tática" @@ -2725,7 +2855,7 @@ msgstr "O identificador deste backend" msgid "The last one occured %s ago" msgstr "O último ocorreu %s atrás" -#: /usr/local/icingaweb/modules/monitoring/application/controllers/ShowController.php:123 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ShowController.php:124 msgid "The parameter `contact' is required" msgstr "O parâmetro `contact' é requerido" @@ -2847,14 +2977,14 @@ msgid "This is the core module for Icinga Web 2." msgstr "Este é o módulo central para o Icinga Web 2." #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/alertsummary/index.phtml:25 -msgid "Time to reaction (Ack, Recover)" +msgid "Time to Reaction (Ack, Recover)" msgstr "Tempo de reação (Ack, Recuperar)" #: /usr/local/icingaweb/modules/monitoring/application/controllers/TimelineController.php:106 msgid "TimeLine interval" msgstr "Intervalo da linha do tempo" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:121 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:161 #: /usr/local/icingaweb/modules/monitoring/application/controllers/TimelineController.php:29 msgid "Timeline" msgstr "Linha do tempo" @@ -2881,9 +3011,16 @@ msgid "Toggling feature.." msgstr "Alternando funcionalidade.." #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/alertsummary/index.phtml:59 -msgid "Top 5 recent alerts" +msgid "Top 5 Recent Alerts" msgstr "Top 5 de alertas recentes" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:26 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:26 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:528 +#: /usr/local/icingaweb/modules/monitoring/application/controllers/ListController.php:575 +msgid "Total Services" +msgstr "Total de serviços" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/alertsummary/index.phtml:32 msgid "Trend" msgstr "Tendências" @@ -2902,39 +3039,30 @@ msgstr "Tipo" msgid "Type: %s" msgstr "Tipo: %s" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:207 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:220 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:210 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:40 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:58 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:40 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:58 +msgid "UNKNOWN" +msgstr "DESCONHECIDO(S)" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:148 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:156 msgctxt "icinga.state" msgid "UNKNOWN" -msgstr "DESCONHECIDO" - -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:74 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:86 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:77 -msgctxt "icinga.state" -msgid "UNREACHABLE" -msgstr "INACESSÍVEL" - -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:102 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:105 -msgctxt "icinga.state" -msgid "UP" -msgstr "UP" +msgstr "DESCONHECIDO(S)" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml:26 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hosts.phtml:49 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:74 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/services.phtml:71 msgid "Unhandled" msgstr "Não tratado(s)" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:39 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:79 msgid "Unhandled Hosts" msgstr "Hosts não tradados" -#: /usr/local/icingaweb/modules/monitoring/configuration.php:44 +#: /usr/local/icingaweb/modules/monitoring/configuration.php:84 msgid "Unhandled Services" msgstr "Serviços não tratados" @@ -2947,7 +3075,7 @@ msgid "Unhandled hosts with state UNREACHABLE" msgstr "Hosts não tratados com o estado INACESSÍVEL" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml:51 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:46 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:47 #, php-format msgid "Unhandled services with state %s" msgstr "Serviços não tratados com o estado %s" @@ -3011,9 +3139,13 @@ msgstr "Nome do usuario" msgid "Value for interval not valid" msgstr "Valor para o intervalo não é válido" -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:173 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:186 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:176 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:46 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/hostgroups.phtml:64 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:46 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/servicegroups.phtml:64 +msgid "WARNING" +msgstr "ATENÇÃO" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:78 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml:86 msgctxt "icinga.state" @@ -3072,13 +3204,13 @@ msgid "notifications per hour" msgstr "notificações por hora" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml:20 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:14 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:15 #: /usr/local/icingaweb/modules/monitoring/library/Monitoring/Object/Service.php:216 msgid "ok" msgstr "ok" #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/history.phtml:135 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:100 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/eventhistory.phtml:109 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/downtimes.phtml:56 #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/comments.phtml:60 msgid "on" @@ -3091,8 +3223,12 @@ msgctxt "datetime" msgid "on %s" msgstr "em %s" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:78 +msgid "overall" +msgstr "global" + #: /usr/local/icingaweb/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml:84 -#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:79 +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:80 #: /usr/local/icingaweb/modules/monitoring/library/Monitoring/Object/Host.php:184 #: /usr/local/icingaweb/modules/monitoring/library/Monitoring/Object/Service.php:228 msgid "pending" @@ -3103,6 +3239,10 @@ msgctxt "multiselection" msgid "row(s) selected" msgstr "linha(s) selecionada(s)" +#: /usr/local/icingaweb/modules/monitoring/application/views/scripts/process/info.phtml:79 +msgid "scheduled" +msgstr "agendado" + #: /usr/local/icingaweb/modules/monitoring/application/views/helpers/MonitoringState.php:101 msgid "since" msgstr "desde" @@ -3126,3 +3266,33 @@ msgstr "up" #: /usr/local/icingaweb/modules/monitoring/library/Monitoring/Object/Service.php:219 msgid "warning" msgstr "atenção" + +#~ msgctxt "icinga.state" +#~ msgid "DOWN" +#~ msgstr "DOWN" + +#~ msgid "Hosts DOWN Handled" +#~ msgstr "Hosts DOWN tratados" + +#~ msgid "Hosts DOWN Unhandled" +#~ msgstr "Hosts DOWN não tratados" + +#~ msgid "Hosts PENDING" +#~ msgstr "Hosts PENDENTES" + +#~ msgid "Hosts UNREACHABLE Handled" +#~ msgstr "Hosts INACESSÍVEIS tratados" + +#~ msgid "Hosts UNREACHABLE Unhandled" +#~ msgstr "Hosts INACESSÍVEIS não tratados" + +#~ msgid "Hosts UP" +#~ msgstr "Hosts UP" + +#~ msgctxt "icinga.state" +#~ msgid "UNREACHABLE" +#~ msgstr "INACESSÍVEL" + +#~ msgctxt "icinga.state" +#~ msgid "UP" +#~ msgstr "UP" diff --git a/modules/monitoring/application/views/helpers/CheckPerformance.php b/modules/monitoring/application/views/helpers/CheckPerformance.php index 19912d3d6..d72dee491 100644 --- a/modules/monitoring/application/views/helpers/CheckPerformance.php +++ b/modules/monitoring/application/views/helpers/CheckPerformance.php @@ -1,6 +1,5 @@ view->escape((string) $struct); + if (is_scalar($struct)) { + return $this->view->escape( + is_string($struct) + ? $struct + : var_export($struct, true) + ); } elseif (is_array($struct)) { return $this->renderArray($struct); } elseif (is_object($struct)) { @@ -37,7 +42,7 @@ class Zend_View_Helper_Customvar extends Zend_View_Helper_Abstract protected function renderObject($object) { - if (empty($object)) { + if (0 === count((array) $object)) { return '{}'; } $out = "{
      \n"; diff --git a/modules/monitoring/application/views/helpers/HostFlags.php b/modules/monitoring/application/views/helpers/HostFlags.php new file mode 100644 index 000000000..6e416691f --- /dev/null +++ b/modules/monitoring/application/views/helpers/HostFlags.php @@ -0,0 +1,36 @@ +host_handled && $host->host_state > 0) { + $icons[] = $this->view->icon('attention-alt', $this->view->translate('Unhandled')); + } + if ($host->host_acknowledged) { + $icons[] = $this->view->icon('ok', $this->view->translate('Acknowledged')); + } + if ($host->host_is_flapping) { + $icons[] = $this->view->icon('flapping', $this->view->translate('Flapping')); + } + if (! $host->host_notifications_enabled) { + $icons[] = $this->view->icon('bell-off-empty', $this->view->translate('Notifications Disabled')); + } + if ($host->host_in_downtime) { + $icons[] = $this->view->icon('plug', $this->view->translate('In Downtime')); + } + if (! $host->host_active_checks_enabled) { + if (! $host->host_passive_checks_enabled) { + $icons[] = $this->view->icon('eye-off', $this->view->translate('Active And Passive Checks Disabled')); + } else { + $icons[] = $this->view->icon('eye-off', $this->view->translate('Active Checks Disabled')); + } + } + return $icons; + } +} diff --git a/modules/monitoring/application/views/helpers/IconImage.php b/modules/monitoring/application/views/helpers/IconImage.php new file mode 100644 index 000000000..12108f30b --- /dev/null +++ b/modules/monitoring/application/views/helpers/IconImage.php @@ -0,0 +1,64 @@ +host_icon_image && ! preg_match('/[\'"]/', $object->host_icon_image)) { + return $this->view->img( + 'img/icons/' . Macro::resolveMacros($object->host_icon_image, $object), + null, + array( + 'alt' => $object->host_icon_image_alt, + 'title' => $object->host_icon_image_alt, + 'data-tooltip-delay' => 0 + ) + ); + } + return ''; + } + + /** + * Display the image_icon of a MonitoredObject + * + * @param MonitoredObject|stdClass $object The host or service + * @return string + */ + public function service($object) + { + if ($object->service_icon_image && ! preg_match('/[\'"]/', $object->service_icon_image)) { + return $this->view->img( + 'img/icons/' . Macro::resolveMacros($object->service_icon_image, $object), + null, + array( + 'alt' => $object->service_icon_image_alt, + 'title' => $object->service_icon_image_alt, + 'data-tooltip-delay' => 0 + ) + ); + } + return ''; + } +} diff --git a/modules/monitoring/application/views/helpers/Link.php b/modules/monitoring/application/views/helpers/Link.php new file mode 100644 index 000000000..fcbba2662 --- /dev/null +++ b/modules/monitoring/application/views/helpers/Link.php @@ -0,0 +1,72 @@ +view->qlink( + $linkText, + 'monitoring/host/show', + array('host' => $host), + array('title' => sprintf($this->view->translate('Show detailed information for host %s'), $linkText)) + ); + } + + /** + * Create a service link + * + * @param string $service Service name + * @param string $serviceLinkText Text for the service link, e.g. the service's display name + * @param string $host Hostname + * @param string $hostLinkText Text for the host link, e.g. the host's display name + * @param string $class An optional class to use for this link + * + * @return string + */ + public function service($service, $serviceLinkText, $host, $hostLinkText, $class = null) + { + return sprintf( + '%s: %s', + $this->host($host, $hostLinkText), + $this->view->qlink( + $serviceLinkText, + 'monitoring/service/show', + array('host' => $host, 'service' => $service), + array( + 'title' => sprintf( + $this->view->translate('Show detailed information for service %s on host %s'), + $serviceLinkText, + $hostLinkText + ), + 'class' => $class + ) + ) + ); + } +} diff --git a/modules/monitoring/application/views/helpers/MonitoringFlags.php b/modules/monitoring/application/views/helpers/MonitoringFlags.php index db4b8dedc..6eb188074 100644 --- a/modules/monitoring/application/views/helpers/MonitoringFlags.php +++ b/modules/monitoring/application/views/helpers/MonitoringFlags.php @@ -1,6 +1,5 @@ 'pending', null => 'pending'); - private $hoststates = array('up', 'down', 'unreachable', 99 => 'pending', null => 'pending'); - - /** - * @deprecated The host or service object must know it's possible states. - */ - public function monitoringState($object, $type = 'service') - { - if ($type === 'service') { - return $this->servicestates[$object->service_state]; - } elseif ($type === 'host') { - return $this->hoststates[$object->host_state]; - } - } - - public function monitoringStateById($id, $type = 'service') - { - if ($type === 'service') { - return $this->servicestates[$id]; - } elseif ($type === 'host') { - return $this->hoststates[$id]; - } - } - - /** - * @deprecated Monitoring colors are clustered. - */ - public function getServiceStateColors() - { - return array('#44bb77', '#FFCC66', '#FF5566', '#E066FF', '#77AAFF'); - } - - /** - * @deprecated Monitoring colors are clustered. - */ - public function getHostStateColors() - { - return array('#44bb77', '#FF5566', '#E066FF', '#77AAFF'); - } - - /** - * @deprecated The service object must know about it's possible states. - */ - public function getServiceStateNames() - { - return array_values($this->servicestates); - } - - /** - * @deprecated The host object must know about it's possible states. - */ - public function getHostStateNames() - { - return array_values($this->hoststates); - } - - /** - * @deprecated Not used anywhere. - */ - public function getStateFlags($object, $type = 'service') - { - $state_classes = array(); - if ($type === 'host') { - $state_classes[] = $this->monitoringState($object, "host"); - if ($object->host_acknowledged || $object->host_in_downtime) { - $state_classes[] = 'handled'; - } - if ($object->host_last_state_change > (time() - 600)) { - $state_classes[] = 'new'; - } - } else { - $state_classes[] = $this->monitoringState($object, "service"); - if ($object->service_acknowledged || $object->service_in_downtime) { - $state_classes[] = 'handled'; - } - if ($object->service_last_state_change > (time() - 600)) { - $state_classes[] = 'new'; - } - } - - return $state_classes; - } - - /** - * @deprecated Not translated. - */ - public function getStateTitle($object, $type) - { - return sprintf( - '%s %s %s', - $this->view->translate(strtoupper($this->monitoringState($object, $type))), - $this->view->translate('since'), - date('Y-m-d H:i:s', $object->{$type.'_last_state_change'}) - ); - } -} diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php index 3c3c411b9..090aff03e 100644 --- a/modules/monitoring/application/views/helpers/Perfdata.php +++ b/modules/monitoring/application/views/helpers/Perfdata.php @@ -1,109 +1,116 @@ asArray(); - $onlyPieChartData = array_filter($pset, function ($e) { return $e->getPercentage() > 0; }); - if ($compact) { - $onlyPieChartData = array_slice($onlyPieChartData, 0, 5); - } else { - $nonPieChartData = array_filter($pset, function ($e) { return $e->getPercentage() == 0; }); - } - - $result = ''; - $table = array(); - foreach ($onlyPieChartData as $perfdata) { - $pieChart = $this->createInlinePie($perfdata); - if ($compact) { - if (! $float) { - $result .= $pieChart->render(); - } else { - $result .= '
      ' . $pieChart->render() . '
      '; + $pieChartData = PerfdataSet::fromString($perfdataStr)->asArray(); + uasort( + $pieChartData, + function ($a, $b) { + return $a->worseThan($b) ? -1 : ($b->worseThan($a) ? 1 : 0); + } + ); + $results = array(); + $keys = array('', 'label', 'value', 'min', 'max', 'warn', 'crit'); + $columns = array(); + $labels = array_combine( + $keys, + array( + '', + $this->view->translate('Label'), + $this->view->translate('Value'), + $this->view->translate('Min'), + $this->view->translate('Max'), + $this->view->translate('Warning'), + $this->view->translate('Critical') + ) + ); + foreach ($pieChartData as $perfdata) { + if ($perfdata->isVisualizable()) { + $columns[''] = ''; + } + foreach ($perfdata->toArray() as $column => $value) { + if (empty($value) || + $column === 'min' && floatval($value) === 0.0 || + $column === 'max' && $perfdata->isPercentage() && floatval($value) === 100) { + continue; + } + $columns[$column] = $labels[$column]; + } + } + // restore original column array sorting + $headers = array(); + foreach ($keys as $column) { + if (isset($columns[$column])) { + $headers[$column] = $labels[$column]; + } + } + $table = array('' . implode('', $headers) . ''); + foreach ($pieChartData as $perfdata) { + if ($compact && $perfdata->isVisualizable()) { + $results[] = $perfdata->asInlinePie($color)->render(); + } else { + $data = array(); + if ($perfdata->isVisualizable()) { + $data []= $perfdata->asInlinePie($color)->render() . ' '; + } elseif (isset($columns[''])) { + $data []= ''; + } + if (! $compact) { + foreach ($perfdata->toArray() as $column => $value) { + if (! isset($columns[$column])) { + continue; + } + $text = $this->view->escape(empty($value) ? '-' : $value); + $data []= sprintf( + '%s', + $text, + String::ellipsisCenter($text, 24) + ); + } + } + $table []= '' . implode('', $data) . ''; + } + } + if ($limit > 0) { + $count = $compact ? count($results) : count($table); + if ($count > $limit) { + if ($compact) { + $results = array_slice($results, 0, $limit); + $title = sprintf($this->view->translate('%d more ...'), $count - $limit); + $results[] = '...'; + } else { + $table = array_slice($table, 0, $limit); } - } else { - if (! $perfdata->isPercentage()) { - // TODO: Should we trust sprintf-style placeholders in perfdata titles? - $pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)'); - } - // $pieChart->setStyle('margin: 0.2em 0.5em 0.2em 0.5em;'); - $table[] = '' . $pieChart->render() - . htmlspecialchars($perfdata->getLabel()) - . ' ' - . htmlspecialchars($this->formatPerfdataValue($perfdata)) . - ' '; } } - if ($compact) { - return $result; + return join('', $results); } else { - $pieCharts = empty($table) ? '' : '' . implode("\n", $table) . '
      '; - return $pieCharts . "\n" . implode("
      \n", $nonPieChartData); + if (empty($table)) { + return ''; + } + return sprintf( + '%s
      ', + isset($columns['']) ? 'perfdata-piecharts' : '', + implode("\n", $table) + ); } } - - protected function calculatePieChartData(Perfdata $perfdata) - { - $rawValue = $perfdata->getValue(); - $minValue = $perfdata->getMinimumValue() !== null ? $perfdata->getMinimumValue() : 0; - $maxValue = $perfdata->getMaximumValue(); - $usedValue = ($rawValue - $minValue); - $unusedValue = ($maxValue - $minValue) - $usedValue; - - $gray = $unusedValue; - $green = $orange = $red = 0; - // TODO(#6122): Add proper treshold parsing. - if ($perfdata->getCriticalThreshold() && $perfdata->getValue() > $perfdata->getCriticalThreshold()) { - $red = $usedValue; - } elseif ($perfdata->getWarningThreshold() && $perfdata->getValue() > $perfdata->getWarningThreshold()) { - $orange = $usedValue; - } else { - $green = $usedValue; - } - - return array($green, $orange, $red, $gray); - } - - protected function formatPerfdataValue(Perfdata $perfdata) - { - if ($perfdata->isBytes()) { - return Format::bytes($perfdata->getValue()); - } elseif ($perfdata->isSeconds()) { - return Format::seconds($perfdata->getValue()); - } elseif ($perfdata->isPercentage()) { - return $perfdata->getValue() . '%'; - } - - return $perfdata->getValue(); - } - - protected function createInlinePie(Perfdata $perfdata) - { - $pieChart = new InlinePie($this->calculatePieChartData($perfdata), $perfdata->getLabel()); - $pieChart->setLabel(htmlspecialchars($perfdata->getLabel())); - $pieChart->setHideEmptyLabel(); - - //$pieChart->setHeight(32)->setWidth(32); - if ($perfdata->isBytes()) { - $pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)'); - $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_BYTES); - } else if ($perfdata->isSeconds()) { - $pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)'); - $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_TIME); - } else { - $pieChart->setTooltipFormat('{{label}}: {{formatted}}%'); - $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_RATIO); - $pieChart->setHideEmptyLabel(); - } - return $pieChart; - } } diff --git a/modules/monitoring/application/views/helpers/PluginOutput.php b/modules/monitoring/application/views/helpers/PluginOutput.php index e3d88b26f..4b401a795 100644 --- a/modules/monitoring/application/views/helpers/PluginOutput.php +++ b/modules/monitoring/application/views/helpers/PluginOutput.php @@ -1,41 +1,55 @@ $1OK$2', + '$1WARNING$2', + '$1CRITICAL$2', + '$1UNKNOWN$2', + '@@@@@@', + ); + + public function pluginOutput($output, $raw = false) { if (empty($output)) { return ''; } - $output = preg_replace('~]+>~', "\n", $output); - if (preg_match('~<\w+[^>]*>~', $output)) { + $output = preg_replace('~]*>~', "\n", $output); + if (strlen($output) > strlen(strip_tags($output))) { // HTML - $output = preg_replace('~getPurifier()->purify($output) ); - } elseif (preg_match('~\\\n~', $output)) { - // Plaintext - $output = '
      '
      -               . preg_replace(
      -              '~\\\n~', "\n", preg_replace(
      -                '~\\\n\\\n~', "\n",
      -                preg_replace('~\[OK\]~', '[OK]',
      -                 preg_replace('~\[WARNING\]~', '[WARNING]',
      -                  preg_replace('~\[CRITICAL\]~', '[CRITICAL]',
      -                   preg_replace('~\@{6,}~', '@@@@@@',
      -                     $this->view->escape($output)
      -                ))))
      -              )
      -            ) . '
      '; } else { - $output = '
      '
      -               . preg_replace('~\@{6,}~', '@@@@@@',
      +            // Plaintext
      +            $output = preg_replace(
      +                self::$txtPatterns,
      +                self::$txtReplacements,
                       $this->view->escape($output)
      -            ) . '
      '; + ); + } + if (! $raw) { + $output = '
      ' . $output . '
      '; } $output = $this->fixLinks($output); return $output; @@ -56,7 +70,7 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract parse_str($m[1], $params); if (isset($params['host'])) { $tag->setAttribute('href', $this->view->baseUrl( - '/monitoring/detail/show?host=' . urlencode($params['host'] + '/monitoring/host/show?host=' . urlencode($params['host'] ))); } } else { diff --git a/modules/monitoring/application/views/helpers/RuntimeVariables.php b/modules/monitoring/application/views/helpers/RuntimeVariables.php index fa9b639a8..9134a9711 100644 --- a/modules/monitoring/application/views/helpers/RuntimeVariables.php +++ b/modules/monitoring/application/views/helpers/RuntimeVariables.php @@ -1,6 +1,5 @@ total_hosts = $result->total_hosts; - $out->total_scheduled_hosts = $result->total_scheduled_hosts; - $out->total_services = $result->total_services; - $out->total_scheduled_services = $result->total_scheduled_services; - $out->average_services_per_host = $result->total_services / $result->total_hosts; - $out->average_scheduled_services_per_host = $result->total_scheduled_services / $result->total_scheduled_hosts; + $out->total_hosts = isset($result->total_hosts) + ? $result->total_hosts + : 0; + $out->total_scheduled_hosts = isset($result->total_scheduled_hosts) + ? $result->total_scheduled_hosts + : 0; + $out->total_services = isset($result->total_services) + ? $result->total_services + : 0; + $out->total_scheduled_services = isset($result->total_scheduled_services) + ? $result->total_scheduled_services + : 0; + $out->average_services_per_host = $out->total_hosts > 0 + ? $out->total_services / $out->total_hosts + : 0; + $out->average_scheduled_services_per_host = $out->total_scheduled_hosts > 0 + ? $out->total_scheduled_services / $out->total_scheduled_hosts + : 0; return $out; } diff --git a/modules/monitoring/application/views/helpers/SelectionToolbar.php b/modules/monitoring/application/views/helpers/SelectionToolbar.php deleted file mode 100644 index 0b4254522..000000000 --- a/modules/monitoring/application/views/helpers/SelectionToolbar.php +++ /dev/null @@ -1,25 +0,0 @@ -' - . ' Show All
    '; - } else { - return ''; - } - } -} diff --git a/modules/monitoring/application/views/helpers/ServiceFlags.php b/modules/monitoring/application/views/helpers/ServiceFlags.php new file mode 100644 index 000000000..91b245cb2 --- /dev/null +++ b/modules/monitoring/application/views/helpers/ServiceFlags.php @@ -0,0 +1,32 @@ +service_handled && $service->service_state > 0) { + $icons[] = $this->view->icon('attention-alt', $this->view->translate('Unhandled')); + } + if ($service->service_acknowledged && !$service->service_in_downtime) { + $icons[] = $this->view->icon('ok', $this->view->translate('Acknowledged')); + } + if ($service->service_is_flapping) { + $icons[] = $this->view->icon('flapping', $this->view->translate('Flapping')); + } + if (!$service->service_notifications_enabled) { + $icons[] = $this->view->icon('bell-off-empty', $this->view->translate('Notifications Disabled')); + } + if ($service->service_in_downtime) { + $icons[] = $this->view->icon('plug', $this->view->translate('In Downtime')); + } + if (!$service->service_active_checks_enabled) { + if (!$service->service_passive_checks_enabled) { + $icons[] = $this->view->icon('eye-off', $this->view->translate('Active And Passive Checks Disabled')); + } else { + $icons[] = $this->view->icon('eye-off', $this->view->translate('Active Checks Disabled')); + } + } + return $icons; + } +} \ No newline at end of file diff --git a/modules/monitoring/application/views/helpers/_RenderServicePerfdata.php b/modules/monitoring/application/views/helpers/_RenderServicePerfdata.php deleted file mode 100644 index d7612ac30..000000000 --- a/modules/monitoring/application/views/helpers/_RenderServicePerfdata.php +++ /dev/null @@ -1,25 +0,0 @@ - array("self::renderDiskPie") - ); - - public function renderServicePerfdata($service) - { - if (isset(self::$RENDERMAP[$service->check_command])) { - $fn = self::$RENDERMAP[$service->check_command]; - $fn($service); - } - } - - public static function renderDiskPie($service) { - $perfdata = $service->performance_data; - if(!$perfdata) - return ""; - - } -} diff --git a/modules/monitoring/application/views/scripts/alertsummary/index.phtml b/modules/monitoring/application/views/scripts/alertsummary/index.phtml index 624b942b3..d96ca5bdc 100644 --- a/modules/monitoring/application/views/scripts/alertsummary/index.phtml +++ b/modules/monitoring/application/views/scripts/alertsummary/index.phtml @@ -1,15 +1,13 @@ -getHelper('MonitoringState'); -?> +compact): ?>
    - tabs ?> -
    + tabs; ?> +
    - widget('limiter') ?> - paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?> + limiter; ?> + paginator; ?>
    - +
    @@ -62,8 +60,7 @@ $helper = $this->getHelper('MonitoringState');
    partial('list/notifications.phtml', array( 'notifications' => $this->recentAlerts, - 'compact' => true, - 'inline' => true + 'compact' => true )); ?>
    @@ -74,8 +71,7 @@ $helper = $this->getHelper('MonitoringState');
    partial('list/notifications.phtml', array( 'notifications' => $this->notifications, - 'compact' => true, - 'inline' => true + 'compact' => true )); ?>
    diff --git a/modules/monitoring/application/views/scripts/chart/test.phtml b/modules/monitoring/application/views/scripts/chart/test.phtml index 1c2046bdf..214f44d33 100644 --- a/modules/monitoring/application/views/scripts/chart/test.phtml +++ b/modules/monitoring/application/views/scripts/chart/test.phtml @@ -1,6 +1,5 @@ -mah -
    -render(); -?> -
    \ No newline at end of file + +
    + render() ?> +
    + \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/command/list.phtml b/modules/monitoring/application/views/scripts/command/list.phtml deleted file mode 100644 index 747dd4b5e..000000000 --- a/modules/monitoring/application/views/scripts/command/list.phtml +++ /dev/null @@ -1,10 +0,0 @@ -

    - \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/command/renderform.phtml b/modules/monitoring/application/views/scripts/command/renderform.phtml deleted file mode 100644 index 9c8e79428..000000000 --- a/modules/monitoring/application/views/scripts/command/renderform.phtml +++ /dev/null @@ -1,25 +0,0 @@ -
    -tabs ?> -
    -
    -objects) && !empty($this->objects) && isset($this->objects[0]->host_name)): ?> - - - - - - - - -objects as $object): ?> - - - - - - -
    icon('host') ?> Hosticon('conf') ?> Service
    host_name; ?>service_description) ? $object->service_description : '') ?>
    - - -form ?> -
    diff --git a/modules/monitoring/application/views/scripts/comment/remove.phtml b/modules/monitoring/application/views/scripts/comment/remove.phtml new file mode 100644 index 000000000..73f8c68a8 --- /dev/null +++ b/modules/monitoring/application/views/scripts/comment/remove.phtml @@ -0,0 +1,11 @@ +
    + + compact): ?> + tabs; ?> + + + render('partials/downtime/downtime-header.phtml'); ?> +
    +
    + +
    diff --git a/modules/monitoring/application/views/scripts/comment/show.phtml b/modules/monitoring/application/views/scripts/comment/show.phtml new file mode 100644 index 000000000..17e72e7c9 --- /dev/null +++ b/modules/monitoring/application/views/scripts/comment/show.phtml @@ -0,0 +1,81 @@ +
    + compact): ?> + tabs; ?> + + +
    + render('partials/comment/comment-header.phtml'); ?> +
    +
    +
    + +

    translate('Comment detail information') ?>

    + + + + comment->objecttype === 'service'): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    translate('Service') ?> + icon('service', $this->translate('Service')); ?> + link()->service( + $this->comment->service_description, + $this->comment->service_display_name, + $this->comment->host_name, + $this->comment->host_display_name + ); + ?> + translate('Host') ?> + icon('host', $this->translate('Host')); ?> + link()->host( + $this->comment->host_name, + $this->comment->host_display_name + ); + ?> +
    translate('Author') ?>icon('user', $this->translate('User')) ?> escape($this->comment->author) ?>
    translate('Persistent') ?>escape($this->comment->persistent) ? $this->translate('Yes') : $this->translate('No') ?>
    translate('Created') ?>formatDateTime($this->comment->timestamp) ?>
    translate('Expires') ?> + comment->expiration ? sprintf( + $this->translate('This comment expires on %s at %s.'), + $this->formatDate($this->comment->expiration), + $this->formatTime($this->comment->expiration) + ) : $this->translate('This comment does not expire.'); + ?> +
    translate('Commands') ?> + +
    + +
    + diff --git a/modules/monitoring/application/views/scripts/comments/delete-all.phtml b/modules/monitoring/application/views/scripts/comments/delete-all.phtml new file mode 100644 index 000000000..698c4eea7 --- /dev/null +++ b/modules/monitoring/application/views/scripts/comments/delete-all.phtml @@ -0,0 +1,12 @@ +
    + + compact): ?> + tabs; ?> + + + render('partials/comment/comments-header.phtml'); ?> +
    + +
    + +
    diff --git a/modules/monitoring/application/views/scripts/comments/show.phtml b/modules/monitoring/application/views/scripts/comments/show.phtml new file mode 100644 index 000000000..4f9c64d47 --- /dev/null +++ b/modules/monitoring/application/views/scripts/comments/show.phtml @@ -0,0 +1,25 @@ +
    + compact): ?> + tabs; ?> + + +
    + render('partials/comment/comments-header.phtml'); ?> +
    +
    + +
    +

    icon('reschedule') ?> translate('Commands') ?>

    + qlink( + sprintf( + $this->translate('Remove %d comments'), + count($comments) + ), + $removeAllLink, + null, + array( + 'icon' => 'trash', + 'title' => $this->translate('Remove all selected comments.') + ) + ) ?> +
    diff --git a/modules/monitoring/application/views/scripts/config/createbackend.phtml b/modules/monitoring/application/views/scripts/config/createbackend.phtml deleted file mode 100644 index 0ef51cd6a..000000000 --- a/modules/monitoring/application/views/scripts/config/createbackend.phtml +++ /dev/null @@ -1,2 +0,0 @@ -

    translate('Add New Backend'); ?>

    - \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/config/createinstance.phtml b/modules/monitoring/application/views/scripts/config/createinstance.phtml deleted file mode 100644 index 49c8b5ec5..000000000 --- a/modules/monitoring/application/views/scripts/config/createinstance.phtml +++ /dev/null @@ -1,2 +0,0 @@ -

    translate('Add New Instance'); ?>

    - \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/config/editbackend.phtml b/modules/monitoring/application/views/scripts/config/editbackend.phtml deleted file mode 100644 index 58b6c18f1..000000000 --- a/modules/monitoring/application/views/scripts/config/editbackend.phtml +++ /dev/null @@ -1,2 +0,0 @@ -

    translate('Edit Existing Backend'); ?>

    - \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/config/editinstance.phtml b/modules/monitoring/application/views/scripts/config/editinstance.phtml deleted file mode 100644 index b12f262c3..000000000 --- a/modules/monitoring/application/views/scripts/config/editinstance.phtml +++ /dev/null @@ -1,2 +0,0 @@ -

    translate('Edit Existing Instance'); ?>

    - \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/config/form.phtml b/modules/monitoring/application/views/scripts/config/form.phtml new file mode 100644 index 000000000..cbf06590d --- /dev/null +++ b/modules/monitoring/application/views/scripts/config/form.phtml @@ -0,0 +1,6 @@ +
    + showOnlyCloseButton(); ?> +
    +
    + +
    \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/config/index.phtml b/modules/monitoring/application/views/scripts/config/index.phtml index b1729f9b1..c985805eb 100644 --- a/modules/monitoring/application/views/scripts/config/index.phtml +++ b/modules/monitoring/application/views/scripts/config/index.phtml @@ -9,7 +9,7 @@ icon('plus'); ?> translate('Create New Monitoring Backend'); ?>

    - +
    @@ -18,18 +18,30 @@ backendsConfig as $backendName => $config): ?> @@ -41,7 +53,7 @@ icon('plus'); ?> translate('Create New Instance'); ?>

    -
    translate('Monitoring Backend'); ?> translate('Remove'); ?>
    - - icon('edit'); ?> escape($backendName); ?> - + qlink( + $backendName, + '/monitoring/config/editbackend', + array('backend' => $backendName), + array( + 'icon' => 'edit', + 'title' => sprintf($this->translate('Edit monitoring backend %s'), $backendName) + ) + ); ?> (translate('Type: %s'), $this->escape($config->type === 'ido' ? 'IDO' : ucfirst($config->type)) ); ?>) - - icon('cancel'); ?> - + qlink( + '', + '/monitoring/config/removebackend', + array('backend' => $backendName), + array( + 'icon' => 'trash', + 'title' => sprintf($this->translate('Remove monitoring backend %s'), $backendName) + ) + ); ?>
    +
    @@ -50,18 +62,30 @@ instancesConfig as $instanceName => $config): ?> diff --git a/modules/monitoring/application/views/scripts/config/removebackend.phtml b/modules/monitoring/application/views/scripts/config/removebackend.phtml deleted file mode 100644 index fc7da17e3..000000000 --- a/modules/monitoring/application/views/scripts/config/removebackend.phtml +++ /dev/null @@ -1,2 +0,0 @@ -

    translate('Remove Existing Backend'); ?>

    - \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/config/removeinstance.phtml b/modules/monitoring/application/views/scripts/config/removeinstance.phtml deleted file mode 100644 index 306d41815..000000000 --- a/modules/monitoring/application/views/scripts/config/removeinstance.phtml +++ /dev/null @@ -1,4 +0,0 @@ -

    translate('Remove Existing Instance'); ?>

    -

    translate('Are you sure you want to remove this instance?'); ?>

    -

    translate('If you have still any environments or views referring to this instance, you won\'t be able to send commands anymore after deletion.'); ?>

    - \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/config/security.phtml b/modules/monitoring/application/views/scripts/config/security.phtml deleted file mode 100644 index 71f2a341a..000000000 --- a/modules/monitoring/application/views/scripts/config/security.phtml +++ /dev/null @@ -1,6 +0,0 @@ -
    - tabs ?> -
    -
    - form ?> -
    diff --git a/modules/monitoring/application/views/scripts/config/show-configuration.phtml b/modules/monitoring/application/views/scripts/config/show-configuration.phtml deleted file mode 100644 index 4cd01441c..000000000 --- a/modules/monitoring/application/views/scripts/config/show-configuration.phtml +++ /dev/null @@ -1,28 +0,0 @@ -
    -

    Saving "escape($this->file); ?>" failed

    -
    -

    - Your escape($this->file); ?> configuration couldn't be stored (error: "exceptionMessage; ?>").
    - This could have one or more of the following reasons: -

    -
      -
    • You don't have file-system permissions to write to the escape($this->file); ?> file
    • -
    • Something went wrong while writing the file
    • -
    • There's an application error preventing you from persisting the configuration
    • -
    -
    - -

    - Details can be seen in your application log (if you don't have access to this file, call your administrator in this case). -
    - In case you can access the configuration file (config/escape($this->file); ?>) by yourself, you can open it and - insert the config manually: - -

    -

    -

    -        
    -escape($this->iniConfigurationString); ?>
    -        
    -    
    -

    \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/downtime/remove.phtml b/modules/monitoring/application/views/scripts/downtime/remove.phtml new file mode 100644 index 000000000..57fe36655 --- /dev/null +++ b/modules/monitoring/application/views/scripts/downtime/remove.phtml @@ -0,0 +1,11 @@ +
    + + compact): ?> + tabs; ?> + + + render('partials/downtime/downtime-header.phtml'); ?> +
    +
    + +
    \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/downtime/show.phtml b/modules/monitoring/application/views/scripts/downtime/show.phtml new file mode 100644 index 000000000..4a3583363 --- /dev/null +++ b/modules/monitoring/application/views/scripts/downtime/show.phtml @@ -0,0 +1,121 @@ +
    + compact): ?> + tabs; ?> + + + render('partials/downtime/downtime-header.phtml'); ?> +
    +
    +

    translate('Downtime detail information') ?>

    +
    translate('Instance'); ?> translate('Remove'); ?>
    - - icon('edit'); ?> escape($instanceName); ?> - + qlink( + $instanceName, + '/monitoring/config/editinstance', + array('instance' => $instanceName), + array( + 'icon' => 'edit', + 'title' => sprintf($this->translate('Edit monitoring instance %s'), $instanceName) + ) + ); ?> (translate('Type: %s'), $config->host !== null ? $this->translate('Remote') : $this->translate('Local') ); ?>) - - icon('cancel'); ?> - + qlink( + '', + '/monitoring/config/removeinstance', + array('instance' => $instanceName), + array( + 'icon' => 'trash', + 'title' => sprintf($this->translate('Remove monitoring instance %s'), $instanceName) + ) + ); ?>
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + downtime->is_flexible): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + isService ? $this->translate('Service') : $this->translate('Host') ?> + + isService): ?> + link()->service( + $downtime->service_description, + $downtime->service_display_name, + $downtime->host_name, + $downtime->host_display_name + ); + $icon = $this->icon('service', $this->translate('Service')); + ?> + + icon('host', $this->translate('Host')); + $link = $this->link()->host($downtime->host_name, $downtime->host_display_name) + ?> + + + +
    translate('Author') ?>icon('user', $this->translate('User')) ?> escape($this->downtime->author_name) ?>
    translate('Comment') ?>icon('comment', $this->translate('Comment')) ?> escape($this->downtime->comment) ?>
    translate('Entry Time') ?>formatDateTime($this->downtime->entry_time) ?>
    escape( + $this->downtime->is_flexible ? + $this->translate('Flexible') : $this->translate('Fixed') + ); ?> + escape( + $this->downtime->is_flexible ? + $this->translate('Flexible downtimes have a hard start and end time,' + . ' but also an additional restriction on the duration in which ' + . ' the host or service may actually be down.') : + $this->translate('Fixed downtimes have a static start and end time.') + ); ?> +
    translate('Scheduled start') ?>formatDateTime($this->downtime->scheduled_start) ?>
    translate('Scheduled end') ?>formatDateTime($this->downtime->scheduled_end) ?>
    translate('Duration') ?>formatDuration($this->downtime->duration) ?>
    translate('Actual start time') ?>formatDateTime($downtime->start) ?>
    translate('Actual end time') ?>formatDateTime($downtime->end) ?>
    translate('In effect') ?> + escape( + $this->downtime->is_in_effect ? + $this->translate('Yes') : $this->translate('No') + ); + ?> +
    translate('Commands') ?> + +
    + +
    + diff --git a/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml b/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml new file mode 100644 index 000000000..fed0e12f7 --- /dev/null +++ b/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml @@ -0,0 +1,12 @@ +
    + + compact): ?> + tabs; ?> + + + render('partials/downtime/downtimes-header.phtml'); ?> +
    + +
    + +
    \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/downtimes/show.phtml b/modules/monitoring/application/views/scripts/downtimes/show.phtml new file mode 100644 index 000000000..5adc984fa --- /dev/null +++ b/modules/monitoring/application/views/scripts/downtimes/show.phtml @@ -0,0 +1,23 @@ +
    + + compact): ?> + tabs; ?> + + + render('partials/downtime/downtimes-header.phtml'); ?> +

    +
    +
    +

    translate('Commands') ?>

    + qlink( + sprintf( + $this->translate('Remove all %d scheduled downtimes'), + count($downtimes) + ), + $removeAllLink, + null, + array( + 'icon' => 'trash' + ) + ) ?> +
    diff --git a/modules/monitoring/application/views/scripts/host/history.phtml b/modules/monitoring/application/views/scripts/host/history.phtml new file mode 100644 index 000000000..82b6f752d --- /dev/null +++ b/modules/monitoring/application/views/scripts/host/history.phtml @@ -0,0 +1,168 @@ +qlink( + $contact, + 'monitoring/show/contact', + array('contact_name' => $contact), + array('title' => sprintf($view->translate('Show detailed information about %s'), $contact)) + ); + } + return '[' . implode(', ', $links) . ']'; +} + +$self = $this; + +$url = $this->url(); +$limit = (int) $url->getParam('limit', 25); +if (! $url->hasParam('page') || ($page = (int) $url->getParam('page')) < 1) { + $page = 1; +} + +$history->limit($limit * $page); + +if (! $this->compact): ?> +
    + tabs; ?> + render('partials/host/object-header.phtml'); ?> +

    translate('This Host\'s Event History'); ?>

    + sortBox; ?> + limiter; ?> + + translate('Scroll to the bottom of this page to load additional events'); ?> + + filterEditor; ?> +
    + +
    + + + peekAhead() as $event): ?> + escape($event->output); + $isService = isset($event->service_description); + switch ($event->type) { + case 'notify': + $icon = 'notification'; + $title = $this->translate('Notification'); + $stateClass = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state); + + $msg = $msg ? preg_replace_callback( + '/^\[([^\]]+)\]/', + function($match) use ($self) { return contactsLink($match, $self); }, + $msg + ) : $this->translate('This notification was not sent out to any contact.'); + break; + case 'comment': + $icon = 'comment'; + $title = $this->translate('Comment'); + break; + case 'comment_deleted': + $icon = 'remove'; + $title = $this->translate('Comment deleted'); + break; + case 'ack': + $icon = 'acknowledgement'; + $title = $this->translate('Acknowledge'); + break; + case 'ack_deleted': + $icon = 'remove'; + $title = $this->translate('Ack removed'); + break; + case 'dt_comment': + $icon = 'in_downtime'; + $title = $this->translate('In Downtime'); + break; + case 'dt_comment_deleted': + $icon = 'remove'; + $title = $this->translate('Downtime removed'); + break; + case 'flapping': + $icon = 'flapping'; + $title = $this->translate('Flapping'); + break; + case 'flapping_deleted': + $icon = 'remove'; + $title = $this->translate('Flapping stopped'); + break; + case 'hard_state': + $stateClass = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state); + $icon = 'attention-alt'; + $title = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state); + break; + case 'soft_state': + $icon = 'spinner'; + $stateClass = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state); + $title = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state); + break; + case 'dt_start': + $icon = 'downtime_start'; + $title = $this->translate('Downtime Start'); + break; + case 'dt_end': + $icon = 'downtime_end'; + $title = $this->translate('Downtime End'); + break; + } + ?> + + + + + + +
    + getIteratorPosition() % $limit === 0): ?> + + + escape($title); ?> +
    + timestamp); ?> +
    + + translate('%s on %s', 'Service running on host'), + $this->qlink( + $event->service_display_name, + 'monitoring/show/service', + array( + 'host' => $event->host_name, + 'service' => $event->service_description + ), + array('title' => sprintf( + $this->translate('Show detailed information for service %s on host %s'), + $event->service_display_name, + $event->host_display_name + )) + ), + $event->host_display_name + ) ?> + + escape($event->host_name); ?> + +
    +
    + icon($icon, $title); ?> createTicketLinks($msg) ?> +
    +
    +hasResult()): ?> + translate('No history events found matching the filter'); ?> +hasMore()): ?> +
    qlink( + $this->translate('Load More'), + $url->setAnchor('page-' . ($page + 1)), + array( + 'page' => $page + 1, + ), + array( + 'id' => 'load-more', + 'class' => 'pull-right load-more button-like' + ) + ); ?>
    + +
    diff --git a/modules/monitoring/application/views/scripts/host/services.phtml b/modules/monitoring/application/views/scripts/host/services.phtml new file mode 100644 index 000000000..11166f8fa --- /dev/null +++ b/modules/monitoring/application/views/scripts/host/services.phtml @@ -0,0 +1,18 @@ +
    + compact): ?> + tabs; ?> + + render('partials/host/object-header.phtml') ?> + render('partials/host/servicesummary.phtml') ?> +
    +partial( + 'list/services.phtml', + 'monitoring', + array( + 'compact' => true, + 'showHost' => false, + 'services' => $services, + 'addColumns' => array(), + 'baseTarget' => '_self' + ) +); ?> \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/host/show.phtml b/modules/monitoring/application/views/scripts/host/show.phtml index 8023c1821..a612ae1af 100644 --- a/modules/monitoring/application/views/scripts/host/show.phtml +++ b/modules/monitoring/application/views/scripts/host/show.phtml @@ -1,6 +1,9 @@ -
    - render('show/components/header.phtml') ?> - render('show/components/hostservicesummary.phtml') ?> +
    + compact): ?> + tabs; ?> + + render('partials/host/object-header.phtml') ?> + render('partials/host/servicesummary.phtml') ?>
    render('show/components/output.phtml') ?> @@ -8,17 +11,21 @@ + render('show/components/acknowledgement.phtml') ?> render('show/components/comments.phtml') ?> - render('show/components/notifications.phtml') ?> render('show/components/downtime.phtml') ?> - render('show/components/flapping.phtml') ?> - render('show/components/perfdata.phtml') ?> - render('show/components/checksource.phtml') ?> + render('show/components/notes.phtml') ?> render('show/components/actions.phtml') ?> - render('show/components/command.phtml') ?> + render('show/components/flapping.phtml') ?> render('show/components/hostgroups.phtml') ?> + render('show/components/perfdata.phtml') ?> + + render('show/components/notifications.phtml') ?> render('show/components/contacts.phtml') ?> + + render('show/components/command.phtml') ?> + render('show/components/checksource.phtml') ?> render('show/components/checkstatistics.phtml') ?> render('show/components/customvars.phtml') ?> render('show/components/flags.phtml') ?> diff --git a/modules/monitoring/application/views/scripts/hosts/show.phtml b/modules/monitoring/application/views/scripts/hosts/show.phtml index 6adc300d8..3dbb0673f 100644 --- a/modules/monitoring/application/views/scripts/hosts/show.phtml +++ b/modules/monitoring/application/views/scripts/hosts/show.phtml @@ -1,113 +1,235 @@
    - tabs ?> + compact): ?> + + + + render('list/components/hostssummary.phtml') ?> + render('partials/host/objects-header.phtml'); ?> +
    -
    - - translate('No hosts matching the filter') ?> + +
    +

    + icon('reschedule') ?> + translate('Commands') ?> +

    + +

    + translatePlural( + 'Issue commands to %s selected host:', + 'Issue commands to all %s selected hosts:', + count($objects) + ), '' . count($objects) . '') ?> +

    + + + translate('No hosts found matching the filter'); ?> -
    - translate('Hosts (%u)'), array_sum(array_values($hostStates))) ?> -
    -
    - hostStatesPieChart ?> -
    -
    - $count) { - echo sprintf('%s: %u
    ', strtoupper($text), $count); - } ?> -
    + -

    - translate('%u Hosts'), - count($objects)) - ?> -

    +
    + qlink( + $this->translate('Reschedule next checks'), + $rescheduleAllLink, + null, + array('icon' => 'reschedule') + ); ?> - +
    + qlink( + $this->translate('Schedule downtimes'), + $downtimeAllLink, + null, + array('icon' => 'plug') + ); ?> -
    - -
    +
    + qlink( + $this->translate('Submit passive check results'), + $processCheckResultAllLink, + null, + array('icon' => 'reply') + ); ?> - +
    + qlink( + $this->translate('Add comments'), + $addCommentLink, + null, + array('icon' => 'comment') + ); ?> - + hasPermission('monitoring/command/send-custom-notification')): ?> +
    + qlink( + sprintf($this->translate('Send a custom notification for all %u hosts'), $hostCount), + $sendCustomNotificationLink, + null, + array('icon' => 'comment') + ); ?> + - -

    - translatePlural( - '%u Unhandled Host Problem', - '%u Unhandled Host Problems', - count($unhandledObjects) - ), - count($unhandledObjects) - ) ?> -

    - - - + - -

    + +

    + icon('attention-alt') ?> + translatePlural( + 'Problem', + 'Problems', + $unhandledCount + $problemCount + ) ?> +

    + + +

    translatePlural( - '%u Acknowledged Host Problem', - '%u Acknowledged Host Problems', - count($acknowledgedObjects) + 'There is %s problem.', + 'There are %s problems.', + $problemCount ), - count($acknowledgedObjects) - ) ?> - -

    - -
    - + '' . $problemCount . '' + ); ?> +

    + qlink( + sprintf( + $this->translatePlural( + 'Schedule a downtime for %u problem host', + 'Schedule a downtime for %u problem hosts', + $problemCount + ), + $problemCount + ), + $downtimeLink, + null, + array('icon' => 'plug') + ); ?> + - -

    - - icon('plug') ?> - translate(sprintf('%u hosts are in downtime', count($objectsInDowntime))) ?> - -

    - + 0): ?> +
    + qlink( + sprintf( + $this->translatePlural( + 'Acknowledge %u unacknowledged problem hosts', + 'Acknowledge %u unacknowledged problem hosts', + $unackCount + ), + $unackCount + ), + $acknowledgeLink, + null, + array('icon' => 'ok') + ); ?> + + + +

    + translatePlural( + 'There is %s unhandled problem host, issue commands to the problematic host:', + 'There are %s unhandled problem hosts, issue commands to the problematic hosts:', + $unhandledCount + ), + '' . $unhandledCount . '' + ); ?> +

    + + qlink( + sprintf( + $this->translatePlural( + 'Schedule a downtime for %u unhandled problem host', + 'Schedule a downtime for %u unhandled problem hosts', + $unhandledCount + ), + $unhandledCount + ), + $downtimeUnhandledLink, + null, + array('icon' => 'plug') + ); ?> + + + + + 0): ?> +
    +

    icon('ok', $this->translate('Acknowledgements')) ?> translate('Acknowledgements') ?>

    + + translatePlural( + '%s Acknowledged Host Problem', + '%s Acknowledged Host Problems', + $acknowledgedCount + ), + '' . $acknowledgedCount . '' + ); ?> + + +
    + + + getScheduledDowntimes()) ?> + + +

    icon('plug', $this->translate('Downtimes'))?> translate('Downtimes')?>

    + qlink( + sprintf( + $this->translatePlural( + '%s scheduled downtime', + '%s scheduled downtimes', + $scheduledDowntimeCount + ), + $scheduledDowntimeCount + ), + $showDowntimesLink, + null, + array('data-base-target' => '_next') + );?> + translate('on all selected hosts.')) ?> + + 0): ?> +
    + qlink( + sprintf( + $this->translatePlural( + '%s host', + '%s hosts', + $inDowntimeCount + ), + $inDowntimeCount + ), + $inDowntimeLink, + null, + array('data-base-target' => '_next') + ); ?> + translate('are currently in downtime.') ?> + + + + getComments())) > 0): ?> +

    icon('comment', $this->translate('Comments'))?> translate('Comments') ?>

    + qlink( + sprintf( + $this->translatePlural( + '%s comment', + '%s comments', + $commentCount + ), + $commentCount + ), + $commentsLink, + null, + array('data-base-target' => '_next') + ); ?> + translate('on all selected hosts.') ?> + - getComments())): ?> -

    - - icon('comment') ?> - translate(sprintf('%u comments', count($objects->getComments()))) ?> - -

    - -
    +
    \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/layout/topbar.phtml b/modules/monitoring/application/views/scripts/layout/topbar.phtml deleted file mode 100644 index f7c58db85..000000000 --- a/modules/monitoring/application/views/scripts/layout/topbar.phtml +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/modules/monitoring/application/views/scripts/list/comments.phtml b/modules/monitoring/application/views/scripts/list/comments.phtml index feb5c9394..3287f08e9 100644 --- a/modules/monitoring/application/views/scripts/list/comments.phtml +++ b/modules/monitoring/application/views/scripts/list/comments.phtml @@ -1,74 +1,58 @@ -compact): ?> +compact): ?>
    - tabs->render($this); ?> -
    - translate('Sort by'); ?> sortControl->render($this); ?> + tabs; ?> +
    + render('list/components/selectioninfo.phtml'); ?>
    - widget('limiter', array('url' => $this->url, 'max' => $comments->count())); ?> - paginationControl($comments, null, null, array('preserve' => $this->preserve)); ?> +

    translate('Comments') ?>

    + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
    -
    - - translate('No comments matching the filter') ?> -
    - - -

    translate('Problem handling') ?>

    translate('Notifications') ?>

    translate('Check execution') ?>

    +
    - - type) { - case 'flapping': - $icon = 'flapping'; - $title = $this->translate('Flapping'); - $tooltip = $this->translate('Comment was caused by a flapping host or service.'); - break; - case 'comment': - $icon = 'user'; - $title = $this->translate('User Comment'); - $tooltip = $this->translate('Comment was created by an user.'); - break; - case 'downtime': - $icon = 'plug'; - $title = $this->translate('Downtime'); - $tooltip = $this->translate('Comment was caused by a downtime.'); - case 'ack': - $icon = 'ok'; - $title = $this->translate('Acknowledgement'); - $tooltip = $this->translate('Comment was caused by an acknowledgement.'); - } - ?> + peekAhead($this->compact) as $comment): ?> + +
    - icon($icon, $tooltip) ?> -
    - escape($title); ?> -
    - prefixedTimeSince($comment->timestamp); ?> + partial('partials/comment/comment-description.phtml', array('comment' => $comment)); ?>
    objecttype === 'service'): ?> - icon('conf'); ?> - service; ?> - - - translate('on') . ' ' . $comment->host; ?> - + icon('service', $this->translate('Service')); ?> qlink( + $this->escape($comment->host_display_name) . ': ' . $this->escape($comment->service_display_name), + 'monitoring/comment/show', + array('comment_id' => $comment->id), + array( + 'title' => sprintf( + $this->translate('Show detailed information for this comment about service %s on host %s'), + $comment->service_display_name, + $comment->host_display_name + ), + 'class' => 'rowaction' + ) + ); ?> - icon('host'); ?> - host; ?> - + icon('host', $this->translate('Host')); ?> qlink( + $this->escape($comment->host_display_name), + 'monitoring/comment/show', + array('comment_id' => $comment->id), + array( + 'title' => sprintf( + $this->translate('Show detailed information for this comment about host %s'), + $comment->host_display_name + ) + ) + ); ?>
    - icon('comment'); ?> author) - ? '[' . $comment->author . '] ' + icon('comment', $this->translate('Comment')); ?> author) + ? '[' . $this->escape($comment->author) . '] ' : ''; ?>escape($comment->comment); ?>
    @@ -78,25 +62,39 @@ ?>
    expiration ? sprintf( - $this->translate('This comment expires on %s at %s.'), - date('d.m.y', $comment->expiration), - date('H:i', $comment->expiration) - ) : $this->translate('This comment does not expire.'); ?> + $this->translate('This comment expires %s.'), + $this->timeUntil($comment->expiration) + ) : $this->translate('This comment does not expire.'); ?>
    populate(array('comment_id' => $comment->id, 'redirect' => $this->url)); - if ($comment->objecttype === 'host') { - $delCommentForm->setAction($this->url('monitoring/host/remove-comment', array('host' => $comment->host))); - } else { - $delCommentForm->setAction($this->url('monitoring/service/remove-comment', array('host' => $comment->host, 'service' => $comment->service))); - } + $delCommentForm->populate( + array( + 'comment_id' => $comment->id, + 'comment_is_service' => isset($comment->service_description) + ) + ); echo $delCommentForm; ?>
    +hasResult()): ?> + translate('No comments found matching the filter'); ?> +hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> +
    diff --git a/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml index 2e19ff352..22f84b583 100644 --- a/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml +++ b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml @@ -1,83 +1,168 @@ setQueryString($f->toQueryString()); +} +$this->baseFilter = isset($this->baseFilter) ? $this->baseFilter : null; + +$stats = $stats->fetchRow(); $selfUrl = 'monitoring/list/hosts'; $currentUrl = Url::fromRequest()->getRelativeUrl(); - -?> -

    compact ? ' data-base-target="col1"' : '' ?>> - qlink(sprintf($this->translate('%s hosts:'), $this->stats->hosts_total), $selfUrl); ?> +?>

    compact ? ' data-base-target="col1"' : ''; ?>> + qlink( + sprintf($this->translatePlural('%u Host', '%u Hosts', $stats->hosts_total), $stats->hosts_total), + $selfUrl, + null, + array('title' => sprintf( + $this->translatePlural('List %u host', 'List all %u hosts', $stats->hosts_total), + $stats->hosts_total + )) + ) ?>: - stats->hosts_up): ?> - + hosts_up): ?> + qlink( - $this->stats->hosts_up, - $selfUrl, - array('host_state' => 0), - array('title' => $this->translate('Hosts with state UP')) - ) ?> + $stats->hosts_up, + urlAddFilterOptional( + $selfUrl, + Filter::where('host_state', 0), + $this->baseFilter + ), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UP', + 'List %u hosts which are currently in state UP', + $stats->hosts_up + ), + $stats->hosts_up + ) + ) + ); ?> - stats->hosts_down_unhandled): ?> - + hosts_down_unhandled): ?> + qlink( - $this->stats->hosts_down_unhandled, - $selfUrl, - array('host_state' => 1, 'host_unhandled' => 1), - array('title' => $this->translate('Unhandled hosts with state DOWN')) - ) ?> - - - stats->hosts_down_handled > 0): ?> - - qlink( - $this->stats->hosts_down_handled, + $stats->hosts_down_unhandled, + urlAddFilterOptional( $selfUrl, - array('host_state' => 1, 'host_unhandled' => 0), - array('title' => $this->translate('Handled hosts with state DOWN')) - ) ?> - - - - stats->hosts_down): ?> - + Filter::matchAll(Filter::where('host_state', 1), Filter::where('host_unhandled', 1)), + $this->baseFilter + ), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state DOWN', + 'List %u hosts which are currently in state DOWN', + $stats->hosts_down_unhandled + ), + $stats->hosts_down_unhandled + )) + ); ?> - stats->hosts_unreachable_unhandled): ?> - + hosts_down_handled): ?> + qlink( - $this->stats->hosts_unreachable_unhandled, - $selfUrl, - array('host_state' => 2, 'host_unhandled' => 1), - array('title' => $this->translate('Unhandled hosts with state UNREACHABLE')) - ) ?> - - - stats->hosts_unreachable_handled > 0): ?> - - qlink( - $this->stats->hosts_unreachable_handled, + $stats->hosts_down_handled, + urlAddFilterOptional( $selfUrl, - array('host_state' => 2, 'host_unhandled' => 0), - array('title' => $this->translate('Handled hosts with state UNREACHABLE')) - ) ?> - - - - stats->hosts_unreachable): ?> + Filter::matchAll(Filter::where('host_state', 1), Filter::where('host_unhandled', 0)), + $this->baseFilter + ), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state DOWN (Acknowledged)', + 'List %u hosts which are currently in state DOWN (Acknowledged)', + $stats->hosts_down_handled + ), + $stats->hosts_down_handled + )) + ); ?> - stats->hosts_pending): ?> - + hosts_down): ?> + + + + hosts_unreachable_unhandled): ?> + qlink( - $this->stats->hosts_pending, - $selfUrl, - array('host_state' => 99), - array('title' => $this->translate('Hosts with state PENDING')) - ) ?> + $stats->hosts_unreachable_unhandled, + urlAddFilterOptional( + $selfUrl, + Filter::matchAll(Filter::where('host_state', 2), Filter::where('host_unhandled', 1)), + $this->baseFilter + ), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UNREACHABLE', + 'List %u hosts which are currently in state UNREACHABLE', + $stats->hosts_unreachable_unhandled + ), + $stats->hosts_unreachable_unhandled + )) + ); ?> + + + hosts_unreachable_handled > 0): ?> + + qlink( + $stats->hosts_unreachable_handled, + urlAddFilterOptional( + $selfUrl, + Filter::matchAll(Filter::where('host_state', 2), Filter::where('host_unhandled', 0)), + $this->baseFilter + ), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UNREACHABLE (Acknowledged)', + 'List %u hosts which are currently in state UNREACHABLE (Acknowledged)', + $stats->hosts_unreachable_handled + ), + $stats->hosts_unreachable_handled + )) + ); ?> + + + + hosts_unreachable): ?> + + + + hosts_pending): ?> + + qlink( + $stats->hosts_pending, + urlAddFilterOptional( + $selfUrl, + Filter::where('host_state', 99), + $this->baseFilter + ), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state PENDING', + 'List %u hosts which are currently in state PENDING', + $stats->hosts_pending + ), + $stats->hosts_pending + )) + ); ?> diff --git a/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml b/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml index dab7071a1..9fd798a0d 100644 --- a/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml +++ b/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml @@ -1,34 +1,73 @@ setQueryString($f->toQueryString()); +} +$this->baseFilter = isset($this->baseFilter) ? $this->baseFilter : null; + +$stats = $stats->fetchRow(); $selfUrl = 'monitoring/list/services'; $currentUrl = Url::fromRequest()->getRelativeUrl(); - -?>

    compact ? ' data-base-target="col1"' : '' ?>> -qlink(sprintf($this->translate('%s services:'), $this->stats->services_total), $selfUrl) ?> - -stats->services_ok): ?> - qlink( - $this->stats->services_ok, +?>

    compact ? ' data-base-target="col1"' : ''; ?>> +qlink( + sprintf($this->translatePlural( + '%u Service', '%u Services', $stats->services_total), + $stats->services_total + ), $selfUrl, - array('service_state' => 0), - array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('ok')))) -) ?> + null, + array('title' => sprintf( + $this->translatePlural('List %u service', 'List all %u services', $stats->services_total), + $stats->services_total + )) +) ?>: + +services_ok): ?> + + qlink( + $stats->services_ok, + urlAddFilterOptional($selfUrl, Filter::where('service_state', 0), $this->baseFilter), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state OK', + 'List %u services which are currently in state OK', + $stats->services_ok + ), + $stats->services_ok + )) + ); + ?> + - 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $state) { $pre = 'services_' . $state; - if ($this->stats->$pre) { + if ($stats->$pre) { $handled = $pre . '_handled'; $unhandled = $pre . '_unhandled'; - $paramsHandled = array('service_state' => $stateId, 'service_handled' => 1); - $paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0); - if ($this->stats->$unhandled) { - $compareUrl = Url::fromPath($selfUrl, $paramsUnhandled)->getRelativeUrl(); + $paramsHandled = Filter::matchAll( + Filter::where('service_state', $stateId), + Filter::where('service_handled', 1) + ); + $paramsUnhandled = Filter::matchAll( + Filter::where('service_state', $stateId), + Filter::where('service_handled', 0) + ); + if ($stats->$unhandled) { + $compareUrl = Url::fromPath($selfUrl)->setQueryString($paramsUnhandled->toQueryString())->getRelativeUrl(); } else { - $compareUrl = Url::fromPath($selfUrl, $paramsHandled)->getRelativeUrl(); + $compareUrl = Url::fromPath($selfUrl)->setQueryString($paramsUnhandled->toQueryString())->getRelativeUrl(); } if ($compareUrl === $currentUrl) { @@ -36,50 +75,73 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ } else { $active = ''; } - - echo ''; - if ($this->stats->$unhandled) { + + echo ''; + if ($stats->$unhandled) { echo $this->qlink( - $this->stats->$unhandled, - $selfUrl, - $paramsUnhandled, - array('title' => sprintf($this->translate('Unhandled services with state %s'), strtoupper($this->translate($state)))) + $stats->$unhandled, + urlAddFilterOptional($selfUrl, $paramsUnhandled, $this->baseFilter), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state %s', + 'List %u services which are currently in state %s', + $stats->$unhandled + ), + $stats->$unhandled, + Service::getStateText($stateId, true) + )) ); } - if ($this->stats->$handled) { + if ($stats->$handled) { - if (Url::fromPath($selfUrl, $paramsHandled)->getRelativeUrl() === $currentUrl) { + if (Url::fromPath($selfUrl)->setQueryString($paramsHandled->toQueryString())->getRelativeUrl() === $currentUrl) { $active = ' active'; } else { $active = ''; } - if ($this->stats->$unhandled) { + if ($stats->$unhandled) { echo ''; } echo $this->qlink( - $this->stats->$handled, - $selfUrl, - $paramsHandled, - array('title' => sprintf($this->translate('Handled services with state %s'), strtoupper($this->translate($state)))) + $stats->$handled, + urlAddFilterOptional($selfUrl, $paramsHandled, $this->baseFilter), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state %s (Acknowledged)', + 'List %u services which are currently in state %s (Acknowledged)', + $stats->$handled + ), + $stats->$handled, + Service::getStateText($stateId, true) + )) ); - if ($this->stats->$unhandled) { + if ($stats->$unhandled) { echo "\n"; } } echo "\n"; } } - ?> -stats->services_pending): ?> - qlink( - $this->stats->services_pending, - $selfUrl, - array('service_state' => 99), - array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('pending')))) -) ?> +services_pending): ?> + + qlink( + $stats->services_pending, + urlAddFilterOptional($selfUrl, Filter::where('service_state', 99), $this->baseFilter), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state PENDING', + 'List %u services which are currently in state PENDING', + $stats->services_pending + ), + $stats->services_pending + )) + ); ?> +

    - diff --git a/modules/monitoring/application/views/scripts/list/contactgroups.phtml b/modules/monitoring/application/views/scripts/list/contactgroups.phtml index f25548c52..3030d4224 100644 --- a/modules/monitoring/application/views/scripts/list/contactgroups.phtml +++ b/modules/monitoring/application/views/scripts/list/contactgroups.phtml @@ -1,38 +1,48 @@ -compact): ?> +compact): ?>
    - tabs ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
    -
    - $groupInfo): ?> +if (count($groupData) === 0) { + echo $this->translate('No contactgroups found matching the filter') . '
    '; + return; +} +?> +
    + $groupInfo): ?>
    -

    +
    +

    -

    +

    +
    - - - escape($c->contact_alias) ?> - + array('contact_name' => $c->contact_name), + array('title' => sprintf( + $this->translate('Show detailed information about %s'), + $c->contact_alias + )) + ); ?>

    contact_email): ?> contact_email; ?> contact_pager): ?> -
    +
    contact_pager; ?>

    diff --git a/modules/monitoring/application/views/scripts/list/contacts.phtml b/modules/monitoring/application/views/scripts/list/contacts.phtml index f6face461..3e10197fb 100644 --- a/modules/monitoring/application/views/scripts/list/contacts.phtml +++ b/modules/monitoring/application/views/scripts/list/contacts.phtml @@ -1,36 +1,33 @@ -getHelper('MonitoringState'); -$contactHelper = $this->getHelper('ContactFlags'); -?> +compact): ?>
    - tabs ?> -
    - sortControl->render($this); ?> -
    - paginationControl($contacts, null, null, array('preserve' => $this->preserve)); ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
    - +
    - + peekAhead($this->compact) as $contact): ?>
    - - contact_name ?> (contact_alias ?>) -
    %2$s', - mt('monitoring', 'Email'), - $this->escape($contact->contact_email) - ) ?>
    + array('contact_name' => $contact->contact_name), + array('title' => sprintf( + $this->translate('Show detailed information about %s'), + $contact->contact_alias + )) + ); ?> (contact_alias; ?>) +
    + translate('Email'); ?>: + escape($contact->contact_email); ?> + +
    contact_pager): ?>
    - : + translate('Pager') ?>: escape($contact->contact_pager) ?>
    @@ -38,13 +35,13 @@ $contactHelper = $this->getHelper('ContactFlags');
    contact_notify_service_timeperiod): ?>
    - : + translate('Service notification period') ?>: escape($contact->contact_notify_service_timeperiod) ?>
    contact_notify_host_timeperiod): ?>
    - : + translate('Host notification period') ?>: escape($contact->contact_notify_host_timeperiod) ?>
    @@ -55,4 +52,17 @@ $contactHelper = $this->getHelper('ContactFlags'); if (true): /* The following piece of HTML MUST be nested in
    + hasResult()): ?> + translate('No contacts found matching the filter'); ?> + hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> +
    diff --git a/modules/monitoring/application/views/scripts/list/downtimes.phtml b/modules/monitoring/application/views/scripts/list/downtimes.phtml index a1aba403d..1125aa9ba 100644 --- a/modules/monitoring/application/views/scripts/list/downtimes.phtml +++ b/modules/monitoring/application/views/scripts/list/downtimes.phtml @@ -1,124 +1,159 @@ -compact): ?> +peekAhead($this->compact); + +if (! $this->compact): ?>
    - tabs->render($this); ?> -
    - translate('Sort by'); ?> sortControl->render($this); ?> - filterEditor): ?> - filterPreview ?> - -
    - widget('limiter', array('url' => $this->url, 'max' => $downtimes->count())); ?> - paginationControl($downtimes, null, null, array('preserve' => $this->preserve)); ?> + tabs; ?> +
    + render('list/components/selectioninfo.phtml'); ?> +
    +

    translate('Downtimes') ?>

    + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
    -
    -filterEditor ?> - - translate('No active downtimes'); ?> -
    - - - +
    service)) { - $stateName = strtolower($this->util()->getServiceStateName($downtime->service_state)); + if (isset($downtime->service_description)) { + $isService = true; + $stateName = Service::getStateText($downtime->service_state); } else { - $stateName = strtolower($this->util()->getHostStateName($downtime->host_state)); + $isService = false; + $stateName = Host::getStateText($downtime->host_state); } ?> + +
    + start <= time() && ! $downtime->is_in_effect): ?> + translate('Ends'); ?> +
    + timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, $this->compact) ?> + is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?>
    - dateTimeRenderer( - ($downtime->is_in_effect ? $downtime->end : $downtime->start), - true - )->render( - $this->translate('on %s', 'datetime'), - $this->translate('at %s', 'time'), - $this->translate('in %s', 'timespan') - ); - ?> + timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?> +
    - service)): ?> - - service; ?> - - - translate('on'); ?> host; ?> - + + icon('service', $this->translate('Service')); ?> qlink( + $this->escape($downtime->host_display_name) . ': ' . $this->escape($downtime->service_display_name), + 'monitoring/downtime/show', + array('downtime_id' => $downtime->id), + array( + 'title' => sprintf( + $this->translate('Show detailed information for this downtime scheduled for service %s on host %s'), + $downtime->service_display_name, + $downtime->host_display_name + ), + 'class' => 'rowaction' + ) + ); ?> - - host; ?> - + icon('host', $this->translate('host')); ?> qlink( + $this->escape($downtime->host_display_name), + 'monitoring/downtime/show', + array('downtime_id' => $downtime->id), + array( + 'title' => sprintf( + $this->translate('Show detailed information for this downtime scheduled for host %s'), + $downtime->host_display_name + ), + 'class' => 'rowaction' + ) + ); ?>
    - icon('comment'); ?> [author; ?>] comment; ?> + icon('comment', $this->translate('Comment')); ?> [escape($downtime->author_name) ?>] escape($downtime->comment) ?>
    is_flexible): ?> is_in_effect): ?> translate('This flexible downtime was started on %s at %s and lasts for %s until %s at %s.'), - date('d.m.y', $downtime->start), - date('H:i', $downtime->start), - $this->format()->duration($downtime->duration), - date('d.m.y', $downtime->end), - date('H:i', $downtime->end) + $isService + ? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.') + : $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'), + $this->formatDate($downtime->start), + $this->formatTime($downtime->start), + $this->formatDuration($downtime->duration), + $this->formatDate($downtime->end), + $this->formatTime($downtime->end) ); ?> translate('This flexible downtime has been scheduled to start between %s - %s and to last for %s.'), - date('d.m.y H:i', $downtime->scheduled_start), - date('d.m.y H:i', $downtime->scheduled_end), - $this->format()->duration($downtime->duration) + $isService + ? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.') + : $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'), + $this->formatDateTime($downtime->scheduled_start), + $this->formatDateTime($downtime->scheduled_end), + $this->formatDuration($downtime->duration) ); ?> is_in_effect): ?> translate('This fixed downtime was started on %s at %s and expires on %s at %s.'), - date('d.m.y', $downtime->start), - date('H:i', $downtime->start), - date('d.m.y', $downtime->end), - date('H:i', $downtime->end) + $isService + ? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.') + : $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'), + $this->formatDate($downtime->start), + $this->formatTime($downtime->start), + $this->formatDate($downtime->end), + $this->formatTime($downtime->end) ); ?> translate('This fixed downtime has been scheduled to start on %s at %s and to end on %s at %s.'), - date('d.m.y', $downtime->scheduled_start), - date('H:i', $downtime->scheduled_start), - date('d.m.y', $downtime->scheduled_end), - date('H:i', $downtime->scheduled_end) + $isService + ? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.') + : $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'), + $this->formatDate($downtime->start), + $this->formatTime($downtime->start), + $this->formatDate($downtime->end), + $this->formatTime($downtime->end) ); ?>
    populate(array('downtime_id' => $downtime->id, 'redirect' => $this->url)); - if (! isset($downtime->service)) { - $delDowntimeForm->setAction($this->url('monitoring/host/delete-downtime', array('host' => $downtime->host))); - } else { - $delDowntimeForm->setAction($this->url('monitoring/service/delete-downtime', array('host' => $downtime->host, 'service' => $downtime->service))); - } + $delDowntimeForm->populate( + array( + 'downtime_id' => $downtime->id, + 'downtime_is_service' => isset($downtime->service_description) + ) + ); echo $delDowntimeForm; ?>
    +hasResult()): ?> + translate('No downtimes found matching the filter, maybe the downtime already expired.'); ?> +hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> +
    diff --git a/modules/monitoring/application/views/scripts/list/eventgrid.phtml b/modules/monitoring/application/views/scripts/list/eventgrid.phtml index 53056daac..37024b841 100644 --- a/modules/monitoring/application/views/scripts/list/eventgrid.phtml +++ b/modules/monitoring/application/views/scripts/list/eventgrid.phtml @@ -1,80 +1,68 @@ - - - -
    - tabs->render($this); ?> -
    - -
    -
    - +if (! $this->compact): ?> +
    + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?> + +
    +
    array( - 'tooltip' => mt('monitoring', '%d ok on %s'), + 'tooltip' => $this->translate('%d hosts ok on %s'), 'color' => '#49DF96', 'opacity' => '0.55' ), 'cnt_unreachable_hard' => array( - 'tooltip' => mt('monitoring', '%d unreachable on %s'), + 'tooltip' => $this->translate('%d hosts unreachable on %s'), 'color' => '#77AAFF', 'opacity' => '0.55' ), 'cnt_critical_hard' => array( - 'tooltip' => mt('monitoring', '%d critical on %s'), + 'tooltip' => $this->translate('%d services critical on %s'), 'color' => '#ff5566', 'opacity' => '0.9' ), 'cnt_warning_hard' => array( - 'tooltip' => mt('monitoring', '%d warning on %s'), + 'tooltip' => $this->translate('%d services warning on %s'), 'color' => '#ffaa44', 'opacity' => '1.0' ), 'cnt_down_hard' => array( - 'tooltip' => mt('monitoring', '%d down on %s'), + 'tooltip' => $this->translate('%d hosts down on %s'), 'color' => '#ff5566', 'opacity' => '0.9' ), 'cnt_unknown_hard' => array( - 'tooltip' => mt('monitoring', '%d unknown on %s'), + 'tooltip' => $this->translate('%d services unknown on %s'), 'color' => '#cc77ff', 'opacity' => '0.7' ), 'cnt_ok' => array( - 'tooltip' => mt('monitoring', '%d ok on %s'), + 'tooltip' => $this->translate('%d services ok on %s'), 'color' => '#49DF96', 'opacity' => '0.55' ) ); -$from = intval($form->getValue('from', strtotime('3 months ago'))); -$to = intval($form->getValue('to', time())); - -// don't display more than ten years, or else this will get really slow -if ($to - $from > 315360000) { - $from = $to - 315360000; -} $data = array(); - -if (count($summary) === 0) { - echo mt('monitoring', 'No state changes in the selected time period.'); -} foreach ($summary as $entry) { $day = $entry->day; $value = $entry->$column; $caption = sprintf( $settings[$column]['tooltip'], $value, - $this->dateFormat()->formatDate(strtotime($day)) + $this->formatDate(strtotime($day)) ); $linkFilter = Filter::matchAll( Filter::expression('timestamp', '<', strtotime($day . ' 23:59:59')), @@ -89,6 +77,19 @@ foreach ($summary as $entry) { ); } +if (! $summary->hasResult()) { + echo $this->translate('No state changes in the selected time period.') . '
    '; + return; +} + +$from = intval($form->getValue('from', strtotime('3 months ago'))); +$to = intval($form->getValue('to', time())); + +// don't display more than ten years, or else this will get really slow +if ($to - $from > 315360000) { + $from = $to - 315360000; +} + $f = new DateTime(); $f->setTimestamp($from); $t = new DateTime(); @@ -116,11 +117,11 @@ for ($i = 0; $i < $diff->days; $i += $step) { } ?>
    - $grid) { ?> + $grid): ?>
    orientation === 'horizontal' ? '
    ' : '' ?>
    - +
    diff --git a/modules/monitoring/application/views/scripts/list/eventhistory.phtml b/modules/monitoring/application/views/scripts/list/eventhistory.phtml index 32f61ca44..8cf9dadb9 100644 --- a/modules/monitoring/application/views/scripts/list/eventhistory.phtml +++ b/modules/monitoring/application/views/scripts/list/eventhistory.phtml @@ -1,124 +1,133 @@ -compact): ?> +url(); +$limit = (int) $url->getParam('limit', 25); +if (! $url->hasParam('page') || ($page = (int) $url->getParam('page')) < 1) { + $page = 1; +} + +$history->limit($limit * $page); + +if (! $this->compact): ?>
    - tabs->render($this); ?> -
    - translate('Sort by'); ?> sortControl->render($this); ?> -
    - - widget('limiter', array('url' => $this->url, 'max' => $this->history->count())); ?> - paginationControl($history, null, null, array('preserve' => $this->preserve)); ?> - + tabs; ?> + sortBox; ?> + limiter; ?> + + translate('Scroll to the bottom of this page to load additional events'); ?> + + filterEditor; ?>
    -
    - - translate('No history events matching the filter') ?> -
    - - - + peekAhead() as $event): ?> output; $title = $event->type; $stateName = 'invalid'; - $isService = isset($event->service); + $isService = isset($event->service_description); switch ($event->type) { case 'notify': $icon = 'bell'; $title = $this->translate('Notification'); - $msg = $event->output; + $msg = $msg ?: $this->translate('This notification was not sent out to any contact.'); break; case 'comment': $icon = 'comment'; $title = $this->translate('Comment'); - $msg = $event->output; break; case 'ack': $icon = 'ok'; $title = $this->translate('Acknowledgement'); - $msg = $event->output; break; case 'dt_comment': $icon = 'plug'; $title = $this->translate('In Downtime'); - $msg = $event->output; break; case 'flapping': $icon = 'flapping'; $title = $this->translate('Flapping'); - $msg = $event->output; break; case 'flapping_deleted': $icon = 'ok'; $title = $this->translate('Flapping Stopped'); - $msg = $event->output; break; case 'hard_state': $icon = $isService ? 'service' : 'host'; - $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output; - $stateName = ( - $isService - ? strtolower($this->util()->getServiceStateName($event->state)) - : strtolower($this->util()->getHostStateName($event->state)) - ); - $title = strtoupper($stateName); // TODO: Should be translatable! + $stateName = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state); + $title = $isService ? Service::getStateText($event->state, true) : Host::getStateText($event->state, true); break; case 'soft_state': $icon = 'lightbulb'; - $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output; - $stateName = ( - $isService - ? strtolower($this->util()->getServiceStateName($event->state)) - : strtolower($this->util()->getHostStateName($event->state)) - ); - $title = strtoupper($stateName); // TODO: Should be translatable! + $stateName = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state); + $title = $isService ? Service::getStateText($event->state, true) : Host::getStateText($event->state, true); break; case 'dt_start': $icon = 'starttime'; $title = $this->translate('Downtime Start'); - $msg = $event->output; break; case 'dt_end': $icon = 'endtime'; $title = $this->translate('Downtime End'); - $msg = $event->output; break; } ?>
    + getIteratorPosition() % $limit === 0): ?> + + escape($title); ?>
    - timestamp); ?> + timeAgo($event->timestamp, $this->compact); ?>
    - - service; ?> - - - translate('on') . ' ' . $event->host; ?> - + link()->service( + $event->service_description, $event->service_display_name, $event->host_name, $event->host_display_name, 'rowaction' + ) ?> - - host; ?> - + link()->host($event->host_name, $event->host_display_name) ?>
    - icon($icon, $title); ?> + icon($icon, $title); ?> escape($msg) ?>
    +hasResult()): ?> + translate('No history events found matching the filter'); ?> +hasMore()): ?> + compact): ?> + qlink( + $this->translate('Show More'), + $url->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> + +
    qlink( + $this->translate('Load More'), + $url->setAnchor('page-' . ($page + 1)), + array( + 'page' => $page + 1, + ), + array( + 'id' => 'load-more', + 'class' => 'pull-right load-more button-like' + ) + ); ?>
    + +
    diff --git a/modules/monitoring/application/views/scripts/list/hostgroups.phtml b/modules/monitoring/application/views/scripts/list/hostgroups.phtml index 2b8ad6c4f..1e0b251ba 100644 --- a/modules/monitoring/application/views/scripts/list/hostgroups.phtml +++ b/modules/monitoring/application/views/scripts/list/hostgroups.phtml @@ -1,135 +1,330 @@ -compact): ?> -
    - +compact): ?>
    - tabs ?> -
    - translate('Sort by'); ?> sortControl->render($this); ?> -
    - widget('limiter')->setMaxLimit(count($hostgroups)); ?> - paginationControl($hostgroups, null, null, array('preserve' => $this->preserve)); ?> -
    -
    + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> filterEditor; ?> +
    - translate('No host groups matching the filter'); - echo '
    '; - return; - } - ?> - +
    +peekAhead($this->compact); +$firstRow = true; +foreach ($hostgroups as $h): ?> + + +
    + + - - - services_critical_last_state_change_unhandled): ?> - + - services_unknown_last_state_change_unhandled): ?> - - services_warning_last_state_change_unhandled): ?> - - services_critical_last_state_change_handled): ?> - - services_unknown_last_state_change_handled): ?> - - services_warning_last_state_change_handled): ?> - - services_ok_last_state_change): ?> - - - - - + + - + +hasResult()): ?>
    translate('Last Problem'); ?> translate('Host Group'); ?>translate('Total Hosts'); ?>translate('Host States'); ?> translate('Total Services'); ?> translate('Service States'); ?>
    - translate('CRITICAL'); ?> + +hosts_down_unhandled) { + $handled = false; + $state = Host::STATE_DOWN; + $lastStateChange = $h->hosts_down_last_state_change_unhandled; +} elseif ($h->hosts_unreachable_unhandled) { + $handled = false; + $state = Host::STATE_UNREACHABLE; + $lastStateChange = $h->hosts_unreachable_last_state_change_unhandled; +} else { + $handled = true; + if ($h->hosts_down_handled) { + $state = Host::STATE_DOWN; + $lastStateChange = $h->hosts_down_last_state_change_handled; + } elseif ($h->hosts_unreachable_handled) { + $state = Host::STATE_UNREACHABLE; + $lastStateChange = $h->hosts_unreachable_last_state_change_handled; + } elseif ($h->hosts_up) { + $state = Host::STATE_UP; + $lastStateChange = $h->hosts_up_last_state_change; + } else { + $state = Host::STATE_PENDING; + $lastStateChange = $h->hosts_pending_last_state_change; + } +} + +?> +
    +
    - prefixedTimeSince($h->services_critical_last_state_change_unhandled); ?> + timeSince($lastStateChange, $this->compact); ?>
    - translate('UNKNOWN'); ?> -
    - prefixedTimeSince($h->services_unknown_last_state_change_unhandled); ?> -
    - translate('WARNING'); ?> -
    - prefixedTimeSince($h->services_warning_last_state_change_unhandled); ?> -
    - translate('CRITICAL'); ?> -
    - prefixedTimeSince($h->services_critical_last_state_change_handled); ?> -
    - translate('UNKNOWN'); ?> -
    - prefixedTimeSince($h->services_unknown_last_state_change_handled); ?> -
    - translate('WARNING'); ?> -
    - prefixedTimeSince($h->services_warning_last_state_change_handled); ?> -
    - translate('OK'); ?> -
    - prefixedTimeSince($h->services_ok_last_state_change); ?> -
    - translate('PENDING'); ?> -
    - prefixedTimeSince($h->services_pending_last_state_change); ?> -
    - - hostgroup; ?> - + qlink( + $h->hostgroup_alias, + 'monitoring/list/hosts', + array('hostgroup_name' => $h->hostgroup_name), + array('title' => sprintf($this->translate('List all hosts in the group "%s"'), $h->hostgroup_alias)) + ); ?> - services_total; ?> + qlink( + $h->hosts_total, + 'monitoring/list/hosts', + array('hostgroup_name' => $h->hostgroup_name), + array('title' => sprintf( + $this->translate('List all hosts in host group "%s"'), + $h->hostgroup_alias + )) + ); ?> + + hosts_up): ?> + + qlink( + $h->hosts_up, + 'monitoring/list/hosts', + array( + 'host_state' => 0, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UP in the host group "%s"', + 'List %u hosts which are currently in state UP in the host group "%s"', + $h->hosts_up + ), + $h->hosts_up, + $h->hostgroup_alias + ) + ) + ); ?> + + + hosts_down_unhandled): ?> + + qlink( + $h->hosts_down_unhandled, + 'monitoring/list/hosts', + array( + 'host_state' => 1, + 'host_acknowledged' => 0, + 'host_in_downtime' => 0, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state DOWN in the host group "%s"', + 'List %u hosts which are currently in state DOWN in the host group "%s"', + $h->hosts_down_unhandled + ), + $h->hosts_down_unhandled, + $h->hostgroup_alias + ) + ) + ); ?> + + hosts_down_handled): ?> + + qlink( + $h->hosts_down_handled, + 'monitoring/list/hosts', + array( + 'host_state' => 1, + 'host_handled' => 1, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state DOWN (Acknowledged) in the host group "%s"', + 'List %u hosts which are currently in state DOWN (Acknowledged) in the host group "%s"', + $h->hosts_down_handled + ), + $h->hosts_down_handled, + $h->hostgroup_alias + ) + ) + ); ?> + + + hosts_down_unhandled): ?> + + + hosts_unreachable_unhandled): ?> + + qlink( + $h->hosts_unreachable_unhandled, + 'monitoring/list/hosts', + array( + 'host_state' => 2, + 'host_acknowledged' => 0, + 'host_in_downtime' => 0, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UNREACHABLE in the host group "%s"', + 'List %u hosts which are currently in state UNREACHABLE in the host group "%s"', + $h->hosts_unreachable_unhandled + ), + $h->hosts_unreachable_unhandled, + $h->hostgroup_alias + ) + ) + ); ?> + + hosts_unreachable_handled): ?> + + qlink( + $h->hosts_unreachable_handled, + 'monitoring/list/hosts', + array( + 'host_state' => 2, + 'host_handled' => 1, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"', + 'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"', + $h->hosts_unreachable_handled + ), + $h->hosts_unreachable_handled, + $h->hostgroup_alias + ) + ) + ); ?> + + + hosts_unreachable_unhandled): ?> + + + hosts_pending): ?> + + qlink( + $h->hosts_pending, + 'monitoring/list/hosts', + array( + 'host_state' => 99, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state PENDING in the host group "%s"', + 'List %u hosts which are currently in state PENDING in the host group "%s"', + $h->hosts_pending + ), + $h->hosts_pending, + $h->hostgroup_alias + ) + ) + ); ?> + + + + qlink( + $h->services_total, + 'monitoring/list/services', + array('hostgroup_name' => $h->hostgroup_name), + array('title' => sprintf( + $this->translate('List all services of all hosts in host group "%s"'), + $h->hostgroup_alias + )) + ); ?> + services_ok): ?> - services_ok + ), + $h->services_ok, + $h->hostgroup_alias + ) ) - ); ?>" title="translate('Services OK'); ?>"> - services_ok; ?> - + ); ?> services_critical_unhandled): ?> - services_critical_unhandled + ), + $h->services_critical_unhandled, + $h->hostgroup_alias + ) ) - ); ?>" title="translate('Services CRITICAL Unhandled'); ?>"> - services_critical_unhandled; ?> - + ); ?> services_critical_handled): ?> - services_critical_handled + ), + $h->services_critical_handled, + $h->hostgroup_alias + ) ) - ); ?>" title="translate('Services CRITICAL Handled'); ?>"> - services_critical_handled; ?> - + ); ?> services_critical_unhandled): ?> @@ -137,33 +332,53 @@ services_unknown_unhandled): ?> - services_unknown_unhandled + ), + $h->services_unknown_unhandled, + $h->hostgroup_alias + ) ) - ); ?>" title="translate('Services UNKNOWN Unhandled'); ?>"> - services_unknown_unhandled; ?> - + ); ?> services_unknown_handled): ?> - services_unknown_handled + ), + $h->services_unknown_handled, + $h->hostgroup_alias + ) ) - ); ?>" title="translate('Services UNKNOWN Handled'); ?>"> - services_unknown_handled; ?> - + ); ?> services_unknown_unhandled): ?> @@ -171,33 +386,53 @@ services_warning_unhandled): ?> - services_warning_unhandled + ), + $h->services_warning_unhandled, + $h->hostgroup_alias + ) ) - ); ?>" title="translate('Services WARNING Unhandled'); ?>"> - services_warning_unhandled; ?> - + ); ?> services_warning_handled): ?> - services_warning_handled + ), + $h->services_warning_handled, + $h->hostgroup_alias + ) ) - ); ?>" title="translate('Services WARNING Handled'); ?>"> - services_warning_handled; ?> - + ); ?> services_warning_unhandled): ?> @@ -205,21 +440,46 @@ services_pending): ?> - services_pending + ), + $h->services_pending, + $h->hostgroup_alias + ) ) - ); ?>" title="translate('Services PENDING'); ?>"> - services_pending; ?> - + ); ?>
    -
    \ No newline at end of file +hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> + + + translate('No hostgroups found matching the filter'); ?> + +
    diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml index 588d39712..62ca65121 100644 --- a/modules/monitoring/application/views/scripts/list/hosts.phtml +++ b/modules/monitoring/application/views/scripts/list/hosts.phtml @@ -1,37 +1,22 @@ getHelper('MonitoringState'); +use Icinga\Module\Monitoring\Object\Host; -if ($this->compact): ?> -
    - -
    - tabs ?> -
    - render('list/components/selectioninfo.phtml') ?> - render('list/components/hostssummary.phtml') ?> - translate('Sort by') ?> sortControl->render($this) ?> +$hosts->peekAhead($this->compact); + +if (! $this->compact): ?> +
    + tabs; ?> +
    + render('list/components/selectioninfo.phtml'); ?> + render('list/components/hostssummary.phtml'); ?>
    - -widget('limiter')->setMaxLimit($this->hosts->count()) ?> -paginationControl($hosts, null, null, array('preserve' => $this->preserve)) ?> -selectionToolbar('multi', $this->href('monitoring/hosts/show?' . $this->filter->toQueryString())) ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
    - +
    -filterEditor ?> -count() === 0) { - echo $this->translate('No hosts matching the filter'); - if (! $this->compact) { - echo '
    '; - } - return; -} -?> - count() === 0) { util()->getHostStateName($host->host_state)); + $hostStateName = Host::getStateText($host->host_state); $hostLink = $this->href('monitoring/host/show', array('host' => $host->host_name)); - $icons = array(); - if (! $host->host_handled && $host->host_state > 0){ - $icons[] = $this->icon('attention-alt', $this->translate('Unhandled')); - } - if ($host->host_acknowledged) { - $icons[] = $this->icon('ok', $this->translate('Acknowledged')); - } - - if ($host->host_is_flapping) { - $icons[] = $this->icon('flapping', $this->translate('Flapping')); - } - - if (! $host->host_notifications_enabled) { - $icons[] = $this->icon('bell-off-empty', $this->translate('Notifications Disabled')); - } - - if ($host->host_in_downtime) { - $icons[] = $this->icon('plug', $this->translate('In Downtime')); - } - - if (! $host->host_active_checks_enabled) { - if (! $host->host_passive_checks_enabled) { - $icons[] = $this->icon('eye-off', $this->translate('Active And Passive Checks Disabled')); - } else { - $icons[] = $this->icon('eye-off', $this->translate('Active Checks Disabled')); - } - } - - if (isset($host->host_last_comment) && $host->host_last_comment !== null) { - $icons[] = $this->icon('comment', $this->translate('Comment: ') . $host->host_last_comment); - } ?> - - extraColumns as $col): ?> + addColumns as $col): ?>
    - monitoringState($host, 'host')) ?>
    +
    + host_state, true); ?> host_state !== 99): ?> - prefixedTimeSince($host->host_last_state_change, true) ?> - host_state > 0 && (int) $host->host_state_type === 0): ?> -
    - Soft host_attempt ?> - +
    + timeSince($host->host_last_state_change, $this->compact) ?> + host_state > 0 && (int) $host->host_state_type === 0): ?> +
    + Soft host_attempt ?> +
    - host_icon_image && ! preg_match('/[\'"]/', $host->host_icon_image)): ?> - icon($this->resolveMacros($host->host_icon_image, $host)) ?> - - - host_name ?> - host_unhandled_services) && $host->host_unhandled_services > 0): ?> - (qlink( - sprintf( - $this->translatePlural('%d unhandled service', '%d unhandled services', $host->host_unhandled_services), - $host->host_unhandled_services), - 'monitoring/show/services', - array( - 'host' => $host->host_name, - 'service_problem' => 1, - 'service_acknowledged' => 0, - 'service_in_downtime' => 0 - ), - array('style' => 'font-weight: normal') + iconImage()->host($host) ?> + hostFlags($host)) ?> + qlink( + $host->host_display_name, + $hostLink, + null, + array( + 'title' => sprintf($this->translate('Show detailed information for host %s'), $host->host_display_name), + 'class' => 'rowaction' + ) + ); ?> + host_name])): ?> + (qlink( + sprintf( + $this->translatePlural('%u unhandled service', '%u unhandled services', $summary[$host->host_name]), + $summary[$host->host_name] + ), + 'monitoring/list/services', + array( + 'host' => $host->host_name, + 'service_problem' => 1, + 'service_handled' => 0 + ), + array( + 'style' => 'font-weight: normal', + 'title' => sprintf( + $this->translatePlural( + 'List %s unhandled service problem on host %s', + 'List %s unhandled service problems on host %s', + $summary[$host->host_name] + ), + $summary[$host->host_name], + $host->host_name + ) + ) ) ?>) -

    escape(substr(strip_tags($host->host_output), 0, 10000)) ?>

    +

    pluginOutput($this->ellipsis($host->host_output, 10000), true) ?>

    escape($host->$col) ?>
    +hasResult()): ?> + translate('No hosts found matching the filter'); ?> +hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> +
    diff --git a/modules/monitoring/application/views/scripts/list/notifications.phtml b/modules/monitoring/application/views/scripts/list/notifications.phtml index 365d32f56..30b3c5875 100644 --- a/modules/monitoring/application/views/scripts/list/notifications.phtml +++ b/modules/monitoring/application/views/scripts/list/notifications.phtml @@ -1,82 +1,82 @@ -compact): ?> +peekAhead($this->compact); + +if (! $this->compact): ?>
    - tabs ?> -
    - translate('Sort by') ?> sortControl->render($this) ?> -
    - widget('limiter') ?> - paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?> -
    - - -inline): ?> -
    - - -notifications)) { - echo 'No notifications yet
    '; - return; -} -?> - - - -service)) { - $isService = true; - $href = $this->href('monitoring/show/service', array( - 'host' => $notification->host, - 'service' => $notification->service - )); - $stateName = strtolower($this->util()->getServiceStateName($notification->notification_state)); - } else { - $isService = false; - $href = $this->href('monitoring/show/host', array( - 'host' => $notification->host - )); - $stateName = strtolower($this->util()->getHostStateName($notification->notification_state)); - } -?> - - - - - - -
    - dateTimeRenderer($notification->notification_start_time)->render( - $this->translate('on %s', 'datetime'), - $this->translate('at %s', 'time'), - $this->translate('%s ago', 'timespan') - ); - ?> - - - service ?> on host ?> - - host ?> - -
    - escape(substr(strip_tags($notification->notification_output), 0, 10000)); ?> -
    - contact): ?> - - Sent to escape($notification->notification_contact) ?> - - -
    - -inline): ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?> +
    + +
    + + + service_description)) { + $isService = true; + $stateName = Service::getStateText($notification->notification_state); + } else { + $isService = false; + $stateName = Host::getStateText($notification->notification_state); + } + ?> + + + + + + +
    + timeAgo($notification->notification_start_time, $this->compact) ?> + + + icon('service', $this->translate('Service')); ?> + link()->service( + $notification->service_description, + $notification->service_display_name, + $notification->host_name, + $notification->host_display_name + ) ?> + + icon('host', $this->translate('Host')); ?> + link()->host($notification->host_name, $notification->host_display_name) ?> + +
    + pluginOutput($this->ellipsis($notification->notification_output, 10000), true) ?> +
    + contact): ?> + + notification_contact_name): ?> + translate('Sent to %s'), + $this->qlink( + $notification->notification_contact_name, + 'monitoring/show/contact', + array('contact_name' => $notification->notification_contact_name) + ) + ) ?> + + translate('This notification was not sent out to any contact.'); ?> + + + +
    +hasResult()): ?> + translate('No notifications found matching the filter'); ?> +hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> +
    - diff --git a/modules/monitoring/application/views/scripts/list/servicegrid.phtml b/modules/monitoring/application/views/scripts/list/servicegrid.phtml index 19aef5d64..73fbfe3b5 100644 --- a/modules/monitoring/application/views/scripts/list/servicegrid.phtml +++ b/modules/monitoring/application/views/scripts/list/servicegrid.phtml @@ -1,23 +1,28 @@ -compact): ?> +compact): ?>
    tabs; ?> -
    - translate('Sort by'); ?> sortControl; ?> -
    + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
    - pivot->toArray(); +if (count($pivotData) === 0) { + echo $this->translate('No services found matching the filter') . ''; + return; +} + $hostFilter = '(host_name=' . implode('|host_name=', array_keys($pivotData)) . ')'; ?> - - - translate('No Services matching the filter'); ?> - - +
    $serviceStates): ?> @@ -32,18 +37,23 @@ $hostFilter = '(host_name=' . implode('|host_name=', array_keys($pivotData)) . ' ); ?> - + diff --git a/modules/monitoring/application/views/scripts/list/servicegroups.phtml b/modules/monitoring/application/views/scripts/list/servicegroups.phtml index b7898de05..eab74364a 100644 --- a/modules/monitoring/application/views/scripts/list/servicegroups.phtml +++ b/modules/monitoring/application/views/scripts/list/servicegroups.phtml @@ -1,25 +1,21 @@ -compact): ?> -
    - +compact): ?>
    - tabs ?> -
    - translate('Sort by'); ?> sortControl->render($this); ?> -
    - widget('limiter')->setMaxLimit(count($servicegroups)); ?> - paginationControl($servicegroups, null, null, array('preserve' => $this->preserve)); ?> -
    -
    + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> filterEditor; ?> +
    - translate('No service groups matching the filter'); - echo '
    '; - return; - } - ?> -
    - + - - - 18 ? substr($service_description, 0, 18) . '...' : $service_description; ?> - - + ), + array( + 'title' => sprintf($this->translate('List all services with the name "%s" on all reported hosts'), $service_description) + ), + false + ); ?>
    @@ -55,18 +65,40 @@ $hostFilter = '(host_name=' . implode('|host_name=', array_keys($pivotData)) . '
    - + qlink( + $host_name, + 'monitoring/list/services?' . $serviceFilter, + array('host' => $host_name), + array('title' => sprintf($this->translate('List all reported services on host %s'), $host_name)) + ); ?> - + + escape($service->service_output); ?> + + qlink( + '', + 'monitoring/show/service', + array( + 'host' => $service->host_name, + 'service' => $service->service_description + ), + array( + 'aria-describedby' => $service->host_name . '_' . $service->service_description . '_desc', + 'class' => 'state_' . Service::getStateText($service->service_state). ($service->service_handled ? ' handled' : ''), + 'title' => $this->escape($service->service_output), + 'aria-label' => sprintf( + $this->translate('Show detailed information for service %s on host %s'), + $service->service_description, + $service->host_name + ) + ) + ); ?> ·
    +
    +peekAhead($this->compact); +$firstRow = true; +foreach ($servicegroups as $s): ?> + + +
    @@ -27,61 +23,64 @@ - - + + services_critical_last_state_change_unhandled): ?> services_unknown_last_state_change_unhandled): ?> services_warning_last_state_change_unhandled): ?> services_critical_last_state_change_handled): ?> services_unknown_last_state_change_handled): ?> services_warning_last_state_change_handled): ?> services_ok_last_state_change): ?> - + +hasResult()): ?>
    translate('Last Problem'); ?> translate('Service Group'); ?>translate('Service States'); ?>
    translate('CRITICAL'); ?>
    - prefixedTimeSince($s->services_critical_last_state_change_unhandled); ?> + timeSince($s->services_critical_last_state_change_unhandled); ?>
    translate('UNKNOWN'); ?>
    - prefixedTimeSince($s->services_unknown_last_state_change_unhandled); ?> + timeSince($s->services_unknown_last_state_change_unhandled); ?>
    translate('WARNING'); ?>
    - prefixedTimeSince($s->services_warning_last_state_change_unhandled); ?> + timeSince($s->services_warning_last_state_change_unhandled); ?>
    translate('CRITICAL'); ?>
    - prefixedTimeSince($s->services_critical_last_state_change_handled); ?> + timeSince($s->services_critical_last_state_change_handled); ?>
    translate('UNKNOWN'); ?>
    - prefixedTimeSince($s->services_unknown_last_state_change_handled); ?> + timeSince($s->services_unknown_last_state_change_handled); ?>
    translate('WARNING'); ?>
    - prefixedTimeSince($s->services_warning_last_state_change_handled); ?> + timeSince($s->services_warning_last_state_change_handled); ?>
    translate('OK'); ?>
    - prefixedTimeSince($s->services_ok_last_state_change); ?> + timeSince($s->services_ok_last_state_change); ?>
    translate('PENDING'); ?>
    - prefixedTimeSince($s->services_pending_last_state_change); ?> + timeSince($s->services_pending_last_state_change); ?>
    - - servicegroup; ?> - + qlink( + $s->servicegroup_alias, + 'monitoring/list/services', + array('servicegroup_name' => $s->servicegroup_name), + array('title' => sprintf($this->translate('List all services in the group "%s"'), $s->servicegroup_alias)) + ); ?> services_total; ?> @@ -89,47 +88,77 @@ services_ok): ?> - services_ok + ), + $s->services_ok, + $s->servicegroup_alias + ) ) - ); ?>" title="translate('Services OK'); ?>"> - services_ok; ?> - + ); ?> services_critical_unhandled): ?> - services_critical_unhandled + ), + $s->services_critical_unhandled, + $s->servicegroup_alias + ) ) - ); ?>" title="translate('Services CRITICAL Unhandled'); ?>"> - services_critical_unhandled; ?> - + ); ?> services_critical_handled): ?> - services_critical_handled + ), + $s->services_critical_handled, + $s->servicegroup_alias + ) ) - ); ?>" title="translate('Services CRITICAL Handled'); ?>"> - services_critical_handled; ?> - + ); ?> services_critical_unhandled): ?> @@ -137,33 +166,53 @@ services_unknown_unhandled): ?> - services_unknown_unhandled + ), + $s->services_unknown_unhandled, + $s->servicegroup_alias + ) ) - ); ?>" title="translate('Services UNKNOWN Unhandled'); ?>"> - services_unknown_unhandled; ?> - + ); ?> services_unknown_handled): ?> - services_unknown_handled + ), + $s->services_unknown_handled, + $s->servicegroup_alias + ) ) - ); ?>" title="translate('Services UNKNOWN Handled'); ?>"> - services_unknown_handled; ?> - + ); ?> services_unknown_unhandled): ?> @@ -171,33 +220,53 @@ services_warning_unhandled): ?> - services_warning_unhandled + ), + $s->services_warning_unhandled, + $s->servicegroup_alias + ) ) - ); ?>" title="translate('Services WARNING Unhandled'); ?>"> - services_warning_unhandled; ?> - + ); ?> services_warning_handled): ?> - services_warning_handled + ), + $s->services_warning_handled, + $s->servicegroup_alias + ) ) - ); ?>" title="translate('Services WARNING Handled'); ?>"> - services_warning_handled; ?> - + ); ?> services_warning_unhandled): ?> @@ -205,21 +274,46 @@ services_pending): ?> - services_pending + ), + $s->services_pending, + $s->servicegroup_alias + ) ) - ); ?>" title="translate('Services PENDING'); ?>"> - services_pending; ?> - + ); ?>
    -
    \ No newline at end of file +hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> + + + translate('No servicegroups found matching the filter'); ?> + +
    diff --git a/modules/monitoring/application/views/scripts/list/services.phtml b/modules/monitoring/application/views/scripts/list/services.phtml index 2ea125527..b12e3176c 100644 --- a/modules/monitoring/application/views/scripts/list/services.phtml +++ b/modules/monitoring/application/views/scripts/list/services.phtml @@ -1,43 +1,30 @@ getHelper('MonitoringState'); +use Icinga\Module\Monitoring\Object\Host; +use Icinga\Module\Monitoring\Object\Service; $selfUrl = 'monitoring/list/services'; +$services->peekAhead($this->compact); -if (!$this->compact): ?> -
    -tabs ?> -
    -render('list/components/selectioninfo.phtml') ?> -render('list/components/servicesummary.phtml') ?> -
    -translate('Sort by') ?> sortControl ?> +if (! $this->compact): ?> +
    + tabs ?> +
    + render('list/components/selectioninfo.phtml') ?> + render('list/components/servicesummary.phtml') ?> +
    + sortBox ?> + limiter ?> + paginator ?> + filterEditor ?>
    -
    -limit === 0): ?> -widget('limiter') ?> - -widget('limiter')->setCurrentPageCount($this->services->count()) ?> -paginationControl($services, null, null, array('preserve' => $this->preserve)) ?> -
    -
    -filterEditor ?> - - -
    - -" data-icinga-multiselect-data="service,host"> -href( 'monitoring/service/show', array( @@ -51,71 +38,64 @@ foreach ($services as $service): 'host' => $service->host_name, ) ); - $serviceStateName = strtolower($this->util()->getServiceStateName($service->service_state)); + $serviceStateName = Service::getStateText($service->service_state); ?> - -extraColumns as $col): ?> +addColumns as $col): ?>
    - translate(strtoupper($helper->monitoringState($service, 'service'))) ?>
    - - compact): ?>prefixedTimeSince($service->service_last_state_change); ?>timeSince($service->service_last_state_change); ?> - service_state > 0 && (int) $service->service_state_type === 0): ?> -
    - Soft service_attempt ?> - +
    + service_state, true) ?> + service_state !== 99): ?> +
    + timeSince($service->service_last_state_change, $this->compact) ?> + service_state > 0 && (int) $service->service_state_type === 0): ?> +
    + Soft service_attempt ?> + +
    - perfdata($service->service_perfdata, true, true) ?> - - service_handled && $service->service_state > 0): ?> - icon('attention-alt', $this->translate('Unhandled')) ?> - - - service_acknowledged && !$service->service_in_downtime): ?> - icon('ok', $this->translate('Acknowledged') . ( - $service->service_last_ack ? ': ' . $service->service_last_ack : '' - )) ?> - - - service_is_flapping): ?> - icon('flapping', $this->translate('Flapping')) ?> - - - service_notifications_enabled): ?> - icon('bell-off-empty', $this->translate('Notifications Disabled')) ?> - - - service_in_downtime): ?> - icon('plug', $this->translate('In Downtime')) ?> - - - service_last_comment) && $service->service_last_comment !== null): ?> - icon('comment', $this->translate('Comment: ') . $service->service_last_comment) ?> - - - service_active_checks_enabled): ?> - service_passive_checks_enabled): ?> - icon('eye-off', $this->translate('Active And Passive Checks Disabled')) ?> - - icon('eye-off', $this->translate('Active Checks Disabled')) ?> - - - - service_icon_image && ! preg_match('/[\'"]/', $service->service_icon_image)): ?> - icon($this->resolveMacros($service->service_icon_image, $service)) ?> - -service_display_name ?>showHost): ?> on host_name; ?> -host_state != 0): ?> - (monitoringState($service, 'host')); ?>) - -
    -

    escape(substr(strip_tags($service->service_output), 0, 10000)); ?>

    + iconImage()->service($service) ?> + serviceFlags($service)) ?> +showHost): ?>qlink( + $service->host_display_name . ($service->host_state != 0 ? ' (' . Host::getStateText($service->host_state, true) . ')' : ''), + $hostLink, + null, + array('title' => sprintf($this->translate('Show detailed information for host %s'), $service->host_display_name)) + ) ?>: +qlink( + $service->service_display_name, + $serviceLink, + null, + array( + 'title' => sprintf( + $this->translate('Show detailed information for service %s on host %s'), + $service->service_display_name, + $service->host_display_name + ), + 'class' => 'rowaction' + ) +) ?>
    +
    perfdata($service->service_perfdata, true, 5) ?>
    +

    pluginOutput($this->ellipsis($service->service_output, 10000), true) ?>

    escape($service->$col) ?>
    +hasResult()): ?> + translate('No services found matching the filter'); ?> +hasMore()): ?> + qlink( + $this->translate('Show More'), + $this->url()->without(array('view', 'limit')), + null, + array( + 'data-base-target' => '_next', + 'class' => 'pull-right show-more' + ) + ); ?> +
    diff --git a/modules/monitoring/application/views/scripts/multi/components/comments.phtml b/modules/monitoring/application/views/scripts/multi/components/comments.phtml deleted file mode 100644 index f0b262c9d..000000000 --- a/modules/monitoring/application/views/scripts/multi/components/comments.phtml +++ /dev/null @@ -1,25 +0,0 @@ -is_service ? 'Services' : 'Hosts'; - -?> - translate('Comments') ?> - - icon('cancel') - ?> translate('Remove Comments') ?>
    - icon('bell-off-empty') - ?> translate('Delay Notifications') ?>
    - - icon('ok') ?> Acknowledge - - - comments . - - diff --git a/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml b/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml deleted file mode 100644 index af8d841b4..000000000 --- a/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml +++ /dev/null @@ -1,19 +0,0 @@ -is_service ? 'Services' : 'Hosts'; - -?> - - Downtimes - - icon('cancel') ?>Remove Downtimes
    - icon('plug') - ?> Schedule Downtimes - - - Change downtimes. - - diff --git a/modules/monitoring/application/views/scripts/multi/components/flags.phtml b/modules/monitoring/application/views/scripts/multi/components/flags.phtml deleted file mode 100644 index 1269e2f49..000000000 --- a/modules/monitoring/application/views/scripts/multi/components/flags.phtml +++ /dev/null @@ -1,25 +0,0 @@ -
    - - form->getElements() as $name => $element): - if ($element instanceof \Icinga\Web\Form\Element\TriStateCheckbox): - $element->setDecorators(array('ViewHelper')); - ?> - - - - - - - - - - - -
    render() ?>
    render() ?>
    -
    \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml b/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml deleted file mode 100644 index 879a13a1d..000000000 --- a/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml +++ /dev/null @@ -1,65 +0,0 @@ -is_service) { - $links[] = $this->qlink( - $object->host_name . ' ' . $object->service_description, - 'monitoring/show/service', - array( - 'host' => $object->host_name, - 'service' => $object->service_description - ) - ); - } else { - $links[] = $this->qlink( - $object->host_name, - 'monitoring/show/host', - array( - 'host' => $object->host_name - ) - ); - } -} - -if ($this->is_service) { - $objectName = $this->translate('Services'); - $link = 'monitoring/list/services'; - $target = array( - 'host' => $this->hostquery, - 'service' => $this->servicequery - ); -} else { - $objectName = $this->translate('Hosts'); - $link = 'monitoring/list/hosts'; - $target = array( - 'host' => $this->hostquery - ); -} - -$more = clone $this->url; - - -?> -qlink( - $this->translate('List all'), - $more->setPath($link), - null, - array('title' => $this->translate('List all selected objects')) -) ?> - - 5) { - echo ' ' . sprintf($this->translate('and %d more'), count($objects) - 5); -} - -?> - - diff --git a/modules/monitoring/application/views/scripts/multi/components/summary.phtml b/modules/monitoring/application/views/scripts/multi/components/summary.phtml deleted file mode 100644 index d832624c7..000000000 --- a/modules/monitoring/application/views/scripts/multi/components/summary.phtml +++ /dev/null @@ -1,59 +0,0 @@ -getHelper('CommandForm'); -$servicequery = isset($this->servicequery) ? $this->servicequery : ''; -$objectName = $this->is_service ? $this->translate('Services') : $this->translate('Hosts'); - -$params = array( - 'host' => $this->target['host'], - 'service' => null, - 'checktime' => time(), - 'forcecheck' => '1' -); -if (array_key_exists('service', $this->target)) { - $params['service'] = $this->target['service']; -} else { - unset($params['service']); -} -?> - - - - icon('rescheduel') - ?> Recheck
    - icon('reschedule') - ?> Reschedule
    - - Perform actions on . - - - - problems ?> Problems - - icon('plug') - ?> Schedule Downtimes - - problems, - count($this->objects), - $objectName - ) ?> - - - unhandled) ?> Unhandled - - icon('ok') - ?> Acknowledge
    - icon('remove_petrol.png') ?> Remove Acknowledgements - - diff --git a/modules/monitoring/application/views/scripts/multi/host.phtml b/modules/monitoring/application/views/scripts/multi/host.phtml deleted file mode 100644 index 6179f6339..000000000 --- a/modules/monitoring/application/views/scripts/multi/host.phtml +++ /dev/null @@ -1,62 +0,0 @@ -is_service = false; -$this->hostquery = implode($this->hostnames, ','); -$this->target = array('host' => $this->hostquery); -?> - -
    - tabs; ?> -
    - -
    - - - -

    Summary for hosts

    - render('multi/components/objectlist.phtml'); ?> - - - - - - - - - - -
    -

    Hosts

    -
    - pie->render(); ?> - - $count) { - if ($count > 0) { - echo ucfirst($state) . ': ' . $count . '
    '; - } - } - ?> -
    - -

    icon('host')?> Host Actions

    - - - - render('multi/components/summary.phtml'); ?> - render('multi/components/comments.phtml'); ?> - render('multi/components/downtimes.phtml'); ?> - -
    - - render('multi/components/flags.phtml') ?> - -
    - - - - diff --git a/modules/monitoring/application/views/scripts/multi/service.phtml b/modules/monitoring/application/views/scripts/multi/service.phtml deleted file mode 100644 index dc2e9f19e..000000000 --- a/modules/monitoring/application/views/scripts/multi/service.phtml +++ /dev/null @@ -1,61 +0,0 @@ -is_service = true; -$this->hostquery = implode($this->hostnames, ','); -$this->servicequery = implode($this->servicenames, ','); -$this->target = array( - 'host' => $this->hostquery, - 'service' => $this->servicequery -); -?> - -
    -tabs ?> -
    - -
    - - - -

    Summary for services

    - - - - - - - - - - - - -
    Services Hosts
    service_pie->render() ?> $count) { - if ($count > 0) { - echo ucfirst($state) . ': ' . $count . '
    '; - } - } - - ?>
    host_pie->render() ?> $count) { - if ($count > 0) { - echo ucfirst($state) . ': ' . $count . '
    '; - } - } - ?>
    - -

    icon('conf')?> Service Actions

    - - - - render('multi/components/objectlist.phtml') ?> - render('multi/components/summary.phtml') ?> - render('multi/components/comments.phtml') ?> - render('multi/components/downtimes.phtml') ?> - -
    - - render('multi/components/flags.phtml') ?> - -
    diff --git a/modules/monitoring/application/views/scripts/partials/command-form.phtml b/modules/monitoring/application/views/scripts/partials/command-form.phtml deleted file mode 100644 index 9832bfe17..000000000 --- a/modules/monitoring/application/views/scripts/partials/command-form.phtml +++ /dev/null @@ -1,28 +0,0 @@ -
    - tabs->showOnlyCloseButton() ?> -
    -
    -

    icon('help', $form->getHelp()) ?>

    - - - - - - - - - getObjects() as $object): /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ ?> - - getType() === $object::TYPE_HOST): ?> - - - - - - - - -
    icon('host') ?> translate('Host') ?>icon('conf') ?> translate('Service') ?>
    escape($object->getName()) ?>escape($object->getHost()->getName()) ?>escape($object->getName()) ?>
    -
    - -
    diff --git a/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml b/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml new file mode 100644 index 000000000..ab14b1b2a --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml @@ -0,0 +1,15 @@ +
    + compact): ?> + tabs; ?> + + getType() === $object::TYPE_HOST): ?> + render('partials/host/object-header.phtml'); ?> + render('partials/host/servicesummary.phtml'); ?> + + render('partials/service/object-header.phtml'); ?> +
    + +
    +
    + +
    \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml b/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml new file mode 100644 index 000000000..42c6e73cd --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml @@ -0,0 +1,19 @@ +
    + + compact): ?> + + + + + render('list/components/servicesummary.phtml') ?> + render('partials/service/objects-header.phtml'); ?> + + render('list/components/hostssummary.phtml') ?> + render('partials/host/objects-header.phtml'); ?> + +
    + +
    +
    + +
    diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml new file mode 100644 index 000000000..87a7308fc --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml @@ -0,0 +1,27 @@ +type) { + case 'flapping': + $icon = 'flapping'; + $title = $this->translate('Flapping'); + $tooltip = $this->translate('Comment was caused by a flapping host or service.'); + break; + case 'comment': + $icon = 'user'; + $title = $this->translate('User Comment'); + $tooltip = $this->translate('Comment was created by an user.'); + break; + case 'downtime': + $icon = 'plug'; + $title = $this->translate('Downtime'); + $tooltip = $this->translate('Comment was caused by a downtime.'); + break; + case 'ack': + $icon = 'ok'; + $title = $this->translate('Acknowledgement'); + $tooltip = $this->translate('Comment was caused by an acknowledgement.'); + break; +} +?> +escape($title); ?>
    +icon($icon, $tooltip) ?> +timeAgo($comment->timestamp, $this->compact); ?> diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml new file mode 100644 index 000000000..8583bfc59 --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml @@ -0,0 +1,17 @@ +objecttype === 'service'): ?> + icon('service', $this->translate('Service')); ?> + host_display_name, + $comment->service_display_name + ) ?> + + icon('host', $this->translate('Host')); ?> + link()->host($comment->host_name, $comment->host_display_name); ?> + + +
    +icon('comment', $this->translate('Comment')); ?> author) + ? '[' . $this->escape($comment->author) . '] ' + : ''; +?>escape($comment->comment); ?> diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml new file mode 100644 index 000000000..c3e0fadda --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml @@ -0,0 +1,10 @@ + + + + + +
    + render('partials/comment/comment-description.phtml'); ?> + + render('partials/comment/comment-detail.phtml'); ?> +
    diff --git a/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml b/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml new file mode 100644 index 000000000..7e2a5d87c --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml @@ -0,0 +1,32 @@ + + 5) { + continue; + } + $this->comment = $comment; + ?> + + + + + + +
    + render('partials/comment/comment-description.phtml'); ?> + + render('partials/comment/comment-detail.phtml'); ?> +
    + +

    + 5): ?> + qlink( + sprintf($this->translate('show all %d comments'), $i), + $listAllLink, + null, + array( + 'icon' => $i > 5 ? 'down-open' : '', + 'data-base-target' => "_next" + ) + ) ?> + +

    diff --git a/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml b/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml new file mode 100644 index 000000000..13bf8fe95 --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml @@ -0,0 +1,64 @@ + + + + + +
    + start <= time() && ! $downtime->is_in_effect): ?> + translate('Ends'); ?> +
    + timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, $this->compact) ?> + + is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?> +
    + timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?> + +
    + + is_flexible): ?> + is_in_effect): ?> + isService + ? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.') + : $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'), + $this->formatDate($downtime->start), + $this->formatTime($downtime->start), + $this->formatDuration($downtime->duration), + $this->formatDate($downtime->end), + $this->formatTime($downtime->end) + ); ?> + + isService + ? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.') + : $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'), + $this->formatDateTime($downtime->scheduled_start), + $this->formatDateTime($downtime->scheduled_end), + $this->formatDuration($downtime->duration) + ); ?> + + + is_in_effect): ?> + isService + ? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.') + : $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'), + $this->formatDate($downtime->start), + $this->formatTime($downtime->start), + $this->formatDate($downtime->end), + $this->formatTime($downtime->end) + ); ?> + + isService + ? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.') + : $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'), + $this->formatDate($downtime->start), + $this->formatTime($downtime->start), + $this->formatDate($downtime->end), + $this->formatTime($downtime->end) + ); ?> + + + +
    diff --git a/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml b/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml new file mode 100644 index 000000000..16e0daee7 --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml @@ -0,0 +1,96 @@ + + + 5) { + break; + } ?> + + + + + + +
    + start <= time() && ! $downtime->is_in_effect): ?> + translate('Ends'); ?> +
    + timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, $this->compact) ?> + + is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?> +
    + timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?> + +
    + isService): ?> + icon('service', $this->translate('Service')) ?> + link()->service( + $downtime->service_description, + $downtime->service_display_name, + $downtime->host_name, + $downtime->host_display_name + ); ?> + + icon('host', $this->translate('Host')) ?> + link()->host($downtime->host_name, $downtime->host_display_name); ?> + +
    + is_flexible): ?> + is_in_effect): ?> + isService + ? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.') + : $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'), + $this->formatDate($downtime->start), + $this->formatTime($downtime->start), + $this->formatDuration($downtime->duration), + $this->formatDate($downtime->end), + $this->formatTime($downtime->end) + ); ?> + + isService + ? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.') + : $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'), + $this->formatDateTime($downtime->scheduled_start), + $this->formatDateTime($downtime->scheduled_end), + $this->formatDuration($downtime->duration) + ); ?> + + + is_in_effect): ?> + isService + ? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.') + : $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'), + $this->formatDate($downtime->start), + $this->formatTime($downtime->start), + $this->formatDate($downtime->end), + $this->formatTime($downtime->end) + ); ?> + + isService + ? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.') + : $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'), + $this->formatDate($downtime->scheduled_start), + $this->formatTime($downtime->scheduled_start), + $this->formatDate($downtime->scheduled_end), + $this->formatTime($downtime->scheduled_end) + ); ?> + + +
    + + 5): ?> +

    + qlink( + sprintf($this->translate('List all %d downtimes'), $i), + $listAllLink, + null, + array( + 'icon' => 'down-open', + 'data-base-target' => "_next" + ) + ) ?> +

    + \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/partials/host/object-header.phtml b/modules/monitoring/application/views/scripts/partials/host/object-header.phtml new file mode 100644 index 000000000..1a2fc356d --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/host/object-header.phtml @@ -0,0 +1,24 @@ + + + + + + +
    + host_state, true); ?>
    + timeSince($object->host_last_state_change); ?> +
    + iconImage()->host($object) ?> + escape($object->host_display_name); ?> + host_display_name !== $object->host_name): ?> + (escape($object->host_name); ?>) + + host_address && $object->host_address !== $object->host_name): ?> +
    + escape($object->host_address); ?> + + render('partials/host/statusicons.phtml'); ?> +
    diff --git a/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml b/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml new file mode 100644 index 000000000..bce62db6b --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml @@ -0,0 +1,45 @@ + + + 0): ?> + + + + 5) { + continue; + } + ?> + + + + + + + +
    host_state, true); ?>
    + iconImage()->host($host) ?> + hostFlags($host)) ?> + escape($host->getName()); ?>
    + escape($host->host_output) ?> +
    +
    + 5): ?> + qlink( + sprintf($this->translate('show all %d hosts'), $i), + $listAllLink, + null, + array( + 'icon' => 'down-open', + 'data-base-target' => '_next' + ) + ); + ?> + +
    + + diff --git a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml new file mode 100644 index 000000000..be146acbe --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml @@ -0,0 +1,150 @@ +setQueryString($f->toQueryString()); +} + +$selfUrl = Url::fromPath('monitoring/list/services', array('host' => $object->host_name)); +?>

    compact ? ' data-base-target="col1"' : ''; ?>> +stats->services_total): ?> +qlink( + sprintf( + $this->translatePlural( + '%u configured service:', + '%u configured services:', + $object->stats->services_total + ), + $object->stats->services_total + ), + $selfUrl, + null, + array( + 'title' => sprintf( + $this->translatePlural( + 'List all %u service on host %s', + 'List all %u services on host %s', + $object->stats->services_total + ), + $object->stats->services_total, + $object->host_name + ), + 'data-base-target' => '_next' + ) +); ?> + +translate('No services configured on this host'); ?> + + + +stats->services_ok): ?> + + qlink( + $object->stats->services_ok, + $selfUrl, + array('service_state' => 0), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state OK on host %s', + 'List %u services which are currently in state OK on host %s', + $object->stats->services_ok + ), + $object->stats->services_ok, + $object->host_name + ), + 'data-base-target' => '_next' + ) + ); ?> + + + 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $state) { + $pre = 'services_' . $state; + if ($object->stats->$pre) { + $handled = $pre . '_handled'; + $unhandled = $pre . '_unhandled'; + $paramsHandled = array('service_state' => $stateId, 'service_handled' => 1); + $paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0); + echo ''; + if ($object->stats->$unhandled) { + + echo $this->qlink( + $object->stats->$unhandled, + $selfUrl, + $paramsUnhandled, + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state %s on host %s', + 'List %u services which are currently in state %s on host %s', + $object->stats->$unhandled + ), + $object->stats->$unhandled, + Service::getStateText($stateId, true), + $object->host_name + ), + 'data-base-target' => '_next' + ) + ); + } + if ($object->stats->$handled) { + if ($object->stats->$unhandled) { + echo ''; + } + echo $this->qlink( + $object->stats->$handled, + $selfUrl, + $paramsHandled, + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state %s (Acknowledged) on host %s', + 'List %u services which are currently in state %s (Acknowledged) on host %s', + $object->stats->$handled + ), + $object->stats->$handled, + Service::getStateText($stateId, true), + $object->host_name + ), + 'data-base-target' => '_next' + ) + ); + if ($object->stats->$unhandled) { + echo "\n"; + } + } + echo "\n"; + } +} +?> +stats->services_pending): ?> + + qlink( + $object->stats->services_pending, + $selfUrl, + array('service_state' => 99), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state PENDING on host %s', + 'List %u services which are currently in state PENDING on host %s', + $object->stats->services_pending + ), + $object->stats->services_pending, + $object->host_name + ), + 'data-base-target' => '_next' + ) + ) ?> + + + +

    diff --git a/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml b/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml new file mode 100644 index 000000000..4421c301f --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/host/statusicons.phtml @@ -0,0 +1,28 @@ +object->host_handled && $this->object->host_state > 0) { + $icons[] = $this->icon('attention-alt', $this->translate('Unhandled')); +} + +if ($this->object->host_acknowledged && !$this->object->host_in_downtime) { + $icons[] = $this->icon('ok', $this->translate('Acknowledged')); +} + +if (! $this->object->host_notifications_enabled) { + $icons[] = $this->icon('bell-off-empty', $this->translate('Notifications Disabled')); +} + +if ($this->object->host_in_downtime) { + $icons[] = $this->icon('plug', $this->translate('In Downtime')); +} + +if (! $this->object->host_active_checks_enabled) { + if ($this->object->host_passive_checks_enabled) { + $icons[] = $this->icon('eye-off', $this->translate('Active Checks Disabled')); + } else { + $icons[] = $this->icon('eye-off', $this->translate('Active And Passive Checks Disabled')); + } +} + +?> \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/partials/service/object-header.phtml b/modules/monitoring/application/views/scripts/partials/service/object-header.phtml new file mode 100644 index 000000000..c3df4044b --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/service/object-header.phtml @@ -0,0 +1,39 @@ + + + + + + + + + + +
    + host_state, true); ?>
    + timeSince($object->host_last_state_change) ?> +
    + iconImage()->service($object) ?> + escape($object->host_display_name); ?> + host_display_name !== $object->host_name): ?> + (escape($object->host_name); ?>) + + host_address && $object->host_address !== $object->host_name): ?> +
    + escape($object->host_address); ?> + + render('partials/host/statusicons.phtml'); ?> +
    + service_state, true); ?>
    + timeSince($object->service_last_state_change) ?> +
    + iconImage()->host($object) ?> + translate('Service'); ?>: escape($object->service_display_name); ?> + service_display_name !== $object->service_description): ?> + (escape($object->service_description); ?>) + + render('partials/service/statusicons.phtml'); ?> +
    diff --git a/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml b/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml new file mode 100644 index 000000000..ff39bcf22 --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml @@ -0,0 +1,44 @@ + + 0): ?> + + + + 5) { + continue; + } + ?> + + + + + + +
    service_state, true); ?>
    + iconImage()->service($service) ?> + serviceFlags($service)) ?> + + escape($service->getHost()->getName()); ?>: + escape($service->getName()); ?>
    +
    + escape($service->service_output) ?> +
    +
    + 5): ?> + qlink( + sprintf($this->translate('show all %d services'), $i), + $listAllLink, + null, + array( + 'icon' => 'down-open', + 'data-base-target' => '_next' + ) + ); + ?> + +
    + diff --git a/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml b/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml new file mode 100644 index 000000000..8859bf189 --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/service/statusicons.phtml @@ -0,0 +1,28 @@ +object->service_handled && $this->object->service_state > 0) { + $icons[] = $this->icon('attention-alt', $this->translate('Unhandled')); +} + +if ($this->object->service_acknowledged && !$this->object->service_in_downtime) { + $icons[] = $this->icon('ok', $this->translate('Acknowledged')); +} + +if (! $this->object->service_notifications_enabled) { + $icons[] = $this->icon('bell-off-empty', $this->translate('Notifications Disabled')); +} + +if ($this->object->service_in_downtime) { + $icons[] = $this->icon('plug', $this->translate('In Downtime')); +} + +if (! $this->object->service_active_checks_enabled) { + if ($this->object->service_passive_checks_enabled) { + $icons[] = $this->icon('eye-off', $this->translate('Active Checks Disabled')); + } else { + $icons[] = $this->icon('eye-off', $this->translate('Active And Passive Checks Disabled')); + } +} + +?> \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/process/disable-notifications.phtml b/modules/monitoring/application/views/scripts/process/disable-notifications.phtml index 2fb3c6c6e..e8c75e53f 100644 --- a/modules/monitoring/application/views/scripts/process/disable-notifications.phtml +++ b/modules/monitoring/application/views/scripts/process/disable-notifications.phtml @@ -1,8 +1,10 @@ +compact): ?>
    - tabs->showOnlyCloseButton() ?> + tabs->showOnlyCloseButton(); ?>
    +
    -

    icon('help', $form->getHelp()) ?>

    +

    notifications_enabled === false): ?>
    translate('Host and service notifications are already disabled.') ?> @@ -13,6 +15,6 @@
    - +
    diff --git a/modules/monitoring/application/views/scripts/process/info.phtml b/modules/monitoring/application/views/scripts/process/info.phtml index 02b8eeb80..85e576116 100644 --- a/modules/monitoring/application/views/scripts/process/info.phtml +++ b/modules/monitoring/application/views/scripts/process/info.phtml @@ -1,36 +1,41 @@ runtimeVariables()->create($this->runtimevariables); $cp = $this->checkPerformance()->create($this->checkperformance); -?> +if (! $this->compact): ?>
    - tabs ?> + tabs; ?>
    +
    -

    translate('Feature Commands') ?>

    - toggleFeaturesForm ?> + toggleFeaturesForm; ?>

    translate('Process Info') ?>

    + + + + - + - + - + @@ -50,19 +55,28 @@ $cp = $this->checkPerformance()->create($this->checkperformance); ? $this->programStatus->global_host_event_handler : $this->translate('N/A'); ?> + + + +
    translate('Program Version') ?>programStatus->program_version + ? $this->programStatus->program_version + : $this->translate('N/A') ?>
    translate('Program Start Time') ?>dateFormat()->formatDateTime($this->programStatus->program_start_time) ?>formatDateTime($this->programStatus->program_start_time) ?>
    translate('Last Status Update'); ?>timeSince($this->programStatus->status_update_time) ?> agotimeAgo($this->programStatus->status_update_time); ?>
    translate('Last External Command Check'); ?>timeSince($this->programStatus->last_command_check) ?> agotimeAgo($this->programStatus->last_command_check); ?>
    translate('Last Log File Rotation'); ?>
    translate('Active Endpoint'); ?>programStatus->endpoint_name + ? $this->programStatus->endpoint_name + : $this->translate('N/A') ?>
    programStatus->is_currently_running === true): ?>
    translate('%s has been up and running with PID %d since %s'), + $this->translate( + '%1$s has been up and running with PID %2$d %3$s', + 'Last format parameter represents the time running' + ), $this->backendName, $this->programStatus->process_id, $this->timeSince($this->programStatus->program_start_time)) ?>
    - translate('%s is not running'), $this->backendName) ?> + translate('Backend %s is not running'), $this->backendName) ?>
    diff --git a/modules/monitoring/application/views/scripts/process/not-running.phtml b/modules/monitoring/application/views/scripts/process/not-running.phtml index 58b6c2980..8439fc49e 100644 --- a/modules/monitoring/application/views/scripts/process/not-running.phtml +++ b/modules/monitoring/application/views/scripts/process/not-running.phtml @@ -1,6 +1,8 @@ +compact): ?>
    - tabs ?> + tabs; ?>
    +
    translate('%s is currently not up and running'), $this->backendName) ?>
    diff --git a/modules/monitoring/application/views/scripts/process/performance.phtml b/modules/monitoring/application/views/scripts/process/performance.phtml index 6276d76d1..0bff8891f 100644 --- a/modules/monitoring/application/views/scripts/process/performance.phtml +++ b/modules/monitoring/application/views/scripts/process/performance.phtml @@ -1,7 +1,8 @@ +compact): ?>
    -tabs ?> + tabs; ?>
    -runtimeVariables()->create($this->runtimevariables); $cp = $this->checkPerformance()->create($this->checkperformance); diff --git a/modules/monitoring/application/views/scripts/service/history.phtml b/modules/monitoring/application/views/scripts/service/history.phtml new file mode 100644 index 000000000..75e0e608f --- /dev/null +++ b/modules/monitoring/application/views/scripts/service/history.phtml @@ -0,0 +1,150 @@ +qlink( + $contact, + 'monitoring/show/contact', + array('contact_name' => $contact), + array('title' => sprintf($view->translate('Show detailed information about %s'), $contact)) + ); + } + return '[' . implode(', ', $links) . ']'; +} + +$self = $this; + +$url = $this->url(); +$limit = (int) $url->getParam('limit', 25); +if (! $url->hasParam('page') || ($page = (int) $url->getParam('page')) < 1) { + $page = 1; +} + +$history->limit($limit * $page); + +if (! $this->compact): ?> +
    + tabs; ?> + render('partials/service/object-header.phtml'); ?> +

    translate('This Service\'s Event History'); ?>

    + sortBox; ?> + limiter; ?> + + translate('Scroll to the bottom of this page to load additional events'); ?> + + filterEditor; ?> +
    + +
    + + + peekAhead() as $event): ?> + escape($event->output); + switch ($event->type) { + case 'notify': + $icon = 'notification'; + $title = $this->translate('Notification'); + $stateClass = Service::getStateText($event->state); + + $msg = $msg ? preg_replace_callback( + '/^\[([^\]]+)\]/', + function($match) use ($self) { return contactsLink($match, $self); }, + $msg + ) : $this->translate('This notification was not sent out to any contact.'); + break; + case 'comment': + $icon = 'comment'; + $title = $this->translate('Comment'); + break; + case 'comment_deleted': + $icon = 'remove'; + $title = $this->translate('Comment deleted'); + break; + case 'ack': + $icon = 'acknowledgement'; + $title = $this->translate('Acknowledge'); + break; + case 'ack_deleted': + $icon = 'remove'; + $title = $this->translate('Ack removed'); + break; + case 'dt_comment': + $icon = 'in_downtime'; + $title = $this->translate('In Downtime'); + break; + case 'dt_comment_deleted': + $icon = 'remove'; + $title = $this->translate('Downtime removed'); + break; + case 'flapping': + $icon = 'flapping'; + $title = $this->translate('Flapping'); + break; + case 'flapping_deleted': + $icon = 'remove'; + $title = $this->translate('Flapping stopped'); + break; + case 'hard_state': + $stateClass = Service::getStateText($event->state); + $icon = 'attention-alt'; + $title = Service::getStateText($event->state); + break; + case 'soft_state': + $icon = 'spinner'; + $stateClass = Service::getStateText($event->state); + $title = Service::getStateText($event->state); + break; + case 'dt_start': + $icon = 'downtime_start'; + $title = $this->translate('Downtime Start'); + break; + case 'dt_end': + $icon = 'downtime_end'; + $title = $this->translate('Downtime End'); + break; + } + ?> + + + + + + +
    + getIteratorPosition() % $limit === 0): ?> + + + escape($title); ?> +
    + timestamp); ?> +
    + translate('%s on %s', 'Service running on host'), + $this->escape($event->service_display_name), + $event->host_display_name + ) ?> +
    +
    + icon($icon, $title); ?> createTicketLinks($msg) ?> +
    +
    +hasResult()): ?> + translate('No history events found matching the filter'); ?> +hasMore()): ?> +
    qlink( + $this->translate('Load More'), + $url->setAnchor('page-' . ($page + 1)), + array( + 'page' => $page + 1, + ), + array( + 'id' => 'load-more', + 'class' => 'pull-right load-more button-like' + ) + ); ?>
    + +
    diff --git a/modules/monitoring/application/views/scripts/service/show.phtml b/modules/monitoring/application/views/scripts/service/show.phtml index a5c95fd61..1fac5752f 100644 --- a/modules/monitoring/application/views/scripts/service/show.phtml +++ b/modules/monitoring/application/views/scripts/service/show.phtml @@ -1,5 +1,8 @@ -
    - render('show/components/header.phtml') ?> +
    + compact): ?> + tabs; ?> + + render('partials/service/object-header.phtml') ?>

    translate("Service detail information") ?>

    @@ -8,17 +11,21 @@ + render('show/components/acknowledgement.phtml') ?> render('show/components/comments.phtml') ?> - render('show/components/notifications.phtml') ?> render('show/components/downtime.phtml') ?> - render('show/components/flapping.phtml') ?> - render('show/components/perfdata.phtml') ?> - render('show/components/checksource.phtml') ?> + render('show/components/notes.phtml') ?> render('show/components/actions.phtml') ?> - render('show/components/command.phtml') ?> + render('show/components/flapping.phtml') ?> render('show/components/servicegroups.phtml') ?> + render('show/components/perfdata.phtml') ?> + + render('show/components/notifications.phtml') ?> render('show/components/contacts.phtml') ?> + + render('show/components/command.phtml') ?> + render('show/components/checksource.phtml') ?> render('show/components/checkstatistics.phtml') ?> render('show/components/customvars.phtml') ?> render('show/components/flags.phtml') ?> diff --git a/modules/monitoring/application/views/scripts/services/show.phtml b/modules/monitoring/application/views/scripts/services/show.phtml index af05979fb..d2ad21bca 100644 --- a/modules/monitoring/application/views/scripts/services/show.phtml +++ b/modules/monitoring/application/views/scripts/services/show.phtml @@ -1,125 +1,218 @@
    - tabs ?> + + compact): ?> + + + + render('list/components/servicesummary.phtml') ?> + render('partials/service/objects-header.phtml'); ?> +
    -
    - - translate('No services matching the filter') ?> +
    +

    + icon('reschedule') ?> + translate('Commands') ?> +

    + + + translate('No services found matching the filter'); ?> +

    + translatePlural( + 'Issue commands to %s selected service:', + 'Issue commands to all %s selected services:', + count($objects) + ), '' . count($objects) . '') ?> +

    + +
    + qlink( + $this->translate('Reschedule next checks'), + $rescheduleAllLink, + null, + array('icon' => 'reschedule') + ); ?> -
    -
    - translate('Services (%u)'), array_sum(array_values($serviceStates))) ?> -
    -
    - serviceStatesPieChart ?> -
    -
    - $count) { - echo sprintf(' %s: %u
    ', strtoupper($text), $count); - } ?> -
    -
    +
    + qlink( + $this->translate('Schedule downtimes'), + $downtimeAllLink, + null, + array('icon' => 'plug') + ); ?> -
    -
    - translate('Hosts (%u)'), array_sum(array_values($hostStates))) ?> -
    -
    - hostStatesPieChart ?> -
    -
    - $count) { - echo sprintf('%s: %u
    ', strtoupper($text), $count); - } ?> -
    -
    +
    + qlink( + $this->translate('Submit passive check results'), + $processCheckResultAllLink, + null, + array('icon' => 'reply') + ); ?> +
    + qlink( + $this->translate('Add comments'), + $addCommentLink, + null, + array('icon' => 'comment') + ); ?> + + hasPermission('monitoring/command/send-custom-notification')): ?> +
    + qlink( + sprintf($this->translate('Send a custom notification for all %u services'), $serviceCount), + $sendCustomNotificationLink, + null, + array('icon' => 'comment') + ); ?> + + + + + +

    + icon('attention-alt') ?> + translate('Problems') ?> +

    - -
    - -
    - - - - - - -

    - translatePlural( - '%u Unhandled Service Problem', - '%u Unhandled Service Problems', - count($unhandledObjects) - ), - count($unhandledObjects) - ) ?> -

    - - - - - -

    + +

    translatePlural( - '%u Acknowledged Service Problem', - '%u Acknowledged Service Problems', - count($acknowledgedObjects) + 'There is %s problem, issue commands to the problem service', + 'There are %s problems, issue commands to the problem services', + $problemCount ), - count($acknowledgedObjects) - ) ?> -

    -
    - -
    - + '' . $problemCount . '' + ); ?> +

    + qlink( + sprintf( + $this->translatePlural( + 'Schedule a downtime for %s problem service', + 'Schedule downtimes for %s problem services', + $problemCount + ), + $problemCount + ), + $downtimeLink, + null, + array('icon' => 'plug') + ); ?> + - -

    - - icon('plug') ?> - translate(sprintf('%u services are in downtime', count($objectsInDowntime))) ?> - -

    - + 0): ?> +
    + qlink( + sprintf( + $this->translatePlural( + 'Acknowledge %u unacknowledged problem service', + 'Acknowledge %u unacknowledged problem services', + $unackCount + ), + $unackCount + ), + $acknowledgeLink, + null, + array('icon' => 'ok') + ); ?> + - getComments())): ?> -

    - - icon('comment') ?> - translate(sprintf('%u comments', count($objects->getComments()))) ?> - -

    + +

    + translate('There are %s unhandled problem services. ' . + 'Issue commands to the problematic services:'), + '' . $unhandledCount . '') ?> + +

    + qlink( + sprintf( + $this->translatePlural( + 'Schedule a downtime for %u unhandled problem service', + 'Schedule a downtime for %u unhandled problem services', + $unhandledCount + ), + $unhandledCount + ), + $downtimeUnhandledLink, + null, + array('icon' => 'plug') + ); ?> + + + +
    + + + 0): ?> +

    icon('ok', $this->translate('Acknowledgements')) ?> translate('Acknowledgements') ?>

    +

    + translatePlural( + '%s Acknowledged Service Problem.', + '%s Acknowledged Service Problems.', + $acknowledgedCount + ), + '' . $acknowledgedCount . '' + ); ?> +

    + + + getScheduledDowntimes()) ?> + + +

    icon('plug', $this->translate('Downtimes')) ?> translate('Downtimes') ?>

    + qlink( + sprintf( + $this->translatePlural( + '%s scheduled downtime', + '%s scheduled downtimes', + $scheduledDowntimeCount + ), + $scheduledDowntimeCount + ), + $showDowntimesLink, + null, + array('data-base-target' => '_next') + );?> + translate('on all selected services.')) ?> + + 0): ?> +
    + qlink( + sprintf($this->translatePlural( + '%s service', + '%s services', + $inDowntimeCount + ), $inDowntimeCount), + $inDowntimeLink, + null, + array('data-base-target' => '_next') + );?> + translate('are currently in downtime.')) ?> + + getComments()) ?> + 0): ?> +

    icon('comment') ?> translate('Comments') ?>

    + qlink( + sprintf( + $this->translatePlural( + '%s comment.', + '%s comments.', + $commentCount + ), $commentCount), + $commentsLink, + null, + array('data-base-target' => '_next') + ); ?> + translate('on all selected services.') ?> + +
    diff --git a/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml b/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml index 9b323e2f5..e7b79354d 100644 --- a/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml +++ b/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml @@ -7,33 +7,49 @@ if (in_array((int) $object->state, array(0, 99))) { return; } -if ($object->getType() === $object::TYPE_HOST) { - $ackLink = $this->href( - 'monitoring/host/acknowledge-problem', - array('host' => $object->getName()) - ); -} else { - $ackLink = $this->href( - 'monitoring/service/acknowledge-problem', - array('host' => $object->getHost()->getName(), 'service' => $object->getName()) - ); -} - if ($object->acknowledged): ?> - + diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml index 9f3211bd3..329a06fb9 100644 --- a/modules/monitoring/application/views/scripts/show/components/actions.phtml +++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml @@ -1,39 +1,25 @@ action_url && ! $object->notes_url) { +// add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201 +$newTabInfo = sprintf(' %s ', $this->translate('opens in new window')); + +$links = $object->getActionUrls(); +foreach ($links as $i => $link) { + $links[$i] = sprintf('%s ' . $newTabInfo . '', $link, 'Action'); +} + +if (isset($this->actions)) { + foreach ($this->actions as $id => $action) { + $links[] = sprintf('%s', $action, $id); + } +} + +if (empty($links)) { return; } -$links = array(); -$linkText = '%s'; - -if ($object->notes_url) { - if (strpos($object->notes_url, "' ") === false) { - $links[] = sprintf($linkText, $this->resolveMacros($object->notes_url, $object), 'Notes'); - } else { - // TODO: We should find out document what's going on here. Looks strange :p - foreach(explode("' ", $object->notes_url) as $url) { - $url = strpos($url, "'") === 0 ? substr($url, 1) : $url; - $url = strrpos($url, "'") === strlen($url) - 1 ? substr($url, 0, strlen($url) - 1) : $url; - $links[] = sprintf($linkText, $this->resolveMacros($url, $object), 'Notes'); - } - } -} -if ($object->action_url) { - if (strpos($object->action_url, "' ") === false) { - $links[] = sprintf($linkText, $this->resolveMacros($object->action_url, $object), 'Action'); - } else { - // TODO: We should find out document what's going on here. Looks strange :p - foreach(explode("' ", $object->action_url) as $url) { - $url = strpos($url, "'") === 0 ? substr($url, 1) : $url; - $url = strrpos($url, "'") === strlen($url) - 1 ? substr($url, 0, strlen($url) - 1) : $url; - $links[] = sprintf($linkText, $this->resolveMacros($url, $object), 'Action'); - } - } -} - ?> - - + + diff --git a/modules/monitoring/application/views/scripts/show/components/checksource.phtml b/modules/monitoring/application/views/scripts/show/components/checksource.phtml index 60bb944f3..07e63e835 100644 --- a/modules/monitoring/application/views/scripts/show/components/checksource.phtml +++ b/modules/monitoring/application/views/scripts/show/components/checksource.phtml @@ -1,7 +1,20 @@ -check_source) return ?> +check_source !== null): ?> - - + + + +is_reachable !== null): ?> + + + + + diff --git a/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml b/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml index 50b40cdcc..415b66b76 100644 --- a/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml +++ b/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml @@ -3,17 +3,11 @@ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ if ($object->getType() === $object::TYPE_HOST) { - $reschedule = $this->href( - 'monitoring/host/reschedule-check', - array('host' => $object->getName()) - ); + $isService = false; $checkAttempts = $object->host_current_check_attempt . '/' . $object->host_max_check_attempts; $stateType = (int) $object->host_state_type; } else { - $reschedule = $this->href( - 'monitoring/service/reschedule-check', - array('host' => $object->getHost()->getName(), 'service' => $object->getName()) - ); + $isService = true; $checkAttempts = $object->service_attempt; $stateType = (int) $object->service_state_type; } @@ -23,36 +17,66 @@ if ($object->getType() === $object::TYPE_HOST) { - + - + check_execution_time): ?> - - - - + + + + check_latency): ?> - - - - + + + + diff --git a/modules/monitoring/application/views/scripts/show/components/command.phtml b/modules/monitoring/application/views/scripts/show/components/command.phtml index 715240679..d7fca7cfa 100644 --- a/modules/monitoring/application/views/scripts/show/components/command.phtml +++ b/modules/monitoring/application/views/scripts/show/components/command.phtml @@ -3,10 +3,41 @@ $parts = explode('!', $object->check_command); $command = array_shift($parts); -?> - - + + + \n \n \n\n"; diff --git a/modules/monitoring/application/views/scripts/show/components/comments.phtml b/modules/monitoring/application/views/scripts/show/components/comments.phtml index 07968dfc8..f2df26aef 100644 --- a/modules/monitoring/application/views/scripts/show/components/comments.phtml +++ b/modules/monitoring/application/views/scripts/show/components/comments.phtml @@ -1,59 +1,74 @@ +hasPermission('monitoring/command/comment/add')) { + /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ + if ($object->getType() === $object::TYPE_HOST) { + $addLink = $this->qlink( + $this->translate('Add comment'), + 'monitoring/host/add-comment', + array('host' => $object->getName()), + array( + 'icon' => 'comment', + 'data-base-target' => '_self', + 'title' => $this->translate('Add a new comment to this host') + ) + ); + } else { + $addLink = $this->qlink( + $this->translate('Add comment'), + 'monitoring/service/add-comment', + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), + array( + 'icon' => 'comment', + 'data-base-target' => '_self', + 'title' => $this->translate('Add a new comment to this service') + ) + ); + } +} +if (empty($object->comments) && ! $addLink) { + return; +} +?> - - + - -comments as $comment) { - // Ticket hook sample - $text = $this->tickets ? preg_replace_callback( - $this->tickets->getPattern(), - array($this->tickets, 'createLink'), - $this->escape($comment->comment) - ) : $this->escape($comment->comment); - - - $form = clone $delCommentForm; - $form->populate(array('comment_id' => $comment->id)); - - ?> - - - - - - - diff --git a/modules/monitoring/application/views/scripts/show/components/contacts.phtml b/modules/monitoring/application/views/scripts/show/components/contacts.phtml index d220968e5..641efb597 100644 --- a/modules/monitoring/application/views/scripts/show/components/contacts.phtml +++ b/modules/monitoring/application/views/scripts/show/components/contacts.phtml @@ -4,15 +4,17 @@ if (! empty($object->contacts)) { $list = array(); foreach ($object->contacts as $contact) { - $list[] = $this->qlink($contact->contact_alias, 'monitoring/show/contact', array( - 'contact' => $contact->contact_name - )); + $list[] = $this->qlink( + $contact->contact_alias, + 'monitoring/show/contact', + array('contact_name' => $contact->contact_name), + array('title' => sprintf($this->translate('Show detailed information about %s'), $contact->contact_alias)) + ); } printf( - "\n", + "\n", $this->translate('Contacts'), - $this->icon('user'), implode(', ', $list) ); @@ -24,14 +26,14 @@ if (! empty($object->contactgroups)) { $list[] = $this->qlink( $contactgroup->contactgroup_alias, 'monitoring/list/contactgroups', - array('contactgroup' => $contactgroup->contactgroup_name) + array('contactgroup_name' => $contactgroup->contactgroup_name), + array('title' => sprintf($this->translate('List contacts in contact-group "%s"'), $contactgroup->contactgroup_alias)) ); } printf( - "\n", + "\n", $this->translate('Contactgroups'), - $this->icon('users'), implode(', ', $list) ); diff --git a/modules/monitoring/application/views/scripts/show/components/customvars.phtml b/modules/monitoring/application/views/scripts/show/components/customvars.phtml index aa2e479e4..b3e4c334b 100644 --- a/modules/monitoring/application/views/scripts/show/components/customvars.phtml +++ b/modules/monitoring/application/views/scripts/show/components/customvars.phtml @@ -1,3 +1,9 @@ +customvars)) return; ?> + + +customvars as $name => $value) { diff --git a/modules/monitoring/application/views/scripts/show/components/downtime.phtml b/modules/monitoring/application/views/scripts/show/components/downtime.phtml index 2cd7147f1..03df6ed6e 100644 --- a/modules/monitoring/application/views/scripts/show/components/downtime.phtml +++ b/modules/monitoring/application/views/scripts/show/components/downtime.phtml @@ -1,67 +1,102 @@ +hasPermission('monitoring/command/downtime/schedule')) { + /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ + if ($object->getType() === $object::TYPE_HOST) { + $addLink = $this->qlink( + $this->translate('Schedule downtime'), + 'monitoring/host/schedule-downtime', + array('host' => $object->getName()), + array( + 'icon' => 'plug', + 'data-base-target' => '_self', + 'title' => $this->translate( + 'Schedule a downtime to suppress all problem notifications within a specific period of time' + ) + ) + ); + } else { + $addLink = $this->qlink( + $this->translate('Schedule downtime'), + 'monitoring/service/schedule-downtime', + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), + array( + 'icon' => 'plug', + 'data-base-target' => '_self', + 'title' => $this->translate( + 'Schedule a downtime to suppress all problem notifications within a specific period of time' + ) + ) + ); + } +} +if (empty($object->comments) && ! $addLink) { + return; +} +?> - - + - -downtimes as $downtime) { - // Ticket hook sample - $text = $this->tickets ? preg_replace_callback( - $this->tickets->getPattern(), - array($this->tickets, 'createLink'), - $this->escape($downtime->comment) - ) : $this->escape($downtime->comment); - - $form = clone $delDowntimeForm; - $form->populate(array('downtime_id' => $downtime->id)); - - if ((bool) $downtime->is_in_effect) { - $state = 'in downtime since ' . $this->timeSince($downtime->start); - } else { - if ((bool) $downtime->is_fixed) { - $state = 'scheduled ' . $this->timeUntil($downtime->start); - } else { - $state = 'scheduled flexible ' . $this->timeUntil($downtime->start); - } - } - - ?> - - - - - - - diff --git a/modules/monitoring/application/views/scripts/show/components/header.phtml b/modules/monitoring/application/views/scripts/show/components/header.phtml deleted file mode 100644 index a2c835e4a..000000000 --- a/modules/monitoring/application/views/scripts/show/components/header.phtml +++ /dev/null @@ -1,32 +0,0 @@ -getType() === $object::TYPE_SERVICE ?> -compact): ?> - - -

    translate('Problem handling') ?>

    translate('Notifications') ?>

    translate('Check execution') ?>

    translate('Acknowledged') ?> + +
    translate('Not acknowledged') ?> - hasPermission('monitoring/command/acknowledge-problem')) { + if ($object->getType() === $object::TYPE_HOST) { + $ackLink = $this->href( + 'monitoring/host/acknowledge-problem', + array('host' => $object->getName()) + ); + } else { + $ackLink = $this->href( + 'monitoring/service/acknowledge-problem', + array('host' => $object->getHost()->getName(), 'service' => $object->getName()) + ); + } ?> - - icon('ok') ?> translate('Acknowledge') ?> - + qlink( + $this->translate('Acknowledge'), + $ackLink, + null, + array( + 'icon' => 'ok', + 'data-base-target' => '_self', + 'title' => $this->translate( + 'Acknowledge this problem, suppress all future notifications for it and tag it as being handled' + ) + ) + ); ?> +
    Foreign URLstranslate('Actions') ?>", $links) ?>
    translate('Check Source') ?> - escape($object->check_source) ?> - + translate('Check Source') ?> + + escape($object->check_source) ?> +
    + translate('Reachable') ?> + + is_reachable ? $this->translate('Yes') : $this->translate('No') ?> +
    translate('Last check') ?> - - timeSince($object->last_check) ?> + + timeAgo($object->last_check) ?>
    translate('Next check') ?> - - icon('reschedule') ?> translate('Reschedule') ?> - + hasPermission('monitoring/command/schedule-check')) { + if ($isService) { + echo $this->qlink( + $this->translate('Reschedule'), + 'monitoring/service/reschedule-check', + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), + array( + 'icon' => 'reschedule', + 'data-base-target' => '_self', + 'title' => $this->translate( + 'Schedule the next active check at a different time than the current one' + ) + ) + ); + } else { + echo $this->qlink( + $this->translate('Reschedule'), + 'monitoring/host/reschedule-check', + array('host' => $object->getName()), + array( + 'icon' => 'reschedule', + 'data-base-target' => '_self', + 'title' => $this->translate( + 'Schedule the next active check at a different time than the current one' + ) + ) + ); + } + } ?> timeUntil($object->next_check) ?>
    translate('Check attempts') ?> translate('(soft state)') ?> (translate('soft state') ?>) translate('(hard state)') ?> (translate('hard state') ?>)
    translate('Check execution time') ?>check_execution_time ?>s
    translate('Check execution time') ?>check_execution_time === null + ? '-' : round((float) $object->check_execution_time, 3) + ?>s
    translate('Check latency') ?>check_latency ?>s
    translate('Check latency') ?>check_latency ?>s
    translate('Command') ?>escape($command) ?> +?> + +
    translate('Command'); ?> + escape($command); ?> + hasPermission('monitoring/command/schedule-check') && $object->passive_checks_enabled) { + $title = sprintf($this->translate('Submit a one time or so called passive result for the %s check'), $command); + if ($object->getType() === $object::TYPE_HOST) { + echo $this->qlink( + $this->translate('Process check result'), + 'monitoring/host/process-check-result', + array('host' => $object->getName()), + array( + 'icon' => 'reply', + 'data-base-target' => '_self', + 'title' => $title + ) + ); + } else { + echo $this->qlink( + $this->translate('Process check result'), + 'monitoring/service/process-check-result', + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), + array( + 'icon' => 'reply', + 'data-base-target' => '_self', + 'title' => $title + ) + ); + } + } ?> +
    %s%s
    translate('Comments') ?> - getType() === $object::TYPE_HOST) { - $addCommentLink = $this->href( - 'monitoring/host/add-comment', - array('host' => $object->getName()) - ); - } else { - $addCommentLink = $this->href( - 'monitoring/service/add-comment', - array('host' => $object->getHost()->getName(), 'service' => $object->getName()) - ); - } - - ?> - - icon('comment') ?> translate('Add comment') ?> - + translate('Comments'); + if (! empty($object->comments) && $addLink) { + echo '
    ' . $addLink; + } + ?>
    + comments)): + echo $addLink; + else: ?> +
      + comments as $comment): + // Form is unset if the current user lacks the respective permission + if (isset($delCommentForm)) { + $deleteButton = clone($delCommentForm); + $deleteButton->populate( + array( + 'comment_id' => $comment->id, + 'comment_is_service' => isset($comment->service_description) + ) + ); + } else { + $deleteButton = ''; + } + ?> +
    • +

      + escape($comment->author) ?> + timeAgo($comment->timestamp) ?> + +

      +

      + (translate('Comment') ?>): + ', $this->createTicketLinks($comment->comment)) ?> +

      +
    • + +
    +
    escape($comment->author) ?> (timeSince($comment->timestamp) ?>) - - - - - -
    - (type) ?>): - - ', $text) ?> -
    -
    %s%s %s
    %s%s
    %s%s %s
    %s%s
    +

    translate('Custom variables') ?>

    +
    translate('Downtimes') ?> - getType() === $object::TYPE_HOST) { - $scheduleDowntimeLink = $this->href( - 'monitoring/host/schedule-downtime', - array('host' => $object->getName()) - ); - } else { - $scheduleDowntimeLink = $this->href( - 'monitoring/service/schedule-downtime', - array('host' => $object->getHost()->getName(), 'service' => $object->getName()) - ); - } - ?> - - icon('plug') ?> translate('Schedule downtime') ?> - + translate('Downtimes'); + if (! empty($object->downtimes) && $addLink) { + echo '
    ' . $addLink; + } + ?>
    + downtimes)): + echo $addLink; + else: ?> +
      + downtimes as $downtime): + if ((bool) $downtime->is_in_effect) { + $state = sprintf( + $this->translate('expires %s', 'Last format parameter represents the downtime expire time'), + $this->timeUntil($downtime->end) + ); + } else { + if ($downtime->start <= time()) { + $state = sprintf( + $this->translate('ends %s', 'Last format parameter represents the end time'), + $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end) + ); + } elseif ((bool) $downtime->is_fixed) { + $state = sprintf( + $this->translate('scheduled %s', 'Last format parameter represents the time scheduled'), + $this->timeUntil($downtime->start) + ); + } else { + $state = sprintf( + $this->translate('scheduled flexible %s', 'Last format parameter represents the time scheduled'), + $this->timeUntil($downtime->start) + ); + } + } + // Form is unset if the current user lacks the respective permission + if (isset($delDowntimeForm)) { + $deleteButton = clone($delDowntimeForm); + $deleteButton->populate( + array( + 'downtime_id' => $downtime->id, + 'downtime_is_service' => $object->getType() === $object::TYPE_SERVICE + ) + ); + } else { + $deleteButton = ''; + } + ?> +
    • +

      + escape($downtime->author_name) ?> + timeAgo($downtime->entry_time) ?> + +

      +

      + translate('Downtime') ?> + - + ', $this->createTicketLinks($downtime->comment)) ?> +

      +
    • + +
    +
    escape($downtime->author) ?> - - - - - -
    - - - ', $text) ?> -
    -
    - - - - - - - - - - - - -
    > - translate($this->util()->getHostStateName($object->host_state)) ?>
    - prefixedTimeSince($object->host_last_state_change, true) ?> -
    escape($object->host_name) ?>host_address && $object->host_address !== $object->host_name): ?> -
    escape($object->host_address) ?> - -
    - translate($this->util()->getServiceStateName($object->service_state)) ?>
    - prefixedTimeSince($object->service_last_state_change, true) ?> -
    translate('Service') ?>: escape($object->service_description) ?> - - render('show/components/statusIcons.phtml') ?> - -
     
    diff --git a/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml b/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml index 40db3e5c4..377b56f8d 100644 --- a/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml +++ b/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml @@ -4,14 +4,16 @@ if (empty($object->hostgroups)) return; $list = array(); foreach ($object->hostgroups as $name => $alias) { - $list[] = $this->qlink($alias, 'monitoring/list/services', array( - 'hostgroup' => $name - )); + $list[] = $this->qlink( + $alias, + 'monitoring/list/hosts', + array('hostgroup_name' => $name), + array('title' => sprintf($this->translate('List all hosts in the group "%s"'), $alias)) + ); } printf( - "%s%s %s\n", + "%s%s\n", $this->translate('Hostgroups'), - $this->icon('host'), implode(', ', $list) ); diff --git a/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml b/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml deleted file mode 100644 index f42e70278..000000000 --- a/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml +++ /dev/null @@ -1,89 +0,0 @@ - $this->object->host_name)); -$currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); - -?> -

    compact ? ' data-base-target="col1"' : '' ?>> -stats->services_total > 0): ?> -qlink(sprintf($this->translate('%s configured services:'), $object->stats->services_total), $selfUrl) ?> - -translate('No services configured on this host'); ?> - - -stats->services_ok > 0): ?> - qlink( - $object->stats->services_ok, - $selfUrl, - array('service_state' => 0), - array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('ok')))) -) ?> - - 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $state) { - $pre = 'services_' . $state; - if ($object->stats->$pre) { - $handled = $pre . '_handled'; - $unhandled = $pre . '_unhandled'; - $paramsHandled = array('service_state' => $stateId, 'service_handled' => 1); - $paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0); - if ($object->stats->$unhandled) { - $compareUrl = $selfUrl->with($paramsUnhandled)->getRelativeUrl(); - } else { - $compareUrl = $selfUrl->with($paramsHandled)->getRelativeUrl(); - } - - if ($compareUrl === $currentUrl) { - $active = ' active'; - } else { - $active = ''; - } - - echo ''; - if ($object->stats->$unhandled) { - - echo $this->qlink( - $object->stats->$unhandled, - $selfUrl, - $paramsUnhandled, - array('title' => sprintf($this->translate('Unhandled services with state %s'), strtoupper($this->translate($state)))) - ); - } - if ($object->stats->$handled) { - - if ($selfUrl->with($paramsHandled)->getRelativeUrl() === $currentUrl) { - $active = ' active'; - } else { - $active = ''; - } - if ($object->stats->$unhandled) { - echo ''; - } - echo $this->qlink( - $object->stats->$handled, - $selfUrl, - $paramsHandled, - array('title' => sprintf($this->translate('Handled services with state %s'), strtoupper($this->translate($state)))) - ); - if ($object->stats->$unhandled) { - echo "\n"; - } - } - echo "\n"; - } -} - -?> -stats->services_pending): ?> - qlink( - $object->stats->services_pending, - $selfUrl, - array('service_state' => 99), - array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('pending')))) -) ?> - - -

    - diff --git a/modules/monitoring/application/views/scripts/show/components/notes.phtml b/modules/monitoring/application/views/scripts/show/components/notes.phtml new file mode 100644 index 000000000..4a5041e50 --- /dev/null +++ b/modules/monitoring/application/views/scripts/show/components/notes.phtml @@ -0,0 +1,26 @@ +getNotes()); +$links = $object->getNotesUrls(); + +if (! empty($links) || ! empty($notes)): ?> + + translate('Notes') ?> + + '; + } + // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201 + $newTabInfo = sprintf( + ' %s ', + $this->translate('opens in new window') + ); + $linkText = '%s ' . $newTabInfo . ''; + foreach ($links as $i => $link) { + $links[$i] = sprintf($linkText, $this->escape($link), $this->escape($link)); + } + echo implode('
    ', $links); + ?> + + + diff --git a/modules/monitoring/application/views/scripts/show/components/notifications.phtml b/modules/monitoring/application/views/scripts/show/components/notifications.phtml index 5c52e872a..9a9195862 100644 --- a/modules/monitoring/application/views/scripts/show/components/notifications.phtml +++ b/modules/monitoring/application/views/scripts/show/components/notifications.phtml @@ -1,35 +1,66 @@ -state, array(0, 99))) { - return; -} - -?> - translate('Notifications') ?> - -current_notification_number > 0) { - if ((int) $object->current_notification_number === 1) { - $msg = sprintf( - $this->translate('A notication has been sent for this issue %s ago'), - $this->timeSince($object->last_notification) - ); - } else { - $msg = sprintf( - $this->translate('%s notications have been sent for this issue'), - $object->current_notification_number - ) . '
    ' . sprintf( - $this->translate('The last one occured %s ago'), - $this->timeSince($object->last_notification) - ); - } - echo $msg; -} else { - echo $this->translate('No notification has been sent for this issue'); -} -?> - + translate('Notifications') ?> + + hasPermission('monitoring/command/send-custom-notification')) { + if ($object->getType() === $object::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + echo $this->qlink( + $this->translate('Send notification'), + 'monitoring/host/send-custom-notification', + array('host' => $object->getName()), + array( + 'icon' => 'bell', + 'data-base-target' => '_self', + 'title' => $this->translate( + 'Send a custom notification to contacts responsible for this host' + ) + ) + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + echo $this->qlink( + $this->translate('Send notification'), + 'monitoring/service/send-custom-notification', + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), + array( + 'icon' => 'bell', + 'data-base-target' => '_self', + 'title' => $this->translate( + 'Send a custom notification to contacts responsible for this service' + ) + ) + ); + } + if (! in_array((int) $object->state, array(0, 99))) { + echo '
    '; + } + } elseif (in_array((int) $object->state, array(0, 99))) { + echo '-'; + } + // We are not interested in notifications for OK or pending objects + if (! in_array((int) $object->state, array(0, 99))) { + if ($object->current_notification_number > 0) { + if ((int) $object->current_notification_number === 1) { + $msg = sprintf( + $this->translate('A notification has been sent for this issue %s.'), + $this->timeAgo($object->last_notification) + ); + } else { + $msg = sprintf( + $this->translate('%d notifications have been sent for this issue.'), + $object->current_notification_number + ) . '
    ' . sprintf( + $this->translate('The last one was sent %s.'), + $this->timeAgo($object->last_notification) + ); + } + } else { + $msg = $this->translate('No notification has been sent for this issue.'); + } + echo $msg; + } + ?> + diff --git a/modules/monitoring/application/views/scripts/show/components/output.phtml b/modules/monitoring/application/views/scripts/show/components/output.phtml index 52b4c9a56..82230beae 100644 --- a/modules/monitoring/application/views/scripts/show/components/output.phtml +++ b/modules/monitoring/application/views/scripts/show/components/output.phtml @@ -1,5 +1,5 @@
    -

    translate('Plugin Output') ?>

    +

    translate('Plugin Output') ?>

    pluginOutput($object->output) ?> pluginOutput($object->long_output) ?>
    diff --git a/modules/monitoring/application/views/scripts/show/components/perfdata.phtml b/modules/monitoring/application/views/scripts/show/components/perfdata.phtml index 086ee1235..0c59693ac 100644 --- a/modules/monitoring/application/views/scripts/show/components/perfdata.phtml +++ b/modules/monitoring/application/views/scripts/show/components/perfdata.phtml @@ -1,7 +1,11 @@ perfdata) return ?> - - translate('Performance data') ?> - - perfdata($object->perfdata) ?> - + + +

    translate('Performance data') ?>

    + + + + + perfdata($object->perfdata) ?> + diff --git a/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml b/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml index 50b71d839..09ff24835 100644 --- a/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml +++ b/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml @@ -4,15 +4,17 @@ if (empty($object->servicegroups)) return; $list = array(); foreach ($object->servicegroups as $name => $alias) { - $list[] = $this->qlink($alias, 'monitoring/list/services', array( - 'servicegroup' => $name - )); + $list[] = $this->qlink( + $alias, + 'monitoring/list/services', + array('servicegroup_name' => $name), + array('title' => sprintf($this->translate('List all services in the group "%s"'), $alias)) + ); } printf( - "%s%s %s\n", + "%s%s\n", $this->translate('Servicegroups'), - $this->icon('services'), implode(', ', $list) ); diff --git a/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml b/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml deleted file mode 100644 index d76c07cf2..000000000 --- a/modules/monitoring/application/views/scripts/show/components/statusIcons.phtml +++ /dev/null @@ -1,49 +0,0 @@ -object; - -$isService = $o instanceof Service; -$obj = new \stdClass(); -$obj->handled = - ($isService) ? $o->service_handled : $o->host_handled; -$obj->state = - ($isService) ? $o->service_state : $o->host_state; -$obj->acknowledged = - ($isService) ? $o->service_acknowledged : $o->host_acknowledged; -$obj->in_downtime = - ($isService) ? $o->in_downtime : $o->host_in_downtime; -$obj->notifications_enabled = - ($isService) ? $o->notifications_enabled : $o->service_notifications_enabled; -$obj->active_checks_enabled = - ($isService) ? $o->active_checks_enabled : $o->host_active_checks_enabled; -$obj->passive_checks_enabled = - ($isService) ? $o->passive_checks_enabled : $o->host_passive_checks_enabled; - -$i = array(); -if (! $obj->handled && $obj->state > 0) { - $i[] = $this->icon('attention-alt', $this->translate('Unhandled')); -} - -if ($obj->acknowledged && ! $obj->in_downtime) { - $i[] = $this->icon('ok', $this->translate('Acknowledged')); -} - -if (!$obj->notifications_enabled) { - $i[] = $this->icon('bell-of-empty', $this->translate('Notifications Disabled')); -} - -if ($obj->in_downtime) { - $i[] = $this->icon('plug', $this->translate('In Downtime')); -} - -if (! $obj->active_checks_enabled) { - if ($obj->passive_checks_enabled) { - $i[] = $this->icon('eye-off', $this->translate('Active Checks Disabled')); - } else { - $i[] = $this->icon('eye-off', $this->translate('Active And Passive Checks Disabled')); - } -} - -?> diff --git a/modules/monitoring/application/views/scripts/show/contact.phtml b/modules/monitoring/application/views/scripts/show/contact.phtml index 92b84dc73..027301c88 100644 --- a/modules/monitoring/application/views/scripts/show/contact.phtml +++ b/modules/monitoring/application/views/scripts/show/contact.phtml @@ -1,6 +1,8 @@ getHelper('ContactFlags') ?>
    - tabs ?> + compact): ?> + tabs; ?> +

    translate('Contact details') ?>

    %1$s', $this->escape($contact->contact_email)) ?> + translate('Email') ?> + + + escape($contact->contact_email); ?> + + contact_pager): ?> - + translate('Pager') ?> escape($contact->contact_pager) ?> - + translate('Hosts') ?> escape($contactHelper->contactFlags($contact, 'host')) ?>
    escape($contact->contact_notify_host_timeperiod) ?> - + translate('Services') ?> escape($contactHelper->contactFlags($contact, 'service')) ?>
    escape($contact->contact_notify_service_timeperiod) ?> @@ -50,10 +56,15 @@

    translate('Notifications sent to this contact') ?>

    + limiter; ?> + paginator; ?>
    -render('list/notifications.phtml') ?> +partial('list/notifications.phtml', array( + 'notifications' => $notifications, + 'compact' => true +)); ?>
    translate('No notifications have been sent for this contact') ?>
    diff --git a/modules/monitoring/application/views/scripts/show/history.phtml b/modules/monitoring/application/views/scripts/show/history.phtml deleted file mode 100644 index dfc1cbf0e..000000000 --- a/modules/monitoring/application/views/scripts/show/history.phtml +++ /dev/null @@ -1,148 +0,0 @@ -
    - render('show/components/header.phtml'); ?> -

    translate('This Object\'s Event History'); ?>

    - widget('limiter', array('url' => $url, 'max' => $history->count())); ?> - paginationControl($history, null, null, array('preserve' => $this->preserve)); ?> -
    - -
    - - translate('No history available for this object'); ?> -
    - - -qlink($contact, 'monitoring/show/contact', array('contact' => $contact)); - } - return '[' . implode(', ', $links) . ']'; -} -?> - - - - - service_description); - switch ($event->type) { - case 'notify': - $icon = 'notification'; - $title = $this->translate('Notification'); - $stateClass = ( - $isService - ? strtolower($this->util()->getServiceStateName($event->state)) - : strtolower($this->util()->getHostStateName($event->state)) - ); - - $msg = preg_replace_callback( - '/^\[([^\]]+)\]/', - function($match) use ($self) { return contactsLink($match, $self); }, - $this->escape($event->output) - ); - break; - case 'comment': - $icon = 'comment'; - $title = $this->translate('Comment'); - $msg = $this->escape($event->output); - break; - case 'comment_deleted': - $icon = 'remove'; - $title = $this->translate('Comment deleted'); - $msg = $this->escape($event->output); - break; - case 'ack': - $icon = 'acknowledgement'; - $title = $this->translate('Acknowledge'); - $msg = $this->escape($event->output); - break; - case 'ack_deleted': - $icon = 'remove'; - $title = $this->translate('Ack removed'); - $msg = $this->escape($event->output); - break; - case 'dt_comment': - $icon = 'in_downtime'; - $title = $this->translate('In Downtime'); - $msg = $this->escape($event->output); - break; - case 'dt_comment_deleted': - $icon = 'remove'; - $title = $this->translate('Downtime removed'); - $msg = $this->escape($event->output); - break; - case 'flapping': - $icon = 'flapping'; - $title = $this->translate('Flapping'); - $msg = $this->escape($event->output); - break; - case 'flapping_deleted': - $icon = 'remove'; - $title = $this->translate('Flapping stopped'); - $msg = $this->escape($event->output); - break; - case 'hard_state': - $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $this->escape($event->output); - $stateClass = ( - $isService - ? strtolower($this->util()->getServiceStateName($event->state)) - : strtolower($this->util()->getHostStateName($event->state)) - ); - $icon = 'attention-alt'; - $title = strtoupper($stateClass); // TODO: Should be translatable! - break; - case 'soft_state': - $icon = 'spinner'; - $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $this->escape($event->output); - $stateClass = ( - $isService - ? strtolower($this->util()->getServiceStateName($event->state)) - : strtolower($this->util()->getHostStateName($event->state)) - ); - $title = strtoupper($stateClass); // TODO: Should be translatable! - break; - case 'dt_start': - $icon = 'downtime_start'; - $title = $this->translate('Downtime Start'); - $msg = $this->escape($event->output); - break; - case 'dt_end': - $icon = 'downtime_end'; - $title = $this->translate('Downtime End'); - $msg = $this->escape($event->output); - break; - } - ?> - - - - - - -
    - escape($title); ?> -
    - timestamp); ?> -
    tickets ? preg_replace_callback( - $this->tickets->getPattern(), - array($this->tickets, 'createLink'), - $msg -) : $msg; - -?> - - escape($event->service_description) . ' ' . $this->translate('on') . ' ' . $this->escape($event->host_name); ?> - - escape($event->host_name); ?> - -
    -
    - icon($icon, $title); ?> -
    -
    -
    diff --git a/modules/monitoring/application/views/scripts/show/host.phtml b/modules/monitoring/application/views/scripts/show/host.phtml deleted file mode 100644 index c4e0ec9a8..000000000 --- a/modules/monitoring/application/views/scripts/show/host.phtml +++ /dev/null @@ -1,31 +0,0 @@ -host_name !== false): ?> -
    - render('show/components/header.phtml') ?> - render('show/components/hostservicesummary.phtml') ?> -
    -
    - render('show/components/output.phtml') ?> - render('show/components/grapher.phtml') ?> - - - - render('show/components/acknowledgement.phtml') ?> - render('show/components/comments.phtml') ?> - render('show/components/notifications.phtml') ?> - render('show/components/downtime.phtml') ?> - render('show/components/flapping.phtml') ?> - render('show/components/perfdata.phtml') ?> - render('show/components/checksource.phtml') ?> - render('show/components/actions.phtml') ?> - render('show/components/command.phtml') ?> - render('show/components/hostgroups.phtml') ?> - render('show/components/contacts.phtml') ?> - render('show/components/checkstatistics.phtml') ?> - render('show/components/customvars.phtml') ?> - render('show/components/flags.phtml') ?> - -
    -
    - -

    Host not found

    - diff --git a/modules/monitoring/application/views/scripts/show/service.phtml b/modules/monitoring/application/views/scripts/show/service.phtml deleted file mode 100644 index 126d60cb7..000000000 --- a/modules/monitoring/application/views/scripts/show/service.phtml +++ /dev/null @@ -1,31 +0,0 @@ -host_name !== false): ?> -
    - render('show/components/header.phtml') ?> -

    translate("Service detail information") ?>

    -
    -
    - render('show/components/output.phtml') ?> - render('show/components/grapher.phtml') ?> - - - - render('show/components/acknowledgement.phtml') ?> - render('show/components/comments.phtml') ?> - render('show/components/notifications.phtml') ?> - render('show/components/downtime.phtml') ?> - render('show/components/flapping.phtml') ?> - render('show/components/perfdata.phtml') ?> - render('show/components/checksource.phtml') ?> - render('show/components/actions.phtml') ?> - render('show/components/command.phtml') ?> - render('show/components/servicegroups.phtml') ?> - render('show/components/contacts.phtml') ?> - render('show/components/checkstatistics.phtml') ?> - render('show/components/customvars.phtml') ?> - render('show/components/flags.phtml') ?> - -
    -
    - -

    Service not found

    - diff --git a/modules/monitoring/application/views/scripts/show/services.phtml b/modules/monitoring/application/views/scripts/show/services.phtml deleted file mode 100644 index ae4f8f699..000000000 --- a/modules/monitoring/application/views/scripts/show/services.phtml +++ /dev/null @@ -1,5 +0,0 @@ -
    -render('show/components/header.phtml') ?> -render('show/components/hostservicesummary.phtml') ?> -
    - diff --git a/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml b/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml index 5949a84d8..af727bc12 100644 --- a/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml +++ b/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml @@ -1,5 +1,7 @@
    -

    translate('Host- and Servicechecks'); ?>

    +
    +

    translate('Host and Service Checks'); ?>

    +
    @@ -12,66 +14,114 @@ diff --git a/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml b/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml index 8e6d35221..c3a16ae2a 100644 --- a/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml +++ b/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml @@ -1,68 +1,124 @@
    -

    translate('Monitoring Features'); ?>

    +
    +

    translate('Monitoring Features'); ?>

    +
    statusSummary->hosts_without_flap_detection || $this->statusSummary->services_without_flap_detection || $this->statusSummary->hosts_flapping || $this->statusSummary->services_flapping): ?> -
    translate('Flap detection'); ?>
    +
    translate('Flap Detection'); ?>
    -
    translate('Flap detection'); ?>
    +
    translate('Flap Detection'); ?>
    statusSummary->hosts_active): ?> -
    - - statusSummary->hosts_active . ' ' . $this->translate('Active'); ?> - -
    +
    qlink( + sprintf( + $this->translatePlural('%u Active', '%u Active', $this->statusSummary->hosts_active), + $this->statusSummary->hosts_active + ), + 'monitoring/list/hosts', + array('host_active_checks_enabled' => 1), + array('title' => sprintf( + $this->translatePlural( + 'List %u actively checked host', + 'List %u actively checked hosts', + $this->statusSummary->hosts_active + ), + $this->statusSummary->hosts_active + )) + ); ?>
    statusSummary->hosts_passive): ?> -
    - - statusSummary->hosts_passive . ' ' . $this->translate('Passive'); ?> - -
    +
    qlink( + sprintf( + $this->translatePlural('%d Passive', '%d Passive', $this->statusSummary->hosts_passive), + $this->statusSummary->hosts_passive + ), + 'monitoring/list/hosts', + array('host_active_checks_enabled' => 0, 'host_passive_checks_enabled' => 1), + array('title' => sprintf( + $this->translatePlural( + 'List %u passively checked host', + 'List %u passively checked hosts', + $this->statusSummary->hosts_passive + ), + $this->statusSummary->hosts_passive + )) + ); ?>
    statusSummary->hosts_not_checked): ?> -
    - - statusSummary->hosts_not_checked . ' ' . $this->translate('Disabled'); ?> - -
    +
    qlink( + sprintf( + $this->translatePlural('%d Disabled', '%d Disabled', $this->statusSummary->hosts_not_checked), + $this->statusSummary->hosts_not_checked + ), + 'monitoring/list/hosts', + array('host_active_checks_enabled' => 0, 'host_passive_checks_enabled' => 0), + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is not being checked at all', + 'List %u hosts which are not being checked at all', + $this->statusSummary->hosts_not_checked + ), + $this->statusSummary->hosts_not_checked + )) + ); ?>
    statusSummary->services_active): ?> -
    - - statusSummary->services_active . ' ' . $this->translate('Active'); ?> - -
    +
    qlink( + sprintf( + $this->translatePlural('%d Active', '%d Active', $this->statusSummary->services_active), + $this->statusSummary->services_active + ), + 'monitoring/list/services', + array('service_active_checks_enabled' => 1), + array('title' => sprintf( + $this->translatePlural( + 'List %u actively checked service', + 'List %u actively checked services', + $this->statusSummary->services_active + ), + $this->statusSummary->services_active + )) + ); ?>
    statusSummary->services_passive): ?> -
    - - statusSummary->services_passive . ' ' . $this->translate('Passive'); ?> - -
    +
    qlink( + sprintf( + $this->translatePlural('%d Passive', '%d Passive', $this->statusSummary->services_passive), + $this->statusSummary->services_passive + ), + 'monitoring/list/services', + array('service_active_checks_enabled' => 0, 'service_passive_checks_enabled' => 1), + array('title' => sprintf( + $this->translatePlural( + 'List %u passively checked service', + 'List %u passively checked services', + $this->statusSummary->services_passive + ), + $this->statusSummary->services_passive + )) + ); ?>
    statusSummary->services_not_checked): ?> -
    - - statusSummary->services_not_checked . ' ' . $this->translate('Disabled'); ?> - -
    +
    qlink( + sprintf( + $this->translatePlural('%d Disabled', '%d Disabled', $this->statusSummary->services_not_checked), + $this->statusSummary->services_not_checked + ), + 'monitoring/list/services', + array('service_active_checks_enabled' => 0, 'service_passive_checks_enabled' => 0), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is not being checked at all', + 'List %u services which are not being checked at all', + $this->statusSummary->services_not_checked + ), + $this->statusSummary->services_not_checked + )) + ); ?>
    @@ -78,42 +134,70 @@ @@ -121,50 +205,78 @@
    +
    statusSummary->hosts_without_flap_detection): ?> -
    - - translate('%d hosts disabled'), $this->statusSummary->hosts_without_flap_detection); ?> - + array('host_flap_detection_enabled' => 0), + array( + 'class' => 'feature-highlight', + 'title' => sprintf( + $this->translatePlural( + 'List %u host for which flap detection has been disabled', + 'List %u hosts for which flap detection has been disabled', + $this->statusSummary->hosts_without_flap_detection + ), + $this->statusSummary->hosts_without_flap_detection + ) + ) + ); ?> -
    - - translate('All hosts enabled'); ?> - + array('host_flap_detection_enabled' => 1), + array('title' => $this->translate( + 'List all hosts, for which flap detection is enabled entirely' + )) + ); ?> statusSummary->hosts_flapping): ?> - - translate('%d hosts flapping'), $this->statusSummary->hosts_flapping); ?> - + array('host_is_flapping' => 1), + array( + 'class' => 'feature-highlight', + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently flapping', + 'List %u hosts which are currently flapping', + $this->statusSummary->hosts_flapping + ), + $this->statusSummary->hosts_flapping + ) + ) + ); ?>
    +
    statusSummary->services_without_flap_detection): ?> -
    - - translate('%d services disabled'), $this->statusSummary->services_without_flap_detection); ?> - + array('service_flap_detection_enabled' => 0), + array( + 'class' => 'feature-highlight', + 'title' => sprintf( + $this->translatePlural( + 'List %u service for which flap detection has been disabled', + 'List %u services for which flap detection has been disabled', + $this->statusSummary->services_without_flap_detection + ), + $this->statusSummary->services_without_flap_detection + ) + ) + ); ?> -
    - - translate('All services enabled'); ?> - + array('service_flap_detection_enabled' => 1), + array('title' => $this->translate( + 'List all services, for which flap detection is enabled entirely' + )) + ); ?> statusSummary->services_flapping): ?> - - translate('%d services flapping'), $this->statusSummary->services_flapping); ?> - + array('service_is_flapping' => 1), + array( + 'class' => 'feature-highlight', + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently flapping', + 'List %u services which are currently flapping', + $this->statusSummary->services_flapping + ), + $this->statusSummary->services_flapping + ) + ) + ); ?>
    +
    statusSummary->hosts_not_triggering_notifications): ?> -
    - - translate('%d hosts disabled'), $this->statusSummary->hosts_not_triggering_notifications); ?> - + array('host_notifications_enabled' => 0), + array( + 'class' => 'feature-highlight', + 'title' => sprintf( + $this->translatePlural( + 'List %u host for which notifications are suppressed', + 'List %u hosts for which notifications are suppressed', + $this->statusSummary->hosts_not_triggering_notifications + ), + $this->statusSummary->hosts_not_triggering_notifications + ) + ) + ); ?> -
    - - translate('All hosts enabled'); ?> - + array('host_notifications_enabled' => 1), + array('title' => $this->translate( + 'List all hosts, for which notifications are enabled entirely' + )) + ); ?>
    +
    statusSummary->services_not_triggering_notifications): ?> -
    - - translate('%d services disabled'), $this->statusSummary->services_not_triggering_notifications); ?> - + array('service_notifications_enabled' => 0), + array( + 'class' => 'feature-highlight', + 'title' => sprintf( + $this->translatePlural( + 'List %u service for which notifications are suppressed', + 'List %u services for which notifications are suppressed', + $this->statusSummary->services_not_triggering_notifications + ), + $this->statusSummary->services_not_triggering_notifications + ) + ) + ); ?> -
    - - translate('All services enabled'); ?> - + array('service_notifications_enabled' => 1), + array('title' => $this->translate( + 'List all services, for which notifications are enabled entirely' + )) + ); ?>
    statusSummary->hosts_not_processing_event_handlers || $this->statusSummary->services_not_processing_event_handlers): ?> -
    translate('Event handlers'); ?>
    +
    translate('Event Handlers'); ?>
    -
    translate('Event handlers'); ?>
    +
    translate('Event Handlers'); ?>
    diff --git a/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml b/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml index 0543f58c5..7f25b6e5f 100644 --- a/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml +++ b/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml @@ -9,20 +9,44 @@ $service_problems = ( ); ?>
    -statusSummary->hosts_up): ?> -

    - - translate('%d Hosts UP'), $this->statusSummary->hosts_up); ?> - -

    - -statusSummary->hosts_pending): ?> -

    - - translate('%d Hosts PENDING'), $this->statusSummary->hosts_pending); ?> - -

    - +
    + statusSummary->hosts_up): ?> +

    qlink( + sprintf( + $this->translatePlural('%u Host UP', '%u Hosts UP', $this->statusSummary->hosts_up), + $this->statusSummary->hosts_up + ), + 'monitoring/list/hosts', + array('host_state' => 0), + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UP', + 'List %u hosts which are currently in state UP', + $this->statusSummary->hosts_up + ), + $this->statusSummary->hosts_up + )) + ); ?>

    + + statusSummary->hosts_pending): ?> +

    qlink( + sprintf( + $this->translatePlural('%u Host PENDING', '%u Hosts PENDING', $this->statusSummary->hosts_pending), + $this->statusSummary->hosts_pending + ), + 'monitoring/list/hosts', + array('host_state' => 99), + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state PENDING', + 'List %u hosts which are currently in state PENDING', + $this->statusSummary->hosts_pending + ), + $this->statusSummary->hosts_pending + )) + ); ?>

    + +
    statusSummary->hosts_down || $this->statusSummary->hosts_unreachable): ?>
    translate('Services'); ?> diff --git a/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml b/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml index cccfa094e..af88ae441 100644 --- a/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml +++ b/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml @@ -1,280 +1,396 @@ + -
    +
    - - translate('CRITICAL', 'icinga.state') ?> - + array( + 'host_problem' => $host_problem, + 'service_state' => 2, + 'service_acknowledged' => 0, + 'service_in_downtime' => 0 + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state CRITICAL', + 'List %u services which are currently in state CRITICAL', + $services_critical_unhandled + ), + $services_critical_unhandled + )) + ); ?> - - translate('Acknowledged') : $this->translate('CRITICAL', 'icinga.state')); ?> - + array( + 'host_problem' => $host_problem, + 'service_state' => 2, + 'service_handled' => 1 + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state CRITICAL (Acknowledged)', + 'List %u services which are currently in state CRITICAL (Acknowledged)', + $services_critical_handled + ), + $services_critical_handled + )) + ); ?> - - 1) { - printf( - $this->translate('%d are passively checked'), - $services_critical_passive - ); - } else { - printf( - $this->translate('%d is passively checked'), - $services_critical_passive - ); - } - ?> - + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state CRITICAL and passively checked', + 'List %u services which are currently in state CRITICAL and passively checked', + $services_critical_passive + ), + $services_critical_passive + )) + ); ?> - - 1) { - printf( - $this->translate('%d are not checked at all'), - $services_critical_not_checked - ); - } else { - printf( - $this->translate('%d is not checked at all'), - $services_critical_not_checked - ); - } - ?> - + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state CRITICAL and not checked at all', + 'List %u services which are currently in state CRITICAL and not checked at all', + $services_critical_not_checked + ), + $services_critical_not_checked + )) + ); ?>
    -
    +
    - - translate('WARNING', 'icinga.state') ?> - + array( + 'host_problem' => $host_problem, + 'service_state' => 1, + 'service_acknowledged' => 0, + 'service_in_downtime' => 0 + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state WARNING', + 'List %u services which are currently in state WARNING', + $services_warning_unhandled + ), + $services_warning_unhandled + )) + ); ?> - - translate('Acknowledged') : $this->translate('WARNING', 'icinga.state')); ?> - + array( + 'host_problem' => $host_problem, + 'service_state' => 1, + 'service_handled' => 1 + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state WARNING (Acknowledged)', + 'List %u services which are currently in state WARNING (Acknowledged)', + $services_warning_handled + ), + $services_warning_handled + )) + ); ?> - - 1) { - printf( - $this->translate('%d are passively checked'), - $services_warning_passive - ); - } else { - printf( - $this->translate('%d is passively checked'), - $services_warning_passive - ); - } - ?> - + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state WARNING and passively checked', + 'List %u services which are currently in state WARNING and passively checked', + $services_warning_passive + ), + $services_warning_passive + )) + ); ?> - - 1) { - printf( - $this->translate('%d are not checked at all'), - $services_warning_not_checked - ); - } else { - printf( - $this->translate('%d is not checked at all'), - $services_warning_not_checked - ); - } - ?> - + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state WARNING and not checked at all', + 'List %u services which are currently in state WARNING and not checked at all', + $services_warning_not_checked + ), + $services_warning_not_checked + )) + ); ?>
    -
    +
    - - translate('UNKNOWN', 'icinga.state') ?> - + array( + 'host_problem' => $host_problem, + 'service_state' => 3, + 'service_acknowledged' => 0, + 'service_in_downtime' => 0 + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state UNKNOWN', + 'List %u services which are currently in state UNKNOWN', + $services_unknown_unhandled + ), + $services_unknown_unhandled + )) + ); ?> - - translate('Acknowledged') : $this->translate('UNKNOWN', 'icinga.state')); ?> - + array( + 'host_problem' => $host_problem, + 'service_state' => 3, + 'service_handled' => 1 + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state UNKNOWN (Acknowledged)', + 'List %u services which are currently in state UNKNOWN (Acknowledged)', + $services_unknown_handled + ), + $services_unknown_handled + )) + ); ?> - - 1) { - printf( - $this->translate('%d are passively checked'), - $services_unknown_passive - ); - } else { - printf( - $this->translate('%d is passively checked'), - $services_unknown_passive - ); - } - ?> - + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state UNKNOWN and passively checked', + 'List %u services which are currently in state UNKNOWN and passively checked', + $services_unknown_passive + ), + $services_unknown_passive + )) + ); ?> - - 1) { - printf( - $this->translate('%d are not checked at all'), - $services_unknown_not_checked - ); - } else { - printf( - $this->translate('%d is not checked at all'), - $services_unknown_not_checked - ); - } - ?> - + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state UNKNOWN and not checked at all', + 'List %u services which are currently in state UNKNOWN and not checked at all', + $services_unknown_not_checked + ), + $services_unknown_not_checked + )) + ); ?>
    -
    - "> + qlink( + $services_ok . ' ' . Service::getStateText(0, true), 'monitoring/list/services', - array('host_problem' => $host_problem, 'service_state' => 0) - ); ?>"> - translate('OK', 'icinga.state') ?> - + array( + 'host_problem' => $host_problem, + 'service_state' => 0 + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state OK', + 'List %u services which are currently in state OK', + $services_ok + ), + $services_ok + )) + ); ?> - - 1) { - printf( - $this->translate('%d are not checked at all'), - $services_ok_not_checked - ); - } else { - printf( - $this->translate('%d is not checked at all'), - $services_ok_not_checked - ); - } - ?> - + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state OK and not checked at all', + 'List %u services which are currently in state OK and not checked at all', + $services_ok_not_checked + ), + $services_ok_not_checked + )) + ); ?>
    -
    - "> + qlink( + $services_pending . ' ' . Service::getStateText(99, true), 'monitoring/list/services', - array('host_problem' => $host_problem, 'service_state' => 99) - ); ?>"> - translate('PENDING', 'icinga.state') ?> - + array( + 'host_problem' => $host_problem, + 'service_state' => 99 + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state PENDING', + 'List %u services which are currently in state PENDING', + $services_pending + ), + $services_pending + )) + ); ?> - - 1) { - printf( - $this->translate('%d are not checked at all'), - $services_pending_not_checked - ); - } else { - printf( - $this->translate('%d is not checked at all'), - $services_pending_not_checked - ); - } - ?> - + ), + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state PENDING and not checked at all', + 'List %u services which are currently in state PENDING and not checked at all', + $services_pending_not_checked + ), + $services_pending_not_checked + )) + ); ?>
    - + \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml b/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml index ba14a1b0d..faeb98668 100644 --- a/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml +++ b/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml @@ -4,20 +4,48 @@ echo ' handled'; } ?>"> -statusSummary->hosts_down): ?> -

    - - translate('%d Hosts DOWN'), $this->statusSummary->hosts_down); ?> - -

    - -statusSummary->hosts_unreachable): ?> -

    - - translate('%d Hosts UNREACHABLE'), $this->statusSummary->hosts_unreachable); ?> - -

    - +
    + statusSummary->hosts_down): ?> +

    qlink( + sprintf( + $this->translatePlural('%u Host DOWN', '%u Hosts DOWN', $this->statusSummary->hosts_down), + $this->statusSummary->hosts_down + ), + 'monitoring/list/hosts', + array('host_state' => 1), + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state DOWN', + 'List %u hosts which are currently in state DOWN', + $this->statusSummary->hosts_down + ), + $this->statusSummary->hosts_down + )) + ); ?>

    + + statusSummary->hosts_unreachable): ?> +

    qlink( + sprintf( + $this->translatePlural( + '%u Host UNREACHABLE', + '%u Hosts UNREACHABLE', + $this->statusSummary->hosts_unreachable + ), + $this->statusSummary->hosts_unreachable + ), + 'monitoring/list/hosts', + array('host_state' => 2), + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UNREACHABLE', + 'List %u hosts which are currently in state UNREACHABLE', + $this->statusSummary->hosts_unreachable + ), + $this->statusSummary->hosts_unreachable + )) + ); ?>

    + +
    translate('Services'); ?> partial( diff --git a/modules/monitoring/application/views/scripts/timeline/index.phtml b/modules/monitoring/application/views/scripts/timeline/index.phtml index a6e32ac08..209eb0f1b 100644 --- a/modules/monitoring/application/views/scripts/timeline/index.phtml +++ b/modules/monitoring/application/views/scripts/timeline/index.phtml @@ -3,13 +3,12 @@ use Icinga\Web\Url; use Icinga\Util\Color; $groupInfo = $timeline->getGroupInfo(); -$firstRow = !$beingExtended; +$firstRow = ! $beingExtended; -?> - +if (! $beingExtended && !$this->compact): ?>
    - tabs ?> -
    + tabs; ?> +
    @@ -21,25 +20,62 @@ $firstRow = !$beingExtended;
    + +

    - +getInterval()) { + case '1d': + $titleTime = sprintf( + $this->translate('on %s', 'timeline.link.title.time'), + $timeInfo[0]->end->format('d/m/Y') + ); + break; + case '1w': + $titleTime = sprintf( + $this->translate('in week %s of %s', 'timeline.link.title.week.and.year'), + $timeInfo[0]->end->format('W'), + $timeInfo[0]->end->format('Y') + ); + break; + case '1m': + $titleTime = sprintf( + $this->translate('in %s', 'timeline.link.title.month.and.year'), + $timeInfo[0]->end->format('F Y') + ); + break; + case '1y': + $titleTime = sprintf( + $this->translate('in %s', 'timeline.link.title.year'), + $timeInfo[0]->end->format('Y') + ); + break; + default: + $titleTime = sprintf( + $this->translate('between %s and %s', 'timeline.link.title.datetime.twice'), + $timeInfo[0]->end->format('d/m/Y g:i A'), + $timeInfo[0]->start->format('d/m/Y g:i A') + ); + } ?>
    - - - end->format($intervalFormat); ?> - - + qlink( + $timeInfo[0]->end->format($intervalFormat), + '/monitoring/list/eventhistory', + array( + 'timestamp<' => $timeInfo[0]->start->getTimestamp(), + 'timestamp>' => $timeInfo[0]->end->getTimestamp() + ), + array('title' => sprintf( + $this->translate('List all event records registered %s', 'timeline.link.title'), + $titleTime + )), + false + ); ?> $labelAndColor): ?> getExtrapolatedCircleWidth($timeInfo[1][$g $circleWidth ); ?>"> - + ); ?>
    @@ -82,10 +130,10 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
    - Monitoring Module Configuration + +## Overview + +Apart from its web configuration capabilities, the local configuration is +stored in `/etc/icingaweb2` by default (depending on your config setup). + + Location | File | Description + ------------------------------|-----------------------|--------------------------- + modules/monitoring | Directory | `monitoring` module specific configuration + modules/monitoring | config.ini | Security settings (e.g. protected custom vars) for the `monitoring` module + modules/monitoring | backends.ini | Backend type and resources (e.g. Icinga IDO DB) + modules/monitoring | [instances.ini](instances.md#instances) | Instances and their transport (e.g. local external command pipe) + + + diff --git a/modules/monitoring/doc/instances.md b/modules/monitoring/doc/instances.md index dd04ea30c..ea8b3f5b8 100644 --- a/modules/monitoring/doc/instances.md +++ b/modules/monitoring/doc/instances.md @@ -3,7 +3,8 @@ ## Abstract The instance.ini defines how icingaweb accesses the command pipe of your icinga process in order to submit external -commands. When you are at the root of your icingaweb installation you can find it under ./config/modules/monitoring/instances.ini. +commands. Depending on the config path (default: /etc/icingaweb2) of your icingaweb installation you can find it +under ./modules/monitoring/instances.ini. ## Syntax @@ -33,5 +34,22 @@ setup key authentication at the endpoint and allow your icingweb's user to acces port=22 ; the port to use (22 if none is given) user=jdoe ; the user to authenticate with +You can also make use of the ssh resource for accessing an icinga pipe with key-based authentication, which will give +you the possibility to define the location of the private key for a specific user, let's have a look: + + [icinga] + path=/usr/local/icinga/var/rw/icinga.cmd ; the path on the remote machine where the icinga.cmd can be found + host=my.remote.machine.com ; the hostname or address of the remote machine + port=22 ; the port to use (22 if none is given) + resource=ssh ; the ssh resource which contains the username and the location of the private key + +And the associated ssh resource: + + [ssh] + type = "ssh" + user = "ssh-user" + private_key = "/etc/icingaweb2/ssh/ssh-user" + + diff --git a/modules/monitoring/doc/security.md b/modules/monitoring/doc/security.md new file mode 100644 index 000000000..cdfe7215c --- /dev/null +++ b/modules/monitoring/doc/security.md @@ -0,0 +1,56 @@ +# Security + +The monitoring module provides an additional set of restrictions and permissions +that can be used for access control. The following sections will list those +restrictions and permissions in detail: + + +## Permissions + +The Icinga Web 2 monitoring module can send commands to the current Icinga2 instance +through the command pipe. A user needs specific permissions to be able to send those +commands when using the monitoring module. + + +| Name | Permits | +|---------------------------------------------|-----------------------------------------------------------------------------| +| monitoring/command/* | Allow all commands | +| monitoring/command/schedule-check | Allow scheduling host and service checks' | +| monitoring/command/acknowledge-problem | Allow acknowledging host and service problems | +| monitoring/command/remove-acknowledgement | Allow removing problem acknowledgements | +| monitoring/command/comment/* | Allow adding and deleting host and service comments | +| monitoring/command/comment/add | Allow commenting on hosts and services | +| monitoring/command/downtime/delete | Allow deleting host and service downtimes' | +| monitoring/command/process-check-result | Allow processing host and service check results | +| monitoring/command/feature/instance | Allow processing commands for toggling features on an instance-wide basis | +| monitoring/command/feature/object | Allow processing commands for toggling features on host and service objects | +| monitoring/command/send-custom-notification | Allow sending custom notifications for hosts and services | + + +## Restrictions + +The monitoring module allows filtering objects: + + +| Keys | Restricts | +|----------------------------|-----------------------------------------------| +| monitoring/filter/objects | Applies a filter to all hosts and services | + + +This filter will affect all hosts and services. Furthermore, it will also +affect all related objects, like notifications, downtimes and events. If a +service is hidden, all notifications, downtimes on that service will be hidden too. + + +### Filter Column Names + +The following filter column names are available in filter expressions: + + +| Column | +|--------------------------------------------------------------| +| host_name | +| hostgroup_name | +| service_description | +| servicegroup_name | +| + all custom variables prefixed with `_host_` or `_service_` | diff --git a/modules/monitoring/library/Monitoring/Backend.php b/modules/monitoring/library/Monitoring/Backend.php index e3672dcd5..f7d9e6d46 100644 --- a/modules/monitoring/library/Monitoring/Backend.php +++ b/modules/monitoring/library/Monitoring/Backend.php @@ -1,4 +1,5 @@ array( - 'comment_internal_id' => 'cm.internal_comment_id', - 'comment_data' => 'cm.comment_data', - 'comment_author' => 'cm.author_name COLLATE latin1_general_ci', - 'comment_timestamp' => 'UNIX_TIMESTAMP(cm.comment_time)', - 'comment_type' => "CASE cm.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END", - 'comment_is_persistent' => 'cm.is_persistent', - 'comment_expiration' => 'CASE cm.expires WHEN 1 THEN UNIX_TIMESTAMP(cm.expiration_time) ELSE NULL END', - 'comment_host' => 'CASE WHEN ho.name1 IS NULL THEN so.name1 ELSE ho.name1 END COLLATE latin1_general_ci', - 'host' => 'CASE WHEN ho.name1 IS NULL THEN so.name1 ELSE ho.name1 END COLLATE latin1_general_ci', // #7278, #7279 - 'comment_service' => 'so.name2 COLLATE latin1_general_ci', - 'service' => 'so.name2 COLLATE latin1_general_ci', // #7278, #7279 - 'comment_objecttype' => "CASE WHEN ho.object_id IS NOT NULL THEN 'host' ELSE CASE WHEN so.object_id IS NOT NULL THEN 'service' ELSE NULL END END", + 'comment_author' => 'c.comment_author', + 'comment_author_name' => 'c.comment_author_name', + 'comment_data' => 'c.comment_data', + 'comment_expiration' => 'c.comment_expiration', + 'comment_internal_id' => 'c.comment_internal_id', + 'comment_is_persistent' => 'c.comment_is_persistent', + 'comment_timestamp' => 'c.comment_timestamp', + 'comment_type' => 'c.comment_type', + 'object_type' => 'c.object_type' + ), + 'hosts' => array( + 'host_display_name' => 'c.host_display_name', + 'host_name' => 'c.host_name', + 'host_state' => 'c.host_state' + ), + 'services' => array( + 'service_description' => 'c.service_description', + 'service_display_name' => 'c.service_display_name', + 'service_host_name' => 'c.service_host_name', + 'service_state' => 'c.service_state' ) ); + /** + * The union + * + * @var Zend_Db_Select + */ + protected $commentQuery; + + /** + * Subqueries used for the comment query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { + $this->commentQuery = $this->db->select(); $this->select->from( - array('cm' => $this->prefix . 'comments'), + array('c' => $this->commentQuery), array() ); - $this->select->joinLeft( - array('ho' => $this->prefix . 'objects'), - 'cm.object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', - array() - ); - $this->select->joinLeft( - array('so' => $this->prefix . 'objects'), - 'cm.object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2', - array() - ); - $this->joinedVirtualTables = array('comments' => true); + $this->joinedVirtualTables['comments'] = true; + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys($this->columnMap['comments'] + $this->columnMap['hosts']); + foreach (array_keys($this->columnMap['services']) as $column) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + $hosts = $this->createSubQuery('hostcomment', $columns); + $this->subQueries[] = $hosts; + $this->commentQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys($this->columnMap['comments'] + $this->columnMap['hosts'] + $this->columnMap['services']); + $services = $this->createSubQuery('servicecomment', $columns); + $this->subQueries[] = $services; + $this->commentQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php index 4ee17de6a..30be5c400 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php @@ -1,54 +1,153 @@ array( - 'state_time' => 'h.deletion_time', - 'timestamp' => 'UNIX_TIMESTAMP(h.deletion_time)', - 'raw_timestamp' => 'h.deletion_time', - 'object_id' => 'h.object_id', - 'type' => "(CASE h.entry_type WHEN 1 THEN 'comment_deleted' WHEN 2 THEN 'dt_comment_deleted' WHEN 3 THEN 'flapping_deleted' WHEN 4 THEN 'ack_deleted' END)", - 'state' => '(NULL)', - 'state_type' => '(NULL)', - 'output' => "('[' || h.author_name || '] ' || h.comment_data)", - 'attempt' => '(NULL)', - 'max_attempts' => '(NULL)', - - 'host' => 'o.name1 COLLATE latin1_general_ci', - 'service' => 'o.name2 COLLATE latin1_general_ci', - 'host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'service_host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'object_type' => "CASE WHEN o.objecttype_id = 1 THEN 'host' ELSE 'service' END" + 'object_type' => 'cdh.object_type' + ), + 'history' => array( + 'type' => 'cdh.type', + 'timestamp' => 'cdh.timestamp', + 'object_id' => 'cdh.object_id', + 'state' => 'cdh.state', + 'output' => 'cdh.output' + ), + 'hosts' => array( + 'host_display_name' => 'cdh.host_display_name', + 'host_name' => 'cdh.host_name' + ), + 'services' => array( + 'service_description' => 'cdh.service_description', + 'service_display_name' => 'cdh.service_display_name', + 'service_host_name' => 'cdh.service_host_name' ) ); - public function whereToSql($col, $sign, $expression) - { - if ($col === 'UNIX_TIMESTAMP(h.deletion_time)') { - return 'h.deletion_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); - } else { - return parent::whereToSql($col, $sign, $expression); - } - } + /** + * The union + * + * @var Zend_Db_Select + */ + protected $commentDeletionHistoryQuery; + /** + * Subqueries used for the comment history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { + $this->commentDeletionHistoryQuery = $this->db->select(); $this->select->from( - array('o' => $this->prefix . 'objects'), - array() - )->join( - array('h' => $this->prefix . 'commenthistory'), - 'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-02 00:00:00' AND h.entry_type <> 2", + array('cdh' => $this->commentDeletionHistoryQuery), array() ); - $this->joinedVirtualTables = array('commenthistory' => true); + $this->joinedVirtualTables['commenthistory'] = true; } + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostcommentdeletionhistory', $columns); + $this->subQueries[] = $hosts; + $this->commentDeletionHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicecommentdeletionhistory', $columns); + $this->subQueries[] = $services; + $this->commentDeletionHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php index e3231d932..b8fdd72a4 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php @@ -1,54 +1,153 @@ array( - 'state_time' => 'h.comment_time', - 'timestamp' => 'UNIX_TIMESTAMP(h.comment_time)', - 'raw_timestamp' => 'h.comment_time', - 'object_id' => 'h.object_id', - 'type' => "(CASE h.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'dt_comment' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END)", - 'state' => '(NULL)', - 'state_type' => '(NULL)', - 'output' => "('[' || h.author_name || '] ' || h.comment_data)", - 'attempt' => '(NULL)', - 'max_attempts' => '(NULL)', - - 'host' => 'o.name1 COLLATE latin1_general_ci', - 'service' => 'o.name2 COLLATE latin1_general_ci', - 'host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'service_host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'object_type' => "CASE WHEN o.objecttype_id = 1 THEN 'host' ELSE 'service' END" + 'object_type' => 'ch.object_type' + ), + 'history' => array( + 'type' => 'ch.type', + 'timestamp' => 'ch.timestamp', + 'object_id' => 'ch.object_id', + 'state' => 'ch.state', + 'output' => 'ch.output' + ), + 'hosts' => array( + 'host_display_name' => 'ch.host_display_name', + 'host_name' => 'ch.host_name' + ), + 'services' => array( + 'service_description' => 'ch.service_description', + 'service_display_name' => 'ch.service_display_name', + 'service_host_name' => 'ch.service_host_name' ) ); - public function whereToSql($col, $sign, $expression) - { - if ($col === 'UNIX_TIMESTAMP(h.comment_time)') { - return 'h.comment_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); - } else { - return parent::whereToSql($col, $sign, $expression); - } - } + /** + * The union + * + * @var Zend_Db_Select + */ + protected $commentHistoryQuery; + /** + * Subqueries used for the comment history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { + $this->commentHistoryQuery = $this->db->select(); $this->select->from( - array('o' => $this->prefix . 'objects'), - array() - )->join( - array('h' => $this->prefix . 'commenthistory'), - 'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1 AND h.entry_type <> 2', + array('ch' => $this->commentHistoryQuery), array() ); - $this->joinedVirtualTables = array('commenthistory' => true); + $this->joinedVirtualTables['commenthistory'] = true; } + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostcommenthistory', $columns); + $this->subQueries[] = $hosts; + $this->commentHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicecommenthistory', $columns); + $this->subQueries[] = $services; + $this->commentHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php index 467e40fc0..cd72c4782 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php @@ -1,51 +1,76 @@ array( - 'contact_id' => 'c.contact_id', - 'contact_name' => 'co.name1 COLLATE latin1_general_ci', - 'contact_alias' => 'c.alias COLLATE latin1_general_ci', - 'contact_email' => 'c.email_address COLLATE latin1_general_ci', - 'contact_pager' => 'c.pager_address', - 'contact_object_id' => 'c.contact_object_id', - 'contact_has_host_notfications' => 'c.host_notifications_enabled', - 'contact_has_service_notfications' => 'c.service_notifications_enabled', - 'contact_can_submit_commands' => 'c.can_submit_commands', - 'contact_notify_service_recovery' => 'c.notify_service_recovery', - 'contact_notify_service_warning' => 'c.notify_service_warning', - 'contact_notify_service_critical' => 'c.notify_service_critical', - 'contact_notify_service_unknown' => 'c.notify_service_unknown', - 'contact_notify_service_flapping' => 'c.notify_service_flapping', - 'contact_notify_service_downtime' => 'c.notify_service_recovery', - 'contact_notify_host_recovery' => 'c.notify_host_recovery', - 'contact_notify_host_down' => 'c.notify_host_down', - 'contact_notify_host_unreachable' => 'c.notify_host_unreachable', - 'contact_notify_host_flapping' => 'c.notify_host_flapping', - 'contact_notify_host_downtime' => 'c.notify_host_downtime', + 'contact_id' => 'c.contact_id', + 'contact' => 'co.name1 COLLATE latin1_general_ci', + 'contact_name' => 'co.name1', + 'contact_alias' => 'c.alias COLLATE latin1_general_ci', + 'contact_email' => 'c.email_address COLLATE latin1_general_ci', + 'contact_pager' => 'c.pager_address', + 'contact_object_id' => 'c.contact_object_id', + 'contact_has_host_notfications' => 'c.host_notifications_enabled', + 'contact_has_service_notfications' => 'c.service_notifications_enabled', + 'contact_can_submit_commands' => 'c.can_submit_commands', + 'contact_notify_service_recovery' => 'c.notify_service_recovery', + 'contact_notify_service_warning' => 'c.notify_service_warning', + 'contact_notify_service_critical' => 'c.notify_service_critical', + 'contact_notify_service_unknown' => 'c.notify_service_unknown', + 'contact_notify_service_flapping' => 'c.notify_service_flapping', + 'contact_notify_service_downtime' => 'c.notify_service_recovery', + 'contact_notify_host_recovery' => 'c.notify_host_recovery', + 'contact_notify_host_down' => 'c.notify_host_down', + 'contact_notify_host_unreachable' => 'c.notify_host_unreachable', + 'contact_notify_host_flapping' => 'c.notify_host_flapping', + 'contact_notify_host_downtime' => 'c.notify_host_downtime' ), 'timeperiods' => array( - 'contact_notify_host_timeperiod' => 'ht.alias COLLATE latin1_general_ci', + 'contact_notify_host_timeperiod' => 'ht.alias COLLATE latin1_general_ci', 'contact_notify_service_timeperiod' => 'st.alias COLLATE latin1_general_ci' ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), 'hosts' => array( - 'host_object_id' => 'ho.object_id', - 'host_name' => 'ho.name1 COLLATE latin1_general_ci', - 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' ), 'services' => array( - 'service_object_id' => 'so.object_id', - 'service_host_name' => 'so.name1 COLLATE latin1_general_ci', - 'service' => 'so.name1 COLLATE latin1_general_ci', - 'service_description' => 'so.name2 COLLATE latin1_general_ci', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' ) ); + /** + * {@inheritdoc} + */ protected function joinBaseTables() { $this->select->from( @@ -53,46 +78,15 @@ class ContactQuery extends IdoQuery array() )->join( array('co' => $this->prefix . 'objects'), - 'c.contact_object_id = co.' . $this->object_id . ' AND co.is_active = 1', - array() - ); - $this->joinedVirtualTables = array('contacts' => true); - } - - protected function joinHosts() - { - $this->select->join( - array('hc' => $this->prefix . 'host_contacts'), - 'hc.contact_object_id = c.contact_object_id', - array() - )->join( - array('h' => $this->prefix . 'hosts'), - 'hc.host_id = h.host_id', - array() - )->join( - array('ho' => $this->prefix . 'objects'), - 'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1', - array() - ); - } - - protected function joinServices() - { - $this->select->join( - array('sc' => $this->prefix . 'service_contacts'), - 'sc.contact_object_id = c.contact_object_id', - array() - )->join( - array('s' => $this->prefix . 'services'), - 'sc.service_id = s.service_id', - array() - )->join( - array('so' => $this->prefix . 'objects'), - 's.service_object_id = so.' . $this->object_id . ' AND so.is_active = 1', + 'co.object_id = c.contact_object_id AND co.is_active = 1', array() ); + $this->joinedVirtualTables['contacts'] = true; } + /** + * Join timeperiods + */ protected function joinTimeperiods() { $this->select->joinLeft( @@ -106,4 +100,103 @@ class ContactQuery extends IdoQuery array() ); } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('hosts'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->joinLeft( + array('hc' => $this->prefix . 'host_contacts'), + 'hc.contact_object_id = c.contact_object_id', + array() + )->joinLeft( + array('h' => $this->prefix . 'hosts'), + 'h.host_id = hc.host_id', + array() + )->joinLeft( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = h.host_object_id AND ho.is_active = 1', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.servicegroup_id = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('sc' => $this->prefix . 'service_contacts'), + 'sc.contact_object_id = c.contact_object_id', + array() + )->joinLeft( + array('s' => $this->prefix . 'services'), + 's.service_id = sc.service_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hosts') || $this->hasJoinedVirtualTable('services')) { + $group = array('c.contact_id', 'co.object_id'); + if ($this->hasJoinedVirtualTable('timeperiods')) { + $group[] = 'ht.timeperiod_id'; + $group[] = 'st.timeperiod_id'; + } + } + + return $group; + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php index 346f2458a..83934d811 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php @@ -1,23 +1,35 @@ array( 'contactgroup' => 'cgo.name1 COLLATE latin1_general_ci', - 'contactgroup_name' => 'cgo.name1 COLLATE latin1_general_ci', - 'contactgroup_alias' => 'cg.alias', + 'contactgroup_name' => 'cgo.name1', + 'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci' ), 'contacts' => array( + 'contact_id' => 'c.contact_id', 'contact' => 'co.name1 COLLATE latin1_general_ci', - 'contact_name' => 'co.name1 COLLATE latin1_general_ci', - 'contact_alias' => 'c.alias', - 'contact_email' => 'c.email_address', + 'contact_name' => 'co.name1', + 'contact_alias' => 'c.alias COLLATE latin1_general_ci', + 'contact_email' => 'c.email_address COLLATE latin1_general_ci', 'contact_pager' => 'c.pager_address', + 'contact_object_id' => 'c.contact_object_id', 'contact_has_host_notfications' => 'c.host_notifications_enabled', 'contact_has_service_notfications' => 'c.service_notifications_enabled', 'contact_can_submit_commands' => 'c.can_submit_commands', @@ -31,21 +43,35 @@ class ContactgroupQuery extends IdoQuery 'contact_notify_host_down' => 'c.notify_host_down', 'contact_notify_host_unreachable' => 'c.notify_host_unreachable', 'contact_notify_host_flapping' => 'c.notify_host_flapping', - 'contact_notify_host_downtime' => 'c.notify_host_downtime', + 'contact_notify_host_downtime' => 'c.notify_host_downtime' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' ), 'hosts' => array( - 'host' => 'ho.name1', - 'host_name' => 'ho.name1' + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' ), 'services' => array( 'service' => 'so.name2 COLLATE latin1_general_ci', - 'service_description' => 'so.name2 COLLATE latin1_general_ci', - 'service_host_name' => 'so.name1 COLLATE latin1_general_ci' + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' ) ); - protected $useSubqueryCount = true; - + /** + * {@inheritdoc} + */ protected function joinBaseTables() { $this->select->from( @@ -53,81 +79,135 @@ class ContactgroupQuery extends IdoQuery array() )->join( array('cgo' => $this->prefix . 'objects'), - 'cg.contactgroup_object_id = cgo.' . $this->object_id . ' AND cgo.is_active = 1', + 'cgo.object_id = cg.contactgroup_object_id AND cgo.is_active = 1 AND cgo.objecttype_id = 11', array() ); - - $this->joinedVirtualTables = array('contactgroups' => true); + $this->joinedVirtualTables['contactgroups'] = true; } + /** + * Join contacts + */ protected function joinContacts() { - $this->select->distinct()->join( + $this->select->joinLeft( array('cgm' => $this->prefix . 'contactgroup_members'), 'cgm.contactgroup_id = cg.contactgroup_id', array() - )->join( + )->joinLeft( array('co' => $this->prefix . 'objects'), - 'cgm.contact_object_id = co.object_id AND co.is_active = 1', + 'co.object_id = cgm.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10', array() - )->join( + )->joinLeft( array('c' => $this->prefix . 'contacts'), 'c.contact_object_id = co.object_id', array() ); } + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('hosts'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ protected function joinHosts() { - $this->select->distinct()->join( + $this->select->joinLeft( array('hcg' => $this->prefix . 'host_contactgroups'), 'hcg.contactgroup_object_id = cg.contactgroup_object_id', array() - )->join( + )->joinLeft( array('h' => $this->prefix . 'hosts'), - 'hcg.host_id = h.host_id', + 'h.host_id = hcg.host_id', array() - )->join( + )->joinLeft( array('ho' => $this->prefix . 'objects'), - 'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1', + 'ho.object_id = h.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', array() ); } + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.servicegroup_id = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ protected function joinServices() { - $scgSub = $this->db->select()->distinct()->from( - $this->prefix . 'service_contactgroups', - array('contactgroup_object_id', 'service_id') - ); - - /* - This subselect is a workaround for a fucking stupid bug. Other tables - may be affected too. We absolutely need uniqueness here. - - mysql> SELECT * FROM icinga_service_contactgroups WHERE - contactgroup_object_id = 143 AND service_id = 2079564; - +-------------------------+-------------+------------+------------------------+ - | service_contactgroup_id | instance_id | service_id | contactgroup_object_id | - +-------------------------+-------------+------------+------------------------+ - | 4904240 | 1 | 2079564 | 143 | - | 4904244 | 1 | 2079564 | 143 | - +-------------------------+-------------+------------+------------------------+ - */ - - $this->select->distinct()->join( + $this->select->joinLeft( array('scg' => $this->prefix . 'service_contactgroups'), - // array('scg' => $scgSub), 'scg.contactgroup_object_id = cg.contactgroup_object_id', array() - )->join( + )->joinLeft( array('s' => $this->prefix . 'services'), - 'scg.service_id = s.service_id', + 's.service_id = scg.service_id', array() - )->join( + )->joinLeft( array('so' => $this->prefix . 'objects'), - 's.service_object_id = so.' . $this->object_id . ' AND so.is_active = 1', + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', array() ); } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hosts') || $this->hasJoinedVirtualTable('services')) { + $group = array('cg.contactgroup_id', 'cgo.object_id'); + if ($this->hasJoinedVirtualTable('contacts')) { + $group[] = 'c.contact_id'; + $group[] = 'co.object_id'; + } + } elseif ($this->hasJoinedVirtualTable('contacts')) { + $group = array( + 'cg.contactgroup_id', + 'cgo.object_id', + 'c.contact_id', + 'co.object_id' + ); + } + + return $group; + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php index 04e17ddcd..3bf54756a 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php @@ -1,6 +1,5 @@ array( 'host' => 'cvo.name1 COLLATE latin1_general_ci', - 'host_name' => 'cvo.name1 COLLATE latin1_general_ci', - 'service_host_name' => 'cvo.name1 COLLATE latin1_general_ci', - 'service' => 'cvo.name2 COLLATE latin1_general_ci', - 'service_description' => 'cvo.name2 COLLATE latin1_general_ci', - 'contact_name' => 'cvo.name1 COLLATE latin1_general_ci', + 'host_name' => 'cvo.name1', + 'service' => 'cvo.name2 COLLATE latin1_general_ci', + 'service_description' => 'cvo.name2', + 'contact' => 'cvo.name1 COLLATE latin1_general_ci', + 'contact_name' => 'cvo.name1', 'object_type' => "CASE cvo.objecttype_id WHEN 1 THEN 'host' WHEN 2 THEN 'service' WHEN 10 THEN 'contact' ELSE 'invalid' END", 'object_type_id' => 'cvo.objecttype_id' -// 'object_type' => "CASE cvo.objecttype_id WHEN 1 THEN 'host' WHEN 2 THEN 'service' WHEN 3 THEN 'hostgroup' WHEN 4 THEN 'servicegroup' WHEN 5 THEN 'hostescalation' WHEN 6 THEN 'serviceescalation' WHEN 7 THEN 'hostdependency' WHEN 8 THEN 'servicedependency' WHEN 9 THEN 'timeperiod' WHEN 10 THEN 'contact' WHEN 11 THEN 'contactgroup' WHEN 12 THEN 'command' ELSE 'other' END" +// 'object_type' => "CASE cvo.objecttype_id WHEN 1 THEN 'host' WHEN 2 THEN 'service' WHEN 3 THEN 'hostgroup' WHEN 4 THEN 'servicegroup' WHEN 5 THEN 'hostescalation' WHEN 6 THEN 'serviceescalation' WHEN 7 THEN 'hostdependency' WHEN 8 THEN 'servicedependency' WHEN 9 THEN 'timeperiod' WHEN 10 THEN 'contact' WHEN 11 THEN 'contactgroup' WHEN 12 THEN 'command' ELSE 'other' END" ), ); diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php index a53fd18e9..1127a537c 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php @@ -1,73 +1,133 @@ array( - 'downtime_author' => 'sd.author_name', - 'author' => 'sd.author_name', - 'downtime_comment' => 'sd.comment_data', - 'downtime_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)', - 'downtime_is_fixed' => 'sd.is_fixed', - 'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END', - 'downtime_triggered_by_id' => 'sd.triggered_by_id', - 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)', - 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', - 'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)", - 'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END', - 'downtime_duration' => 'sd.duration', - 'downtime_is_in_effect' => 'sd.is_in_effect', - 'downtime_internal_id' => 'sd.internal_downtime_id', - 'downtime_host' => 'CASE WHEN ho.name1 IS NULL THEN so.name1 ELSE ho.name1 END COLLATE latin1_general_ci', // #7278, #7279 - 'host' => 'CASE WHEN ho.name1 IS NULL THEN so.name1 ELSE ho.name1 END COLLATE latin1_general_ci', - 'downtime_service' => 'so.name2 COLLATE latin1_general_ci', - 'service' => 'so.name2 COLLATE latin1_general_ci', // #7278, #7279 - 'downtime_objecttype' => "CASE WHEN ho.object_id IS NOT NULL THEN 'host' ELSE CASE WHEN so.object_id IS NOT NULL THEN 'service' ELSE NULL END END", - 'downtime_host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', - 'downtime_service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END' + 'downtimes' => array( + 'downtime_author' => 'd.downtime_author', + 'downtime_author_name' => 'd.downtime_author_name', + 'downtime_comment' => 'd.downtime_comment', + 'downtime_duration' => 'd.downtime_duration', + 'downtime_end' => 'd.downtime_end', + 'downtime_entry_time' => 'd.downtime_entry_time', + 'downtime_internal_id' => 'd.downtime_internal_id', + 'downtime_is_fixed' => 'd.downtime_is_fixed', + 'downtime_is_flexible' => 'd.downtime_is_flexible', + 'downtime_is_in_effect' => 'd.downtime_is_in_effect', + 'downtime_scheduled_end' => 'd.downtime_scheduled_end', + 'downtime_scheduled_start' => 'd.downtime_scheduled_start', + 'downtime_start' => 'd.downtime_start', + 'object_type' => 'd.object_type' ), + 'hosts' => array( + 'host_display_name' => 'd.host_display_name', + 'host_name' => 'd.host_name', + 'host_state' => 'd.host_state' + ), + 'services' => array( + 'service_description' => 'd.service_description', + 'service_display_name' => 'd.service_display_name', + 'service_host_name' => 'd.service_host_name', + 'service_state' => 'd.service_state' + ) ); /** - * Join with scheduleddowntime + * The union + * + * @var Zend_Db_Select + */ + protected $downtimeQuery; + + /** + * Subqueries used for the downtime query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} */ protected function joinBaseTables() { + $this->downtimeQuery = $this->db->select(); $this->select->from( - array('sd' => $this->prefix . 'scheduleddowntime'), + array('d' => $this->downtimeQuery), array() ); - $this->select->joinLeft( - array('ho' => $this->prefix . 'objects'), - 'sd.object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', - array() - ); - $this->select->joinLeft( - array('so' => $this->prefix . 'objects'), - 'sd.object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2', - array() - ); - $this->select->joinLeft( - array('hs' => $this->prefix . 'hoststatus'), - 'ho.object_id = hs.host_object_id', - array() - ); - $this->select->joinLeft( - array('ss' => $this->prefix . 'servicestatus'), - 'so.object_id = ss.service_object_id', - array() - ); - $this->joinedVirtualTables = array('downtime' => true); + $this->joinedVirtualTables['downtimes'] = true; + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys($this->columnMap['downtimes'] + $this->columnMap['hosts']); + foreach (array_keys($this->columnMap['services']) as $column) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + $hosts = $this->createSubQuery('hostdowntime', $columns); + $this->subQueries[] = $hosts; + $this->downtimeQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys($this->columnMap['downtimes'] + $this->columnMap['hosts'] + $this->columnMap['services']); + $services = $this->createSubQuery('servicedowntime', $columns); + $this->subQueries[] = $services; + $this->downtimeQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php index f9ec831a7..08b6cec01 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php @@ -1,54 +1,153 @@ array( - 'state_time' => 'h.actual_end_time', - 'timestamp' => 'UNIX_TIMESTAMP(h.actual_end_time)', - 'raw_timestamp' => 'h.actual_end_time', - 'object_id' => 'h.object_id', - 'type' => "('dt_end')", - 'state' => '(NULL)', - 'state_type' => '(NULL)', - 'output' => "('[' || h.author_name || '] ' || h.comment_data)", - 'attempt' => '(NULL)', - 'max_attempts' => '(NULL)', - - 'host' => 'o.name1 COLLATE latin1_general_ci', - 'service' => 'o.name2 COLLATE latin1_general_ci', - 'host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'service_host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'object_type' => "CASE WHEN o.objecttype_id = 1 THEN 'host' ELSE 'service' END" + 'object_type' => 'deh.object_type' + ), + 'history' => array( + 'type' => 'deh.type', + 'timestamp' => 'deh.timestamp', + 'object_id' => 'deh.object_id', + 'state' => 'deh.state', + 'output' => 'deh.output' + ), + 'hosts' => array( + 'host_display_name' => 'deh.host_display_name', + 'host_name' => 'deh.host_name' + ), + 'services' => array( + 'service_description' => 'deh.service_description', + 'service_display_name' => 'deh.service_display_name', + 'service_host_name' => 'deh.service_host_name' ) ); - public function whereToSql($col, $sign, $expression) - { - if ($col === 'UNIX_TIMESTAMP(h.actual_end_time)') { - return 'h.actual_end_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); - } else { - return parent::whereToSql($col, $sign, $expression); - } - } + /** + * The union + * + * @var Zend_Db_Select + */ + protected $downtimeEndHistoryQuery; + /** + * Subqueries used for the downtime end history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { + $this->downtimeEndHistoryQuery = $this->db->select(); $this->select->from( - array('o' => $this->prefix . 'objects'), + array('deh' => $this->downtimeEndHistoryQuery), array() - )->join( - array('h' => $this->prefix . 'downtimehistory'), - 'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1', - array() - )->where('h.actual_end_time > ?', '1970-01-02 00:00:00'); - $this->joinedVirtualTables = array('downtimehistory' => true); + ); + $this->joinedVirtualTables['downtimehistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostdowntimeendhistory', $columns); + $this->subQueries[] = $hosts; + $this->downtimeEndHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicedowntimeendhistory', $columns); + $this->subQueries[] = $services; + $this->downtimeEndHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; } } - diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php index 01c56cd4d..e312480e7 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php @@ -1,54 +1,153 @@ array( - 'state_time' => 'h.actual_start_time', - 'timestamp' => 'UNIX_TIMESTAMP(h.actual_start_time)', - 'raw_timestamp' => 'h.actual_start_time', - 'object_id' => 'h.object_id', - 'type' => "('dt_start')", - 'state' => '(NULL)', - 'state_type' => '(NULL)', - 'output' => "('[' || h.author_name || '] ' || h.comment_data)", - 'attempt' => '(NULL)', - 'max_attempts' => '(NULL)', - - 'host' => 'o.name1 COLLATE latin1_general_ci', - 'service' => 'o.name2 COLLATE latin1_general_ci', - 'host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'service_host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'object_type' => "CASE WHEN o.objecttype_id = 1 THEN 'host' ELSE 'service' END" + 'object_type' => 'dsh.object_type' + ), + 'history' => array( + 'type' => 'dsh.type', + 'timestamp' => 'dsh.timestamp', + 'object_id' => 'dsh.object_id', + 'state' => 'dsh.state', + 'output' => 'dsh.output' + ), + 'hosts' => array( + 'host_display_name' => 'dsh.host_display_name', + 'host_name' => 'dsh.host_name' + ), + 'services' => array( + 'service_description' => 'dsh.service_description', + 'service_display_name' => 'dsh.service_display_name', + 'service_host_name' => 'dsh.service_host_name' ) ); - public function whereToSql($col, $sign, $expression) - { - if ($col === 'UNIX_TIMESTAMP(h.actual_start_time)') { - return 'h.actual_start_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); - } else { - return parent::whereToSql($col, $sign, $expression); - } - } + /** + * The union + * + * @var Zend_Db_Select + */ + protected $downtimeStartHistoryQuery; + /** + * Subqueries used for the downtime start history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { + $this->downtimeStartHistoryQuery = $this->db->select(); $this->select->from( - array('o' => $this->prefix . 'objects'), + array('dsh' => $this->downtimeStartHistoryQuery), array() - )->join( - array('h' => $this->prefix . 'downtimehistory'), - 'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1', - array() - )->where('h.actual_start_time > ?', '1970-01-02 00:00:00'); - $this->joinedVirtualTables = array('downtimehistory' => true); + ); + $this->joinedVirtualTables['downtimehistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostdowntimestarthistory', $columns); + $this->subQueries[] = $hosts; + $this->downtimeStartHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicedowntimestarthistory', $columns); + $this->subQueries[] = $services; + $this->downtimeStartHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; } } - diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventHistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventHistoryQuery.php deleted file mode 100644 index 2d5a0a2d8..000000000 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventHistoryQuery.php +++ /dev/null @@ -1,120 +0,0 @@ - array( - 'cnt_notification' => "SUM(CASE eh.type WHEN 'notify' THEN 1 ELSE 0 END)", - 'cnt_hard_state' => "SUM(CASE eh.type WHEN 'hard_state' THEN 1 ELSE 0 END)", - 'cnt_soft_state' => "SUM(CASE eh.type WHEN 'hard_state' THEN 1 ELSE 0 END)", - 'cnt_downtime_start' => "SUM(CASE eh.type WHEN 'dt_start' THEN 1 ELSE 0 END)", - 'cnt_downtime_end' => "SUM(CASE eh.type WHEN 'dt_end' THEN 1 ELSE 0 END)", - 'host' => 'eho.name1 COLLATE latin1_general_ci', - 'service' => 'eho.name2 COLLATE latin1_general_ci', - 'host_name' => 'eho.name1 COLLATE latin1_general_ci', - 'service_description' => 'eho.name2 COLLATE latin1_general_ci', - 'object_type' => 'eh.object_type', - 'timestamp' => 'eh.timestamp', - 'state' => 'eh.state', - 'attempt' => 'eh.attempt', - 'max_attempts' => 'eh.max_attempts', - 'output' => 'eh.output', // we do not want long_output - 'type' => 'eh.type', - 'service_host_name' => 'eho.name1 COLLATE latin1_general_ci', - 'service_description' => 'eho.name2 COLLATE latin1_general_ci' - ), - 'hostgroups' => array( - 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', - ), - ); - - protected $useSubqueryCount = true; - - protected function joinBaseTables() - { - $columns = array( - 'timestamp', - 'object_id', - 'type', - 'output', - 'state', - 'state_type', - 'object_type', - 'attempt', - 'max_attempts', - ); - $this->subQueries = array( - $this->createSubQuery('Statehistory', $columns), - $this->createSubQuery('Downtimestarthistory', $columns), - $this->createSubQuery('Downtimeendhistory', $columns), - $this->createSubQuery('Commenthistory', $columns), - $this->createSubQuery('Commentdeletionhistory', $columns), - $this->createSubQuery('Notificationhistory', $columns) - ); - $sub = $this->db->select()->union($this->subQueries, Zend_Db_Select::SQL_UNION_ALL); - - $this->select->from( - array('eho' => $this->prefix . 'objects'), - '*' - )->join( - array('eh' => $sub), - 'eho.' . $this->object_id . ' = eh.' . $this->object_id . ' AND eho.is_active = 1', - array() - ); - $this->joinedVirtualTables = array('eventhistory' => true); - } - - public function order($columnOrAlias, $dir = null) - { - foreach ($this->subQueries as $sub) { - $sub->requireColumn($columnOrAlias); - } - - return parent::order($columnOrAlias, $dir); - } - - public function addFilter(Filter $filter) - { - foreach ($this->subQueries as $sub) { - $sub->applyFilter(clone $filter); - } - return $this; - } - - public function where($condition, $value = null) - { - $this->requireColumn($condition); - foreach ($this->subQueries as $sub) { - $sub->where($condition, $value); - } - return $this; - } - - protected function joinHostgroups() - { - $this->select->join( - array('hgm' => $this->prefix . 'hostgroup_members'), - 'hgm.host_object_id = eho.object_id', - array() - )->join( - array('hg' => $this->prefix . 'hostgroups'), - 'hgm.hostgroup_id = hg.' . $this->hostgroup_id, - array() - )->join( - array('hgo' => $this->prefix . 'objects'), - 'hgo.' . $this->object_id. ' = hg.hostgroup_object_id' . ' AND hgo.is_active = 1', - array() - ); - return $this; - } - -} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php index 88a120db2..cae57b8e0 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php @@ -1,90 +1,57 @@ array( - 'day' => 'DATE(sh.state_time)', - 'cnt_events' => 'COUNT(*)', - 'objecttype_id' => 'sho.objecttype_id', - 'cnt_up' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 0 THEN 1 ELSE 0 END)', - 'cnt_down_hard' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 1 AND state_type = 1 THEN 1 ELSE 0 END)', - 'cnt_down' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 1 THEN 1 ELSE 0 END)', - 'cnt_unreachable_hard' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 2 AND state_type = 1 THEN 1 ELSE 0 END)', - 'cnt_unreachable' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 2 THEN 1 ELSE 0 END)', - 'cnt_unknown_hard' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 3 AND state_type = 1 THEN 1 ELSE 0 END)', - 'cnt_unknown' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 3 THEN 1 ELSE 0 END)', - 'cnt_unknown_hard' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 3 AND state_type = 1 THEN 1 ELSE 0 END)', - 'cnt_critical' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 2 THEN 1 ELSE 0 END)', - 'cnt_critical_hard' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 2 AND state_type = 1 THEN 1 ELSE 0 END)', - 'cnt_warning' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 1 THEN 1 ELSE 0 END)', - 'cnt_warning_hard' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 1 AND state_type = 1 THEN 1 ELSE 0 END)', - 'cnt_ok' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 0 THEN 1 ELSE 0 END)', - 'host' => 'sho.name1 COLLATE latin1_general_ci', - 'service' => 'sho.name2 COLLATE latin1_general_ci', - 'host_name' => 'sho.name1 COLLATE latin1_general_ci', - 'service_description' => 'sho.name2 COLLATE latin1_general_ci', - 'timestamp' => 'UNIX_TIMESTAMP(sh.state_time)' - ), - - 'servicegroups' => array( - 'servicegroup' => 'sgo.name1' - ), - - 'hostgroups' => array( - 'hostgroup' => 'hgo.name1' - ) + /** + * The columns additionally provided by this query + * + * @var array + */ + protected $additionalColumns = array( + 'day' => 'DATE(FROM_UNIXTIME(sth.timestamp))', + 'cnt_up' => "SUM(CASE WHEN sth.object_type = 'host' AND sth.state = 0 THEN 1 ELSE 0 END)", + 'cnt_down_hard' => "SUM(CASE WHEN sth.object_type = 'host' AND sth.state = 1 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_down' => "SUM(CASE WHEN sth.object_type = 'host' AND sth.state = 1 THEN 1 ELSE 0 END)", + 'cnt_unreachable_hard' => "SUM(CASE WHEN sth.object_type = 'host' AND sth.state = 2 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_unreachable' => "SUM(CASE WHEN sth.object_type = 'host' AND sth.state = 2 THEN 1 ELSE 0 END)", + 'cnt_unknown_hard' => "SUM(CASE WHEN sth.object_type = 'service' AND sth.state = 3 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_unknown' => "SUM(CASE WHEN sth.object_type = 'service' AND sth.state = 3 THEN 1 ELSE 0 END)", + 'cnt_unknown_hard' => "SUM(CASE WHEN sth.object_type = 'service' AND sth.state = 3 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_critical' => "SUM(CASE WHEN sth.object_type = 'service' AND sth.state = 2 THEN 1 ELSE 0 END)", + 'cnt_critical_hard' => "SUM(CASE WHEN sth.object_type = 'service' AND sth.state = 2 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_warning' => "SUM(CASE WHEN sth.object_type = 'service' AND sth.state = 1 THEN 1 ELSE 0 END)", + 'cnt_warning_hard' => "SUM(CASE WHEN sth.object_type = 'service' AND sth.state = 1 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_ok' => "SUM(CASE WHEN sth.object_type = 'service' AND sth.state = 0 THEN 1 ELSE 0 END)" ); + /** + * {@inheritdoc} + */ protected function joinBaseTables() { - $this->select->from( - array('sh' => $this->prefix . 'statehistory'), - array() - )->join( - array('sho' => $this->prefix . 'objects'), - 'sh.object_id = sho.object_id AND sho.is_active = 1', - array() - ) - ->group('DATE(sh.state_time)'); - $this->joinedVirtualTables = array('statehistory' => true); + parent::joinBaseTables(); + $this->requireVirtualTable('history'); + $this->columnMap['statehistory'] += $this->additionalColumns; + $this->select->group(array('DATE(FROM_UNIXTIME(sth.timestamp))')); } - protected function joinHostgroups() + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) { - $this->select->join( - array('hgm' => $this->prefix . 'hostgroup_members'), - 'hgm.host_object_id = sho.object_id', - array() - )->join( - array('hgs' => $this->prefix . 'hostgroups'), - 'hgm.hostgroup_id = hgs.hostgroup_id', - array() - )->join( - array('hgo' => $this->prefix . 'objects'), - 'hgo.object_id = hgs.hostgroup_object_id', - array() - ); - } + if (array_key_exists($columnOrAlias, $this->additionalColumns)) { + $subQueries = $this->subQueries; + $this->subQueries = array(); + parent::order($columnOrAlias, $dir); + $this->subQueries = $subQueries; + } else { + parent::order($columnOrAlias, $dir); + } - protected function joinServicegroups() - { - $this->select->join( - array('sgm' => $this->prefix . 'servicegroup_members'), - 'sgm.service_object_id = sho.object_id', - array() - )->join( - array('sgs' => $this->prefix . 'servicegroups'), - 'sgm.servicegroup_id = sgs.servicegroup_id', - array() - )->join( - array('sgo' => $this->prefix . 'objects'), - 'sgo.object_id = sgs.servicegroup_object_id', - array() - ); + return $this; } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php new file mode 100644 index 000000000..e14fc6e86 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php @@ -0,0 +1,111 @@ + array( + 'cnt_notification' => "SUM(CASE eh.type WHEN 'notify' THEN 1 ELSE 0 END)", + 'cnt_hard_state' => "SUM(CASE eh.type WHEN 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_soft_state' => "SUM(CASE eh.type WHEN 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_downtime_start' => "SUM(CASE eh.type WHEN 'dt_start' THEN 1 ELSE 0 END)", + 'cnt_downtime_end' => "SUM(CASE eh.type WHEN 'dt_end' THEN 1 ELSE 0 END)", + 'host_name' => 'eh.host_name', + 'service_description' => 'eh.service_description', + 'object_type' => 'eh.object_type', + 'timestamp' => 'eh.timestamp', + 'state' => 'eh.state', + 'output' => 'eh.output', + 'type' => 'eh.type', + 'host_display_name' => 'eh.host_display_name', + 'service_display_name' => 'eh.service_display_name' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $columns = array( + 'timestamp', + 'output', + 'type', + 'state', + 'object_type', + 'object_id', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + ); + $this->subQueries = array( + $this->createSubQuery('Statehistory', $columns), + $this->createSubQuery('Downtimestarthistory', $columns), + $this->createSubQuery('Downtimeendhistory', $columns), + $this->createSubQuery('Commenthistory', $columns), + $this->createSubQuery('Commentdeletionhistory', $columns), + $this->createSubQuery('Notification', $columns) + ); + $sub = $this->db->select()->union($this->subQueries, Zend_Db_Select::SQL_UNION_ALL); + $this->select->from(array('eh' => $sub), array()); + $this->joinedVirtualTables['eventhistory'] = true; + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php index 186a1e63e..da33c2bc6 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php @@ -1,99 +1,128 @@ array( - 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', - 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)', - 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', - 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', - 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)', - 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', - 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', - 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)', - 'hostgroup' => 'hostgroup' + 'hoststatussummary' => array( + 'hostgroup' => 'hostgroup COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hostgroup_alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hostgroup_name', + 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', + 'hosts_down_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN state_change ELSE 0 END)', + 'hosts_down_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN state_change ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)', + 'hosts_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 99 THEN state_change ELSE 0 END)', + 'hosts_severity' => 'MAX(CASE WHEN object_type = \'host\' THEN severity ELSE 0 END)', + 'hosts_total' => 'SUM(CASE WHEN object_type = \'host\' THEN 1 ELSE 0 END)', + 'hosts_unreachable_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN state_change ELSE 0 END)', + 'hosts_unreachable_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN state_change ELSE 0 END)', + 'hosts_up_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 0 THEN state_change ELSE 0 END)' ), - 'servicestatussummary' => array( - 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', - 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', - 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', - 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', - 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', - 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)', - 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', - 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)', - 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', - 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', - 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', - 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', - 'services_severity' => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)', - 'services_ok_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 0 THEN state_change ELSE 0 END)', - 'services_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 99 THEN state_change ELSE 0 END)', - 'services_warning_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', + 'servicestatussummary' => array( + 'servicegroup' => 'servicegroup COLLATE latin1_general_ci', + 'servicegroup_alias' => 'servicegroup_alias COLLATE latin1_general_ci', + 'servicegroup_name' => 'servicegroup_name', + 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', 'services_critical_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', - 'services_unknown_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', - 'services_warning_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', 'services_critical_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', + 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', + 'services_ok_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 0 THEN state_change ELSE 0 END)', + 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', + 'services_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 99 THEN state_change ELSE 0 END)', + 'services_severity' => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)', + 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', + 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', + 'services_unknown_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', 'services_unknown_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', - 'servicegroup' => 'servicegroup' + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', + 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', + 'services_warning_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', + 'services_warning_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)' ) ); + /** + * {@inheritdoc} + */ + protected $useSubqueryCount = true; + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { $columns = array( 'object_type', - 'host_state', + 'host_state' ); - // Prepend group column since we'll use columns index 0 later for grouping - if (in_array('servicegroup', $this->desiredColumns)) { - array_unshift($columns, 'servicegroup'); + if (in_array('servicegroup', $this->desiredColumns) || in_array('servicegroup_name', $this->desiredColumns)) { + $columns[] = 'servicegroup'; + $columns[] = 'servicegroup_name'; + $columns[] = 'servicegroup_alias'; + $groupColumns = array('servicegroup_name', 'servicegroup_alias'); } else { - array_unshift($columns, 'hostgroup'); + $columns[] = 'hostgroup'; + $columns[] = 'hostgroup_name'; + $columns[] = 'hostgroup_alias'; + $groupColumns = array('hostgroup_name', 'hostgroup_alias'); } $hosts = $this->createSubQuery( 'Hoststatus', $columns + array( - 'state' => 'host_state', - 'acknowledged' => 'host_acknowledged', - 'in_downtime' => 'host_in_downtime', - 'state_change' => 'host_last_state_change', - 'severity' => 'host_severity' + 'state' => 'host_state', + 'acknowledged' => 'host_acknowledged', + 'in_downtime' => 'host_in_downtime', + 'state_change' => 'host_last_state_change', + 'severity' => 'host_severity' ) ); - if (in_array('servicegroup', $this->desiredColumns)) { - $hosts->group(array('sgo.name1', 'ho.object_id', 'state', 'acknowledged', 'in_downtime')); + if (in_array('servicegroup_name', $this->desiredColumns)) { + $hosts->group(array( + 'sgo.name1', + 'ho.object_id', + 'sg.alias', + 'state', + 'acknowledged', + 'in_downtime', + 'state_change', + 'severity' + )); } $services = $this->createSubQuery( 'Status', $columns + array( - 'state' => 'service_state', - 'acknowledged' => 'service_acknowledged', - 'in_downtime' => 'service_in_downtime', - 'state_change' => 'service_last_state_change', - 'severity' => 'service_severity' + 'state' => 'service_state', + 'acknowledged' => 'service_acknowledged', + 'in_downtime' => 'service_in_downtime', + 'state_change' => 'service_last_state_change', + 'severity' => 'service_severity' ) ); - - $groupColumn = 'hostgroup'; - - if (in_array('servicegroup', $this->desiredColumns)) { - $groupColumn = 'servicegroup'; - } - $union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); - $this->select->from(array('statussummary' => $union), array())->group(array($groupColumn)); - + $this->select->from(array('statussummary' => $union), array())->group($groupColumns); $this->joinedVirtualTables = array( 'servicestatussummary' => true, 'hoststatussummary' => true diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php new file mode 100644 index 000000000..6fae0c658 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php @@ -0,0 +1,173 @@ + array( + 'comment_author' => 'c.author_name COLLATE latin1_general_ci', + 'comment_author_name' => 'c.author_name', + 'comment_data' => 'c.comment_data', + 'comment_expiration' => 'CASE c.expires WHEN 1 THEN UNIX_TIMESTAMP(c.expiration_time) ELSE NULL END', + 'comment_internal_id' => 'c.internal_comment_id', + 'comment_is_persistent' => 'c.is_persistent', + 'comment_timestamp' => 'UNIX_TIMESTAMP(c.comment_time)', + 'comment_type' => "CASE c.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END", + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_type' => '(\'host\')' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'hoststatus' => array( + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('c' => $this->prefix . 'comments'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = c.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['comments'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('services')) { + $group = array('c.comment_id', 'ho.object_id'); + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('hoststatus')) { + $group[] = 'hs.hoststatus_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php new file mode 100644 index 000000000..a9c8f5f82 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php @@ -0,0 +1,41 @@ +timestampForSql($this->valueToTimestamp($expression)); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(); + $this->select->where("hch.deletion_time > '1970-01-02 00:00:00'"); + $this->columnMap['history']['timestamp'] = str_replace( + 'comment_time', + 'deletion_time', + $this->columnMap['history']['timestamp'] + ); + $this->columnMap['history']['type'] = str_replace( + 'END)', + "END || '_deleted')", + $this->columnMap['history']['type'] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php new file mode 100644 index 000000000..1931b6638 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php @@ -0,0 +1,167 @@ + array( + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_type' => '(\'host\')' + ), + 'history' => array( + 'type' => "(CASE hch.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'dt_comment' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END)", + 'timestamp' => 'UNIX_TIMESTAMP(hch.comment_time)', + 'object_id' => 'hch.object_id', + 'state' => '(-1)', + 'output' => "('[' || hch.author_name || '] ' || hch.comment_data)", + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + /** + * {@inheritdoc} + */ + public function whereToSql($col, $sign, $expression) + { + if ($col === 'UNIX_TIMESTAMP(hch.comment_time)') { + return 'hch.comment_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('hch' => $this->prefix . 'commenthistory'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hch.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['commenthistory'] = true; + $this->joinedVirtualTables['history'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('services')) { + $group = array('hch.commenthistory_id', 'ho.object_id'); + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php new file mode 100644 index 000000000..2776bcdc2 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php @@ -0,0 +1,179 @@ + array( + 'downtime_author' => 'sd.author_name COLLATE latin1_general_ci', + 'downtime_author_name' => 'sd.author_name', + 'downtime_comment' => 'sd.comment_data', + 'downtime_duration' => 'sd.duration', + 'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END', + 'downtime_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)', + 'downtime_internal_id' => 'sd.internal_downtime_id', + 'downtime_is_fixed' => 'sd.is_fixed', + 'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END', + 'downtime_is_in_effect' => 'sd.is_in_effect', + 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', + 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)', + 'downtime_start' => 'UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)', + 'downtime_triggered_by_id' => 'sd.triggered_by_id', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_type' => '(\'host\')' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'hoststatus' => array( + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sd' => $this->prefix . 'scheduleddowntime'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'sd.object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['downtimes'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('servicegroups') || $this->hasJoinedVirtualTable('services')) { + $group = array('sd.scheduleddowntime_id', 'ho.object_id'); + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('hoststatus')) { + $group[] = 'hs.hoststatus_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php new file mode 100644 index 000000000..8ef831f80 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php @@ -0,0 +1,39 @@ +timestampForSql( + $this->valueToTimestamp($expression) + ); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(true); + $this->select->where("hdh.actual_end_time > '1970-01-02 00:00:00'"); + $this->columnMap['history']['type'] = "('dt_end')"; + $this->columnMap['history']['timestamp'] = str_replace( + 'actual_start_time', + 'actual_end_time', + $this->columnMap['history']['timestamp'] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php new file mode 100644 index 000000000..118f7e526 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php @@ -0,0 +1,176 @@ + array( + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_type' => '(\'host\')' + ), + 'history' => array( + 'type' => "('dt_start')", + 'timestamp' => 'UNIX_TIMESTAMP(hdh.actual_start_time)', + 'object_id' => 'hdh.object_id', + 'state' => '(-1)', + 'output' => "('[' || hdh.author_name || '] ' || hdh.comment_data)", + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + /** + * {@inheritdoc} + */ + public function whereToSql($col, $sign, $expression) + { + if ($col === 'UNIX_TIMESTAMP(hdh.actual_start_time)') { + return 'hdh.actual_start_time ' . $sign . ' ' . $this->timestampForSql( + $this->valueToTimestamp($expression) + ); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('hdh' => $this->prefix . 'downtimehistory'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hdh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + + if (@func_get_arg(0) === false) { + $this->select->where( + "hdh.actual_start_time > '1970-01-02 00:00:00'" + ); + } + + $this->joinedVirtualTables['downtimehistory'] = true; + $this->joinedVirtualTables['history'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('services')) { + $group = array('hdh.downtimehistory_id', 'ho.object_id'); + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php index eedb2f1e9..5144953c2 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php @@ -1,24 +1,51 @@ array( - 'hostgroups' => 'hgo.name1 COLLATE latin1_general_ci', - 'hostgroup_name' => 'hgo.name1 COLLATE latin1_general_ci', - 'hostgroup_alias' => 'hg.alias', - 'id' => 'hg.hostgroup_id', + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hostobjects' => array( + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1' ), 'hosts' => array( - 'host' => 'ho.name1 COLLATE latin1_general_ci', - 'host_name' => 'ho.name1 COLLATE latin1_general_ci' + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' ) ); + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { $this->select->from( @@ -26,22 +53,89 @@ class HostgroupQuery extends IdoQuery array() )->join( array('hgo' => $this->prefix . 'objects'), - 'hg.hostgroup_object_id = hgo.' . $this->object_id . ' AND hgo.is_active = 1', + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', array() ); - $this->joinedVirtualTables = array('hostgroups' => true); + $this->joinedVirtualTables['hostgroups'] = true; } - protected function joinHosts() + /** + * Join host objects + */ + protected function joinHostobjects() { - $this->select->join( + $this->select->joinLeft( array('hgm' => $this->prefix . 'hostgroup_members'), 'hgm.hostgroup_id = hg.hostgroup_id', array() - )->join( + )->joinLeft( array('ho' => $this->prefix . 'objects'), - 'hgm.host_object_id = ho.object_id AND ho.is_active = 1', + 'hgm.host_object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', array() ); } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('hostobjects'); + $this->select->joinLeft( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->requireVirtualTable('hosts'); + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = h.host_object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostobjects')) { + $group = array('hg.hostgroup_id', 'hgo.object_id'); + } + + return $group; + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php new file mode 100644 index 000000000..ce5e0f85b --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php @@ -0,0 +1,146 @@ + array( + 'hostgroup' => 'hostgroup COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hostgroup_alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hostgroup_name', + 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND handled != 0 THEN 1 ELSE 0 END)', + 'hosts_down_handled_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND handled != 0 THEN state_change ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)', + 'hosts_down_unhandled_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND handled = 0 THEN state_change ELSE 0 END)', + 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)', + 'hosts_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 99 THEN state_change ELSE 0 END)', + 'hosts_severity' => 'MAX(CASE WHEN object_type = \'host\' THEN severity ELSE 0 END)', + 'hosts_total' => 'SUM(CASE WHEN object_type = \'host\' THEN 1 ELSE 0 END)', + 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND handled != 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND handled != 0 THEN state_change ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND handled = 0 THEN state_change ELSE 0 END)', + 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', + 'hosts_up_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 0 THEN state_change ELSE 0 END)', + 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state = 0 THEN 1 ELSE 0 END)', + 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', + 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', + 'services_severity' => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)', + 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', + 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state = 0 THEN 1 ELSE 0 END)', + 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state = 0 THEN 1 ELSE 0 END)', + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $summaryQuery; + + /** + * Subqueries used for the summary query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + // TODO(el): Allow to switch between hard and soft states + $hosts = $this->createSubQuery( + 'Hoststatus', + array( + 'handled' => 'host_handled', + 'host_state' => new Zend_Db_Expr('NULL'), + 'hostgroup_alias', + 'hostgroup_name', + 'object_type', + 'severity' => 'host_severity', + 'state' => 'host_state', + 'state_change' => 'host_last_state_change' + ) + ); + $hosts->select()->where('hgo.name1 IS NOT NULL'); // TODO(9458): Should be possible using our filters! + $this->subQueries[] = $hosts; + $services = $this->createSubQuery( + 'Servicestatus', + array( + 'handled' => 'service_handled', + 'host_state' => 'host_hard_state', + 'hostgroup_alias', + 'hostgroup_name', + 'object_type', + 'severity' => new Zend_Db_Expr('NULL'), + 'state' => 'service_state', + 'state_change' => 'service_last_state_change' + ) + ); + $services->select()->where('hgo.name1 IS NOT NULL'); // TODO(9458): Should be possible using our filters! + $this->subQueries[] = $services; + $this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); + $this->select->from(array('statussummary' => $this->summaryQuery), array()); + $this->group(array('statussummary.hostgroup_name', 'statussummary.hostgroup_alias')); + $this->joinedVirtualTables['hoststatussummary'] = true; + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + if (! $this->hasAliasName($columnOrAlias)) { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php new file mode 100644 index 000000000..f2bbe5f47 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php @@ -0,0 +1,257 @@ + array( + 'notification_output' => 'hn.output', + 'notification_start_time' => 'UNIX_TIMESTAMP(hn.start_time)', + 'notification_state' => 'hn.state', + 'notification_object_id' => 'hn.object_id', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_type' => '(\'host\')' + ), + 'history' => array( + 'type' => "('notify')", + 'timestamp' => 'UNIX_TIMESTAMP(hn.start_time)', + 'object_id' => 'hn.object_id', + 'state' => 'hn.state', + 'output' => null + ), + 'contactnotifications' => array( + 'contact' => 'cno.name1 COLLATE latin1_general_ci', + 'notification_contact_name' => 'cno.name1', + 'contact_object_id' => 'cno.object_id' + ), + 'acknowledgements' => array( + 'acknowledgement_entry_time' => 'UNIX_TIMESTAMP(a.entry_time)', + 'acknowledgement_author_name' => 'a.author_name', + 'acknowledgement_comment_data' => 'a.comment_data' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + /** + * {@inheritdoc} + */ + public function whereToSql($col, $sign, $expression) + { + if ($col === 'UNIX_TIMESTAMP(hn.start_time)') { + return 'hn.start_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + switch ($this->ds->getDbType()) { + case 'mysql': + $concattedContacts = "GROUP_CONCAT(" + . "DISTINCT cno.name1 ORDER BY cno.name1 SEPARATOR ', '" + . ") COLLATE latin1_general_ci"; + break; + case 'pgsql': + // TODO: Find a way to order the contact alias list: + $concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(DISTINCT cno.name1), ', ')"; + break; + case 'oracle': + // TODO: This is only valid for Oracle >= 11g Release 2 + $concattedContacts = "LISTAGG(cno.name1, ', ') WITHIN GROUP (ORDER BY cno.name1)"; + // Alternatives: + // + // RTRIM(XMLAGG(XMLELEMENT(e, column_name, ',').EXTRACT('//text()')), + // + // not supported and not documented but works since 10.1, + // however it is NOT always present: + // WM_CONCAT(c.alias) + break; + } + + $this->columnMap['history']['output'] = "('[' || $concattedContacts || '] ' || hn.output)"; + + $this->select->from( + array('hn' => $this->prefix . 'notifications'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hn.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['notifications'] = true; + } + + /** + * Join virtual table history + */ + protected function joinHistory() + { + $this->requireVirtualTable('contactnotifications'); + } + + /** + * Join contact notifications + */ + protected function joinContactnotifications() + { + $this->select->joinLeft( + array('cn' => $this->prefix . 'contactnotifications'), + 'cn.notification_id = hn.notification_id', + array() + ); + $this->select->joinLeft( + array('cno' => $this->prefix . 'objects'), + 'cno.object_id = cn.contact_object_id', + array() + ); + } + + /** + * Join acknowledgements + */ + protected function joinAcknowledgements() + { + $this->select->joinLeft( + array('a' => $this->prefix . 'acknowledgements'), + 'a.object_id = hn.object_id', + array() + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + if ( + $this->hasJoinedVirtualTable('history') + || $this->hasJoinedVirtualTable('services') + || $this->hasJoinedVirtualTable('hostgroups') + ) { + $group = array('hn.notification_id', 'ho.object_id'); + if ($this->hasJoinedVirtualTable('contactnotifications') && !$this->hasJoinedVirtualTable('history')) { + $group[] = 'cno.object_id'; + } + } elseif ($this->hasJoinedVirtualTable('contactnotifications')) { + $group = array('hn.notification_id', 'cno.object_id', 'ho.object_id'); + } + + if (! empty($group)) { + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('acknowledgements')) { + $group[] = 'a.acknowledgement_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php new file mode 100644 index 000000000..b2fca387c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php @@ -0,0 +1,140 @@ + array( + 'host_name' => 'so.name1', + 'service_description' => 'so.name2' + ), + 'hostgroups' => array( + 'hostgroup_name' => 'hgo.name1' + ), + 'servicegroups' => array( + 'servicegroup_name' => 'sgo.name1' + ), + 'problemsummary' => array( + 'unhandled_service_count' => 'SUM( + CASE + WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 + THEN 0 + ELSE 1 + END + )' + ) + ); + + /** + * Set the HoststatusQuery to use + * + * @param HoststatusQuery $query + * + * @return $this + */ + public function setHoststatusQuery(HoststatusQuery $query) + { + $this->hostStatusQuery = clone $query; + $this->hostStatusQuery->setIsSubQuery(); + $this->hostStatusQuery->columns(array('object_id')); + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('so' => $this->prefix . 'objects'), + array() + )->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + + $this->select->group(array('so.name1')); + $this->select->where('so.is_active = 1'); + $this->joinedVirtualTables['services'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join the statussummary + */ + protected function joinProblemsummary() + { + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id AND ss.current_state > 0', + array() + )->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = s.host_object_id', + array() + )->join( + array('h' => $this->hostStatusQuery), + 'h.object_id = s.host_object_id', + array() + ); + + $this->select->having($this->getMappedField('unhandled_service_count') . ' > 0'); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php new file mode 100644 index 000000000..8cb3b0964 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php @@ -0,0 +1,183 @@ + 0, + 'hard_state' => 1 + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'statehistory' => array( + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_type' => '(\'host\')' + ), + 'history' => array( + 'type' => "(CASE WHEN hh.state_type = 1 THEN 'hard_state' ELSE 'soft_state' END)", + 'timestamp' => 'UNIX_TIMESTAMP(hh.state_time)', + 'object_id' => 'hh.object_id', + 'state' => 'hh.state', + 'output' => "('[ ' || hh.current_check_attempt || '/' || hh.max_check_attempts || ' ] ' || hh.output)", + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + /** + * {@inheritdoc} + */ + public function whereToSql($col, $sign, $expression) + { + if ($col === 'UNIX_TIMESTAMP(hh.state_time)') { + return 'hh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); + } elseif ( + $col === $this->columnMap['history']['type'] + && ! is_array($expression) + && array_key_exists($expression, $this->types) + ) { + return 'hh.state_type ' . $sign . ' ' . $this->types[$expression]; + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('hh' => $this->prefix . 'statehistory'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['statehistory'] = true; + $this->joinedVirtualTables['history'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('services')) { + $group = array('hh.statehistory_id', 'ho.object_id'); + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php index e9903ec86..968d5607e 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php @@ -1,52 +1,96 @@ array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), 'hosts' => array( - 'host' => 'ho.name1 COLLATE latin1_general_ci', - 'host_name' => 'ho.name1 COLLATE latin1_general_ci', - 'host_display_name' => 'h.display_name', - 'host_alias' => 'h.alias', - 'host_address' => 'h.address', - 'host_ipv4' => 'INET_ATON(h.address)', - 'host_icon_image' => 'h.icon_image', - 'object_type' => '(\'host\')' + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_action_url' => 'h.action_url', + 'host_address' => 'h.address', + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', + 'host_icon_image' => 'h.icon_image', + 'host_icon_image_alt' => 'h.icon_image_alt', + 'host_ipv4' => 'INET_ATON(h.address)', + 'host_name' => 'ho.name1', + 'host_notes' => 'h.notes', + 'host_notes_url' => 'h.notes_url', + 'object_type' => '(\'host\')', + 'object_id' => 'ho.object_id' ), 'hoststatus' => array( - 'problems' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END', - 'handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END', - 'unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 END', - 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', - 'host_output' => 'hs.output', - 'host_long_output' => 'hs.long_output', - 'host_perfdata' => 'hs.perfdata', - 'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END', - 'host_acknowledged' => 'hs.problem_has_been_acknowledged', - 'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', - 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END', - 'host_does_active_checks' => 'hs.active_checks_enabled', - 'host_accepts_passive_checks' => 'hs.passive_checks_enabled', - 'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)', - 'host_last_hard_state' => 'hs.last_hard_state', - 'host_check_command' => 'hs.check_command', - 'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)', - 'host_next_check' => 'CASE WHEN hs.should_be_scheduled THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END', - 'host_check_execution_time' => 'hs.execution_time', - 'host_check_latency' => 'hs.latency', - 'host_notifications_enabled' => 'hs.notifications_enabled', - 'host_last_time_up' => 'hs.last_time_up', - 'host_last_time_down' => 'hs.last_time_down', - 'host_last_time_unreachable' => 'hs.last_time_unreachable', - 'host_current_check_attempt' => 'hs.current_check_attempt', - 'host_max_check_attempts' => 'hs.max_check_attempts', - 'host_severity' => 'CASE WHEN hs.current_state = 0 + 'host_acknowledged' => 'hs.problem_has_been_acknowledged', + 'host_acknowledgement_type' => 'hs.acknowledgement_type', + 'host_active_checks_enabled' => 'hs.active_checks_enabled', + 'host_active_checks_enabled_changed' => 'CASE WHEN hs.active_checks_enabled = h.active_checks_enabled THEN 0 ELSE 1 END', + 'host_attempt' => 'hs.current_check_attempt || \'/\' || hs.max_check_attempts', + 'host_check_command' => 'hs.check_command', + 'host_check_execution_time' => 'hs.execution_time', + 'host_check_latency' => 'hs.latency', + 'host_check_source' => 'hs.check_source', + 'host_check_type' => 'hs.check_type', + 'host_current_check_attempt' => 'hs.current_check_attempt', + 'host_current_notification_number' => 'hs.current_notification_number', + 'host_event_handler' => 'hs.event_handler', + 'host_event_handler_enabled' => 'hs.event_handler_enabled', + 'host_event_handler_enabled_changed' => 'CASE WHEN hs.event_handler_enabled = h.event_handler_enabled THEN 0 ELSE 1 END', + 'host_failure_prediction_enabled' => 'hs.failure_prediction_enabled', + 'host_flap_detection_enabled' => 'hs.flap_detection_enabled', + 'host_flap_detection_enabled_changed' => 'CASE WHEN hs.flap_detection_enabled = h.flap_detection_enabled THEN 0 ELSE 1 END', + 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END', + 'host_hard_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE CASE WHEN hs.state_type = 1 THEN hs.current_state ELSE hs.last_hard_state END END', + 'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', + 'host_is_flapping' => 'hs.is_flapping', + 'host_is_passive_checked' => 'CASE WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN 1 ELSE 0 END', + 'host_is_reachable' => 'hs.is_reachable', + 'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)', + 'host_last_hard_state' => 'hs.last_hard_state', + 'host_last_hard_state_change' => 'UNIX_TIMESTAMP(hs.last_hard_state_change)', + 'host_last_notification' => 'UNIX_TIMESTAMP(hs.last_notification)', + 'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)', + 'host_last_time_down' => 'UNIX_TIMESTAMP(hs.last_time_down)', + 'host_last_time_unreachable' => 'UNIX_TIMESTAMP(hs.last_time_unreachable)', + 'host_last_time_up' => 'UNIX_TIMESTAMP(hs.last_time_up)', + 'host_long_output' => 'hs.long_output', + 'host_max_check_attempts' => 'hs.max_check_attempts', + 'host_modified_host_attributes' => 'hs.modified_host_attributes', + 'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END', + 'host_next_notification' => 'UNIX_TIMESTAMP(hs.next_notification)', + 'host_no_more_notifications' => 'hs.no_more_notifications', + 'host_normal_check_interval' => 'hs.normal_check_interval', + 'host_notifications_enabled' => 'hs.notifications_enabled', + 'host_notifications_enabled_changed' => 'CASE WHEN hs.notifications_enabled = h.notifications_enabled THEN 0 ELSE 1 END', + 'host_obsessing' => 'hs.obsess_over_host', + 'host_obsessing_changed' => 'CASE WHEN hs.obsess_over_host = h.obsess_over_host THEN 0 ELSE 1 END', + 'host_output' => 'hs.output', + 'host_passive_checks_enabled' => 'hs.passive_checks_enabled', + 'host_passive_checks_enabled_changed' => 'CASE WHEN hs.passive_checks_enabled = h.passive_checks_enabled THEN 0 ELSE 1 END', + 'host_percent_state_change' => 'hs.percent_state_change', + 'host_perfdata' => 'hs.perfdata', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', + 'host_problem_has_been_acknowledged' => 'hs.problem_has_been_acknowledged', + 'host_process_performance_data' => 'hs.process_performance_data', + 'host_retry_check_interval' => 'hs.retry_check_interval', + 'host_scheduled_downtime_depth' => 'hs.scheduled_downtime_depth', + 'host_severity' => 'CASE WHEN hs.current_state = 0 THEN CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 16 @@ -81,243 +125,195 @@ class HoststatusQuery extends IdoQuery CASE WHEN hs.state_type = 1 THEN 8 ELSE 0 - END' - ), - 'hostgroups' => array( - 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + END', + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', + 'host_state_type' => 'hs.state_type', + 'host_status_update_time' => 'hs.status_update_time', + 'host_unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 END' ), 'servicegroups' => array( - 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', - ), - 'contactgroups' => array( - 'contactgroup' => 'contactgroup', - ), - 'contacts' => array( - 'contact' => 'hco.name1 COLLATE latin1_general_ci', + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' ), 'services' => array( - 'services_cnt' => 'SUM(1)', - 'services_ok' => 'SUM(CASE WHEN ss.current_state = 0 THEN 1 ELSE 0 END)', - 'services_warning' => 'SUM(CASE WHEN ss.current_state = 1 THEN 1 ELSE 0 END)', - 'services_critical' => 'SUM(CASE WHEN ss.current_state = 2 THEN 1 ELSE 0 END)', - 'services_unknown' => 'SUM(CASE WHEN ss.current_state = 3 THEN 1 ELSE 0 END)', - 'services_pending' => 'SUM(CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 1 ELSE 0 END)', - 'services_problem' => 'SUM(CASE WHEN ss.current_state > 0 THEN 1 ELSE 0 END)', - 'services_problem_handled' => 'SUM(CASE WHEN ss.current_state > 0 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)', - 'services_problem_unhandled' => 'SUM(CASE WHEN ss.current_state > 0 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)', - 'services_warning_handled' => 'SUM(CASE WHEN ss.current_state = 1 AND (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_critical_handled' => 'SUM(CASE WHEN ss.current_state = 2 AND (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_unknown_handled' => 'SUM(CASE WHEN ss.current_state = 3 AND (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_warning_unhandled' => 'SUM(CASE WHEN ss.current_state = 1 AND (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) = 0 THEN 1 ELSE 0 END)', - 'services_critical_unhandled' => 'SUM(CASE WHEN ss.current_state = 2 AND (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) = 0 THEN 1 ELSE 0 END)', - 'services_unknown_unhandled' => 'SUM(CASE WHEN ss.current_state = 3 AND (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) = 0 THEN 1 ELSE 0 END)', - ), + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + ) ); - protected $aggregateColumnIdx = array( - 'services_cnt' => true, - 'services_problem' => true, - 'services_problem_handled' => true, - 'services_problem_unhandled' => true, - ); - - protected $hcgSub; - - public function getDefaultColumns() - { - return $this->columnMap['hosts'] + $this->columnMap['hoststatus']; - } - + /** + * {@inheritdoc} + */ protected function joinBaseTables() { - // TODO: Shall we always add hostobject? - $this->select->from( - array('ho' => $this->prefix . 'objects'), - array() - )->join( - array('hs' => $this->prefix . 'hoststatus'), - 'ho.' . $this->object_id . ' = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', - array() - )->join( - array('h' => $this->prefix . 'hosts'), - 'hs.host_object_id = h.host_object_id', - array() - ); - $this->joinedVirtualTables = array( - 'hosts' => true, - 'hoststatus' => true, - ); - } - - protected function joinStatus() - { - $this->requireVirtualTable('services'); - } - - protected function joinServiceStatus() - { - $this->requireVirtualTable('services'); - } - - protected function joinServices() - { - $this->select->join( - array('s' => $this->prefix . 'services'), - 's.host_object_id = h.host_object_id', - array() - )->join( - array('so' => $this->prefix . 'objects'), - 'so.' . $this->object_id . ' = s.service_object_id AND so.is_active = 1', - array() - )->joinLeft( - array('ss' => $this->prefix . 'servicestatus'), - 'so.' . $this->object_id . ' = ss.service_object_id', - array() - ); - foreach ($this->getColumns() as $col) { - $real = $this->aliasToColumnName($col); - if (substr($real, 0, 4) === 'SUM(') { - continue; - } - $this->select->group($real); + if (version_compare($this->getIdoVersion(), '1.10.0', '<')) { + $this->columnMap['hoststatus']['host_check_source'] = '(NULL)'; } - $this->useSubqueryCount = true; + + if (version_compare($this->getIdoVersion(), '1.13.0', '<')) { + $this->columnMap['hoststatus']['host_is_reachable'] = '(NULL)'; + } + + $this->select->from( + array('h' => $this->prefix . 'hosts'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = h.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['hosts'] = true; } + /** + * Join host groups + */ protected function joinHostgroups() { - if ($this->hasJoinedVirtualTable('services')) { - return $this->joinServiceHostgroups(); - } else { - return $this->joinHostHostgroups(); - } - } - - protected function joinServiceHostgroups() - { - $this->select->join( + $this->select->joinLeft( array('hgm' => $this->prefix . 'hostgroup_members'), - 'hgm.host_object_id = s.host_object_id', + 'hgm.host_object_id = ho.object_id', array() - )->join( + )->joinLeft( array('hg' => $this->prefix . 'hostgroups'), - 'hgm.hostgroup_id = hg.' . $this->hostgroup_id, + 'hg.hostgroup_id = hgm.hostgroup_id', array() - )->join( + )->joinLeft( array('hgo' => $this->prefix . 'objects'), - 'hgo.' . $this->object_id . ' = hg.hostgroup_object_id' - . ' AND hgo.is_active = 1', + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', array() ); - return $this; } - protected function joinHostHostgroups() + /** + * Join host status + */ + protected function joinHoststatus() { $this->select->join( - array('hgm' => $this->prefix . 'hostgroup_members'), - 'hgm.host_object_id = h.host_object_id', - array() - )->join( - array('hg' => $this->prefix . 'hostgroups'), - 'hgm.hostgroup_id = hg.' . $this->hostgroup_id, - array() - )->join( - array('hgo' => $this->prefix . 'objects'), - 'hgo.' . $this->object_id . ' = hg.hostgroup_object_id' . ' AND hgo.is_active = 1', + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = ho.object_id', array() ); - return $this; - } - - protected function joinContacts() - { - $this->hcgcSub = $this->db->select()->distinct()->from( - array('hcgc' => $this->prefix . 'host_contactgroups'), - array('host_name' => 'ho.name1') - )->join( - array('cgo' => $this->prefix . 'objects'), - 'hcg.contactgroup_object_id = cgo.' . $this->object_id . ' AND cgo.is_active = 1', - array() - )->join( - array('h' => $this->prefix . 'hosts'), - 'hcg.host_id = h.host_id', - array() - )->join( - array('ho' => $this->prefix . 'objects'), - 'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1', - array() - ); - $this->select->join( - array('hcg' => $this->hcgSub), - 'hcg.host_name = ho.name1', - array() - ); - - return $this; - } - - protected function filterContactgroup($value) - { - $this->hcgSub->where( - $this->prepareFilterStringForColumn( - 'cgo.name1 COLLATE latin1_general_ci', - $value - ) - ); - return $this; - } - - protected function joinContactgroups() - { - $this->hcgSub = $this->createContactgroupFilterSubselect(); - $this->select->join( - array('hcg' => $this->hcgSub), - 'hcg.object_id = ho.object_id', - array() - ); - return $this; - } - - protected function createContactgroupFilterSubselect() - { - die((string) $this->db->select()->distinct()->from( - array('hcg' => $this->prefix . 'host_contactgroups'), - array('object_id' => 'ho.object_id') - )->join( - array('cgo' => $this->prefix . 'objects'), - 'hcg.contactgroup_object_id = cgo.' . $this->object_id - . ' AND cgo.is_active = 1', - array() - )->join( - array('h' => $this->prefix . 'hosts'), - 'hcg.host_id = h.host_id', - array() - )->join( - array('ho' => $this->prefix . 'objects'), - 'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1', - array() - )); } + /** + * Join service groups + */ protected function joinServicegroups() { - // TODO: Only hosts with services having such servicegroups $this->requireVirtualTable('services'); - $this->select->join( + $this->select->joinLeft( array('sgm' => $this->prefix . 'servicegroup_members'), 'sgm.service_object_id = s.service_object_id', array() - )->join( + )->joinLeft( array('sg' => $this->prefix . 'servicegroups'), 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, array() - )->join( + )->joinLeft( array('sgo' => $this->prefix . 'objects'), - 'sgo.' . $this->object_id . ' = sg.servicegroup_object_id' - . ' AND sgo.is_active = 1', + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', array() ); - return $this; + } + + /** + * Join services + */ + protected function joinServices() + { + $this->requireVirtualTable('hosts'); + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = h.host_object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('services')) { + $group = array('h.host_id', 'ho.object_id'); + if ($this->hasJoinedVirtualTable('hoststatus')) { + $group[] = 'hs.hoststatus_id'; + } + + if ($this->hasJoinedVirtualTable('serviceproblemsummary')) { + $group[] = 'sps.unhandled_services_count'; + } + + if ($this->hasJoinedVirtualTable('hostgroups')) { + $selected = false; + foreach ($this->columns as $alias => $column) { + if ($column instanceof Zend_Db_Expr) { + continue; + } + + $table = $this->aliasToTableName( + $this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias) + ); + if ($table === 'hostgroups') { + $selected = true; + break; + } + } + + if ($selected) { + $group[] = 'hg.hostgroup_id'; + $group[] = 'hgo.object_id'; + } + } + + if ($this->hasJoinedVirtualTable('servicegroups')) { + $selected = false; + foreach ($this->columns as $alias => $column) { + if ($column instanceof Zend_Db_Expr) { + continue; + } + + $table = $this->aliasToTableName( + $this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias) + ); + if ($table === 'servicegroups') { + $selected = true; + break; + } + } + + if ($selected) { + $group[] = 'sg.servicegroup_id'; + $group[] = 'sgo.object_id'; + } + } + } + + return $group; + } + + /** + * Query the service problem summary for all hosts of this query's result set + * + * @return HostserviceproblemsummaryQuery + */ + public function queryServiceProblemSummary() + { + return $this->createSubQuery('Hostserviceproblemsummary') + ->setHostStatusQuery($this) + ->columns(array( + 'host_name', + 'unhandled_service_count' + ) + ); } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php new file mode 100644 index 000000000..da065d184 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php @@ -0,0 +1,75 @@ + array( + 'hosts_down' => 'SUM(CASE WHEN state = 1 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN state = 1 AND handled = 1 THEN 1 ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN state = 1 AND handled = 0 THEN 1 ELSE 0 END)', + 'hosts_pending' => 'SUM(CASE WHEN state = 99 THEN 1 ELSE 0 END)', + 'hosts_total' => 'SUM(1)', + 'hosts_unreachable' => 'SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN state = 2 AND handled = 1 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN state = 2 AND handled = 0 THEN 1 ELSE 0 END)', + 'hosts_up' => 'SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END)' + ) + ); + + /** + * The host status sub select + * + * @var HostStatusQuery + */ + protected $subSelect; + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + $this->subSelect->applyFilter(clone $filter); + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + // TODO(el): Allow to switch between hard and soft states + $this->subSelect = $this->createSubQuery( + 'Hoststatus', + array( + 'handled' => 'host_handled', + 'state' => 'host_state', + 'state_change' => 'host_last_state_change' + ) + ); + $this->select->from( + array('hoststatussummary' => $this->subSelect->setIsSubQuery(true)), + array() + ); + $this->joinedVirtualTables['hoststatussummary'] = true; + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->subSelect->where($condition, $value); + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php index 4682487d2..de89d23bb 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php @@ -1,17 +1,19 @@ getExpression() === '*') { + return; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing + } + $col = $filter->getColumn(); $this->requireColumn($col); @@ -265,11 +482,51 @@ abstract class IdoQuery extends DbQuery public function addFilter(Filter $filter) { $this->requireFilterColumns($filter); - parent::addFilter($filter); + return parent::addFilter($filter); + } + + /** + * Recurse the given filter and ensure that any string conversion is case-insensitive + * + * @param Filter $filter + */ + protected function lowerColumnsWithoutCollation(Filter $filter) + { + if ($filter instanceof FilterExpression) { + if ( + in_array($filter->getColumn(), $this->columnsWithoutCollation) + && strpos($filter->getColumn(), 'LOWER') !== 0 + ) { + $filter->setColumn('LOWER(' . $filter->getColumn() . ')'); + $expression = $filter->getExpression(); + if (is_array($expression)) { + $filter->setExpression(array_map('strtolower', $expression)); + } else { + $filter->setExpression(strtolower($expression)); + } + } + } else { + foreach ($filter->filters() as $chainedFilter) { + $this->lowerColumnsWithoutCollation($chainedFilter); + } + } + } + + protected function applyFilterSql($select) + { + if (! empty($this->columnsWithoutCollation)) { + $this->lowerColumnsWithoutCollation($this->filter); + } + + parent::applyFilterSql($select); } public function where($condition, $value = null) { + if ($value === '*') { + return $this; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing + } + $this->requireColumn($condition); $col = $this->getMappedField($condition); if ($col === null) { @@ -292,7 +549,6 @@ abstract class IdoQuery extends DbQuery $mapped = $this->getMappedField($field); if ($mapped === null) { return stripos($field, 'UNIX_TIMESTAMP') !== false; - return false; } return stripos($mapped, 'UNIX_TIMESTAMP') !== false; } @@ -307,6 +563,8 @@ abstract class IdoQuery extends DbQuery $this->object_id = $this->host_id = $this->service_id = $this->hostgroup_id = $this->servicegroup_id = $this->contact_id = $this->contactgroup_id = 'id'; + $this->customVarsJoinTemplate = + '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s'; foreach ($this->columnMap as &$columns) { foreach ($columns as &$value) { $value = preg_replace('/UNIX_TIMESTAMP/', 'localts2unixts', $value); @@ -320,10 +578,24 @@ abstract class IdoQuery extends DbQuery */ private function initializeForPostgres() { + $this->customVarsJoinTemplate = + '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s'; foreach ($this->columnMap as $table => & $columns) { foreach ($columns as $key => & $value) { - $value = preg_replace('/ COLLATE .+$/', '', $value); - $value = preg_replace('/inet_aton\(([[:word:].]+)\)/i', '$1::inet - \'0.0.0.0\'', $value); + $value = preg_replace('/ COLLATE .+$/', '', $value, -1, $count); + if ($count > 0) { + $this->columnsWithoutCollation[] = $this->getMappedField($key); + } + $value = preg_replace( + '/inet_aton\(([[:word:].]+)\)/i', + '(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)', + $value + ); + $value = preg_replace( + '/UNIX_TIMESTAMP(\((?>[^()]|(?-1))*\))/i', + 'CASE WHEN ($1 < \'1970-01-03 00:00:00+00\'::timestamp with time zone) THEN 0 ELSE UNIX_TIMESTAMP($1) END', + $value + ); } } } @@ -337,28 +609,17 @@ abstract class IdoQuery extends DbQuery { parent::init(); $this->prefix = $this->ds->getTablePrefix(); - - if ($this->ds->getDbType() === 'oracle') { + $dbType = $this->ds->getDbType(); + if ($dbType === 'oracle') { $this->initializeForOracle(); - } elseif ($this->ds->getDbType() === 'pgsql') { + } elseif ($dbType === 'pgsql') { $this->initializeForPostgres(); } - $this->dbSelect(); - + $this->joinBaseTables(); $this->select->columns($this->columns); - //$this->joinBaseTables(); $this->prepareAliasIndexes(); } - protected function dbSelect() - { - if ($this->select === null) { - $this->select = $this->db->select(); - $this->joinBaseTables(); - } - return clone $this->select; - } - /** * Join the base tables for this query */ @@ -400,6 +661,11 @@ abstract class IdoQuery extends DbQuery $resolvedColumns = array(); foreach ($columns as $alias => $col) { + if ($col instanceof Zend_Db_Expr) { + // Support selecting NULL as column for example + $resolvedColumns[$alias] = $col; + continue; + } $this->requireColumn($col); if ($this->isCustomvar($col)) { $name = $this->getCustomvarColumnName($col); @@ -408,6 +674,8 @@ abstract class IdoQuery extends DbQuery } if (is_int($alias)) { $alias = $col; + } else { + $this->idxCustomAliases[$alias] = $col; } $resolvedColumns[$alias] = preg_replace('|\n|', ' ', $name); @@ -435,7 +703,7 @@ abstract class IdoQuery extends DbQuery * * @param $alias The alias of the column to require * - * @return self Fluent interface + * @return $this Fluent interface * @see IdoQuery::requireVirtualTable The method initializing required joins * @throws \Icinga\Exception\ProgrammingError When an unknown column is requested */ @@ -470,7 +738,7 @@ abstract class IdoQuery extends DbQuery * Require a virtual table for the given table name if not already required * * @param String $name The table name to require - * @return self Fluent interface + * @return $this Fluent interface */ protected function requireVirtualTable($name) { @@ -497,7 +765,7 @@ abstract class IdoQuery extends DbQuery * This requires a join$Table() method to exist * * @param String $table The table to join by calling join$Table() in the concrete implementation - * @return self Fluent interface + * @return $this Fluent interface * * @throws \Icinga\Exception\ProgrammingError If the join method for this table does not exist */ @@ -548,30 +816,32 @@ abstract class IdoQuery extends DbQuery protected function hasCustomvar($customvar) { - return array_key_exists($customvar, $this->customVars); + return array_key_exists(strtolower($customvar), $this->customVars); } protected function joinCustomvar($customvar) { // TODO: This is not generic enough yet list($type, $name) = $this->customvarNameToTypeName($customvar); - $alias = ($type === 'host' ? 'hcv_' : 'scv_') . strtolower($name); + $alias = ($type === 'host' ? 'hcv_' : 'scv_') . $name; $this->customVars[$customvar] = $alias; - // TODO: extend if we allow queries with only hosts / only services - // ($leftcol s.host_object_id vs h.host_object_id if ($this->hasJoinedVirtualTable('services')) { $leftcol = 's.' . $type . '_object_id'; + } elseif ($type === 'service') { + $this->requireVirtualTable('services'); + $leftcol = 's.service_object_id'; } else { - $leftcol = 'h.' . $type . '_object_id'; + $this->requireVirtualTable('hosts'); + $leftcol = 'h.host_object_id'; } + $joinOn = sprintf( - '%s = %s.object_id AND %s.varname = %s', + $this->customVarsJoinTemplate, $leftcol, $alias, - $alias, - $this->db->quote(strtoupper($name)) + $this->db->quote($name) ); $this->select->joinLeft( @@ -585,7 +855,7 @@ abstract class IdoQuery extends DbQuery protected function customvarNameToTypeName($customvar) { - // TODO: Improve this: + $customvar = strtolower($customvar); if (! preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $customvar, $m)) { throw new ProgrammingError( 'Got invalid custom var: "%s"', @@ -600,8 +870,19 @@ abstract class IdoQuery extends DbQuery return array_key_exists($name, $this->joinedVirtualTables); } + /** + * Get the query column of a already joined custom variable + * + * @param string $customvar + * + * @return string + * @throws QueryException If the custom variable has not been joined + */ protected function getCustomvarColumnName($customvar) { + if (! isset($this->customVars[($customvar = strtolower($customvar))])) { + throw new QueryException('Custom variable %s has not been joined', $customvar); + } return $this->customVars[$customvar] . '.varvalue'; } @@ -610,6 +891,29 @@ abstract class IdoQuery extends DbQuery return $this->idxAliasColumn[$alias]; } + /** + * Get the alias of a column expression as defined in the {@link $columnMap} property. + * + * @param string $alias Potential custom alias + * + * @return string + */ + public function customAliasToAlias($alias) + { + if (isset($this->idxCustomAliases[$alias])) { + return $this->idxCustomAliases[$alias]; + } + return $alias; + } + + /** + * Create a sub query + * + * @param string $queryName + * @param array $columns + * + * @return static + */ protected function createSubQuery($queryName, $columns = array()) { $class = '\\' @@ -624,16 +928,59 @@ abstract class IdoQuery extends DbQuery * * @param array $columns * - * @return self + * @return $this */ public function columns(array $columns) { + $this->idxCustomAliases = array(); $this->columns = $this->resolveColumns($columns); // TODO: we need to refresh our select! // $this->select->columns($columns); return $this; } + /** + * {@inheritdoc} + */ + public function _getGroup() + { + throw new NotImplementedError('Does not work in its current state but will, probably, in the future'); + + // TODO: order by?? + $group = parent::getGroup(); + if (! empty($group) && $this->ds->getDbType() === 'pgsql') { + $group = is_array($group) ? $group : array($group); + foreach ($this->columns as $alias => $column) { + if ($column instanceof Zend_Db_Expr) { + continue; + } + + // TODO: What if $alias is neither a native nor a custom alias??? + $table = $this->aliasToTableName( + $this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias) + ); + + // TODO: We cannot rely on the underlying select here, tables may be joined multiple times with + // different aliases so the only way to get the correct alias here is to register such by ourself + // for each virtual column (We may also inspect $column for the alias but this will probably lead + // to false positives.. AND prevents custom implementations from providing their own "mapping") + if (($tableAlias = $this->getJoinedTableAlias($this->prefix . $table)) === null) { + $tableAlias = $table; + } + + // TODO: Same issue as with identifying table aliases; Our virtual tables are not named exactly how + // they are in the IDO. We definitely need to register aliases explicitly (hint: DbRepository + // is already providing such..) + $aliasedPk = $tableAlias . '.' . $this->getPrimaryKeyColumn($table); + if (! in_array($aliasedPk, $group)) { + $group[] = $aliasedPk; + } + } + } + + return $group; + } + // TODO: Move this away, see note related to $idoVersion var protected function getIdoVersion() { @@ -659,4 +1006,90 @@ abstract class IdoQuery extends DbQuery } return self::$idoVersion; } + + /** + * Return the name of the primary key column for the given table name + * + * @param string $table + * + * @return string + * + * @throws ProgrammingError In case $table is unknown + */ + protected function getPrimaryKeyColumn($table) + { + // TODO: For god's sake, make this being a mapping + // (instead of matching a ton of properties using a ridiculous long switch case) + switch ($table) + { + case 'instances': + return $this->instance_id; + case 'objects': + return $this->object_id; + case 'acknowledgements': + return $this->acknowledgement_id; + case 'commenthistory': + return $this->commenthistory_id; + case 'contactnotifiations': + return $this->contactnotification_id; + case 'downtimehistory': + return $this->downtimehistory_id; + case 'flappinghistory': + return $this->flappinghistory_id; + case 'notifications': + return $this->notification_id; + case 'statehistory': + return $this->statehistory_id; + case 'comments': + return $this->comment_id; + case 'customvariablestatus': + return $this->customvariablestatus_id; + case 'hoststatus': + return $this->hoststatus_id; + case 'programstatus': + return $this->programstatus_id; + case 'runtimevariables': + return $this->runtimevariable_id; + case 'scheduleddowntime': + return $this->scheduleddowntime_id; + case 'servicestatus': + return $this->servicestatus_id; + case 'contactstatus': + return $this->contactstatus_id; + case 'commands': + return $this->command_id; + case 'contactgroup_members': + return $this->contactgroup_member_id; + case 'contactgroups': + return $this->contactgroup_id; + case 'contacts': + return $this->contact_id; + case 'customvariables': + return $this->customvariable_id; + case 'host_contactgroups': + return $this->host_contactgroup_id; + case 'host_contacts': + return $this->host_contact_id; + case 'hostgroup_members': + return $this->hostgroup_member_id; + case 'hostgroups': + return $this->hostgroup_id; + case 'hosts': + return $this->host_id; + case 'service_contactgroups': + return $this->service_contactgroup_id; + case 'service_contacts': + return $this->service_contact_id; + case 'servicegroup_members': + return $this->servicegroup_member_id; + case 'servicegroups': + return $this->servicegroup_id; + case 'services': + return $this->service_id; + case 'timeperiods': + return $this->timeperiod_id; + default: + throw new ProgrammingError('Cannot provide a primary key column. Table "%s" is unknown', $table); + } + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php index 00b6957b8..e9085de90 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php @@ -1,113 +1,174 @@ array( - 'notification_output' => 'n.output', - 'notification_start_time' => 'UNIX_TIMESTAMP(n.start_time)', - 'notification_state' => 'n.state', - 'notification_object_id' => 'n.object_id' + 'notifications' => array( + 'notification_state' => 'n.notification_state', + 'notification_start_time' => 'n.notification_start_time', + 'notification_contact_name' => 'n.notification_contact_name', + 'notification_output' => 'n.notification_output', + 'notification_object_id' => 'n.notification_object_id', + 'contact_object_id' => 'n.contact_object_id', + 'acknowledgement_entry_time' => 'n.acknowledgement_entry_time', + 'acknowledgement_author_name' => 'n.acknowledgement_author_name', + 'acknowledgement_comment_data' => 'n.acknowledgement_comment_data', + 'object_type' => 'n.object_type' ), - 'objects' => array( - 'host' => 'o.name1', - 'service' => 'o.name2' + 'history' => array( + 'type' => 'n.type', + 'timestamp' => 'n.timestamp', + 'object_id' => 'n.object_id', + 'state' => 'n.state', + 'output' => 'n.output' ), - 'contact' => array( - 'notification_contact' => 'c_o.name1', - 'contact_object_id' => 'c_o.object_id' + 'hosts' => array( + 'host_display_name' => 'n.host_display_name', + 'host_name' => 'n.host_name' ), - 'command' => array( - 'notification_command' => 'cmd_o.name1' - ), - 'acknowledgement' => array( - 'acknowledgement_entry_time' => 'UNIX_TIMESTAMP(a.entry_time)', - 'acknowledgement_author_name' => 'a.author_name', - 'acknowledgement_comment_data' => 'a.comment_data' + 'services' => array( + 'service_description' => 'n.service_description', + 'service_display_name' => 'n.service_display_name', + 'service_host_name' => 'n.service_host_name' ) ); /** - * Fetch basic information about notifications + * The union + * + * @var Zend_Db_Select + */ + protected $notificationQuery; + + /** + * Subqueries used for the notification query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} */ protected function joinBaseTables() { + $this->notificationQuery = $this->db->select(); $this->select->from( - array('n' => $this->prefix . 'notifications'), + array('n' => $this->notificationQuery), array() ); - $this->joinedVirtualTables = array('notification' => true); + $this->joinedVirtualTables['notifications'] = true; } /** - * Fetch description of each affected host/service + * Join history related columns and tables */ - protected function joinObjects() + protected function joinHistory() { - $this->select->join( - array('o' => $this->prefix . 'objects'), - 'n.object_id = o.object_id AND o.is_active = 1 AND o.objecttype_id IN (1, 2)', - array() - ); + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); } /** - * Fetch name of involved contacts and/or contact groups + * Join hosts */ - protected function joinContact() + protected function joinHosts() { - $this->select->join( - array('c' => $this->prefix . 'contactnotifications'), - 'n.notification_id = c.notification_id', - array() - ); - $this->select->join( - array('c_o' => $this->prefix . 'objects'), - 'c.contact_object_id = c_o.object_id', - array() - ); + $columns = array_keys($this->columnMap['hosts']); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + $columns[] = 'object_type'; + } else { + $columns = array_merge($columns, array_keys($this->columnMap['notifications'])); + } + $hosts = $this->createSubQuery('hostnotification', $columns); + $this->subQueries[] = $hosts; + $this->notificationQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); } /** - * Fetch name of the command which was used to send out a notification + * Join services */ - protected function joinCommand() + protected function joinServices() { - $this->select->join( - array('cmd_c' => $this->prefix . 'contactnotifications'), - 'n.notification_id = cmd_c.notification_id', - array() - ); - $this->select->joinLeft( - array('cmd_m' => $this->prefix . 'contactnotificationmethods'), - 'cmd_c.contactnotification_id = cmd_m.contactnotification_id', - array() - ); - $this->select->joinLeft( - array('cmd_o' => $this->prefix . 'objects'), - 'cmd_m.command_object_id = cmd_o.object_id', - array() - ); + $columns = array_keys($this->columnMap['hosts'] + $this->columnMap['services']); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + $columns[] = 'object_type'; + } else { + $columns = array_merge($columns, array_keys($this->columnMap['notifications'])); + } + $services = $this->createSubQuery('servicenotification', $columns); + $this->subQueries[] = $services; + $this->notificationQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); } - protected function joinAcknowledgement() + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) { - $this->select->joinLeft( - array('a' => $this->prefix . 'acknowledgements'), - 'n.object_id = a.object_id', - array() - ); + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function columns(array $columns) + { + parent::columns($columns); + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php index 4bf712f4c..8bbdc0751 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php @@ -1,6 +1,5 @@ array( - 'timestamp' => 'UNIX_TIMESTAMP(n.start_time)', - 'raw_timestamp' => 'n.start_time', - 'state_time' => 'n.start_time', - 'object_id' => 'n.object_id', - 'type' => "('notify')", - 'state' => 'n.state', - 'state_type' => '(NULL)', - 'output' => null, - 'attempt' => '(NULL)', - 'max_attempts' => '(NULL)', + 'state_time' => 'n.start_time', + 'timestamp' => 'UNIX_TIMESTAMP(n.start_time)', + 'raw_timestamp' => 'n.start_time', + 'object_id' => 'n.object_id', + 'type' => "('notify')", + 'state' => 'n.state', + 'state_type' => '(NULL)', + 'output' => null, + 'attempt' => '(NULL)', + 'max_attempts' => '(NULL)', - 'host' => 'o.name1 COLLATE latin1_general_ci', - 'service' => 'o.name2 COLLATE latin1_general_ci', - 'host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'service_host_name' => 'o.name1 COLLATE latin1_general_ci', - 'service_description' => 'o.name2 COLLATE latin1_general_ci', - 'object_type' => "CASE WHEN o.objecttype_id = 1 THEN 'host' ELSE 'service' END" + 'host' => 'o.name1 COLLATE latin1_general_ci', + 'service' => 'o.name2 COLLATE latin1_general_ci', + 'host_name' => 'o.name1', + 'service_description' => 'o.name2', + 'object_type' => "CASE WHEN o.objecttype_id = 1 THEN 'host' ELSE 'service' END" ) ); diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php index e6abd4f77..f84b41ca5 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php @@ -1,6 +1,5 @@ array( - 'id' => 'programstatus_id', - 'status_update_time' => 'UNIX_TIMESTAMP(status_update_time)', - 'program_start_time' => 'UNIX_TIMESTAMP(program_start_time)', - 'program_end_time' => 'UNIX_TIMESTAMP(program_end_time)', - 'is_currently_running' => 'is_currently_running', + 'id' => 'programstatus_id', + 'status_update_time' => 'UNIX_TIMESTAMP(programstatus.status_update_time)', + 'program_version' => 'program_version', + 'program_start_time' => 'UNIX_TIMESTAMP(programstatus.program_start_time)', + 'program_end_time' => 'UNIX_TIMESTAMP(programstatus.program_end_time)', + 'is_currently_running' => 'CASE WHEN (programstatus.is_currently_running = 0) + THEN + 0 + ELSE + CASE WHEN (UNIX_TIMESTAMP(programstatus.status_update_time) + 60 > UNIX_TIMESTAMP(NOW())) + THEN + 1 + ELSE + 0 + END + END', 'process_id' => 'process_id', + 'endpoint_name' => 'endpoint_name', 'daemon_mode' => 'daemon_mode', - 'last_command_check' => 'UNIX_TIMESTAMP(last_command_check)', - 'last_log_rotation' => 'UNIX_TIMESTAMP(last_log_rotation)', + 'last_command_check' => 'UNIX_TIMESTAMP(programstatus.last_command_check)', + 'last_log_rotation' => 'UNIX_TIMESTAMP(programstatus.last_log_rotation)', 'notifications_enabled' => 'notifications_enabled', - 'disable_notif_expire_time' => 'UNIX_TIMESTAMP(disable_notif_expire_time)', + 'disable_notif_expire_time' => 'UNIX_TIMESTAMP(programstatus.disable_notif_expire_time)', 'active_service_checks_enabled' => 'active_service_checks_enabled', 'passive_service_checks_enabled' => 'passive_service_checks_enabled', 'active_host_checks_enabled' => 'active_host_checks_enabled', @@ -38,4 +49,19 @@ class ProgramstatusQuery extends IdoQuery 'global_service_event_handler' => 'global_service_event_handler', ) ); + + protected function joinBaseTables() + { + parent::joinBaseTables(); + + if (version_compare($this->getIdoVersion(), '1.11.7', '<')) { + $this->columnMap['programstatus']['endpoint_name'] = '(0)'; + } + if (version_compare($this->getIdoVersion(), '1.11.8', '<')) { + $this->columnMap['programstatus']['program_version'] = '(NULL)'; + } + if (version_compare($this->getIdoVersion(), '1.8', '<')) { + $this->columnMap['programstatus']['disable_notif_expire_time'] = '(NULL)'; + } + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php index cf3b6ea6d..fe1a6f263 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php @@ -1,6 +1,5 @@ array( + 'comment_author' => 'c.author_name COLLATE latin1_general_ci', + 'comment_author_name' => 'c.author_name', + 'comment_data' => 'c.comment_data', + 'comment_expiration' => 'CASE c.expires WHEN 1 THEN UNIX_TIMESTAMP(c.expiration_time) ELSE NULL END', + 'comment_internal_id' => 'c.internal_comment_id', + 'comment_is_persistent' => 'c.is_persistent', + 'comment_timestamp' => 'UNIX_TIMESTAMP(c.comment_time)', + 'comment_type' => "CASE c.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END", + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'hoststatus' => array( + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ), + 'servicestatus' => array( + 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('c' => $this->prefix . 'comments'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = c.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['comments'] = true; + } + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * Join service status + */ + protected function joinServicestatus() + { + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('servicegroups')) { + $group = array('c.comment_id', 'so.object_id'); + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('services')) { + $group[] = 's.service_id'; + } + + if ($this->hasJoinedVirtualTable('hoststatus')) { + $group[] = 'hs.hoststatus_id'; + } + + if ($this->hasJoinedVirtualTable('servicestatus')) { + $group[] = 'ss.servicestatus_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php new file mode 100644 index 000000000..69e49e1ea --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php @@ -0,0 +1,41 @@ +timestampForSql($this->valueToTimestamp($expression)); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(); + $this->select->where("sch.deletion_time > '1970-01-02 00:00:00'"); + $this->columnMap['history']['timestamp'] = str_replace( + 'comment_time', + 'deletion_time', + $this->columnMap['history']['timestamp'] + ); + $this->columnMap['history']['type'] = str_replace( + 'END)', + "END || '_deleted')", + $this->columnMap['history']['type'] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php new file mode 100644 index 000000000..baa3d4d5a --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php @@ -0,0 +1,166 @@ + array( + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'history' => array( + 'type' => "(CASE sch.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'dt_comment' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END)", + 'timestamp' => 'UNIX_TIMESTAMP(sch.comment_time)', + 'object_id' => 'sch.object_id', + 'state' => '(-1)', + 'output' => "('[' || sch.author_name || '] ' || sch.comment_data)", + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ) + ); + + /** + * {@inheritdoc} + */ + public function whereToSql($col, $sign, $expression) + { + if ($col === 'UNIX_TIMESTAMP(sch.comment_time)') { + return 'sch.comment_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sch' => $this->prefix . 'commenthistory'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sch.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['commenthistory'] = true; + $this->joinedVirtualTables['history'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('servicegroups')) { + $group = array('sch.commenthistory_id', 'so.object_id'); + if ($this->hasJoinedVirtualTable('services')) { + $group[] = 'h.host_id'; + $group[] = 's.service_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php new file mode 100644 index 000000000..09162ea35 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php @@ -0,0 +1,202 @@ + array( + 'downtime_author' => 'sd.author_name COLLATE latin1_general_ci', + 'downtime_author_name' => 'sd.author_name', + 'downtime_comment' => 'sd.comment_data', + 'downtime_duration' => 'sd.duration', + 'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END', + 'downtime_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)', + 'downtime_internal_id' => 'sd.internal_downtime_id', + 'downtime_is_fixed' => 'sd.is_fixed', + 'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END', + 'downtime_is_in_effect' => 'sd.is_in_effect', + 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', + 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)', + 'downtime_start' => 'UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)', + 'downtime_triggered_by_id' => 'sd.triggered_by_id', + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'hoststatus' => array( + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ), + 'servicestatus' => array( + 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sd' => $this->prefix . 'scheduleddowntime'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'sd.object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['downtimes'] = true; + } + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * Join service status + */ + protected function joinServicestatus() + { + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('servicegroups')) { + $group = array('sd.scheduleddowntime_id', 'so.object_id'); + + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('hoststatus')) { + $group[] = 'hs.hoststatus_id'; + } + + if ($this->hasJoinedVirtualTable('services')) { + $group[] = 's.service_id'; + } + + if ($this->hasJoinedVirtualTable('servicestatus')) { + $group[] = 'ss.servicestatus_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php new file mode 100644 index 000000000..44303a9cd --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php @@ -0,0 +1,39 @@ +timestampForSql( + $this->valueToTimestamp($expression) + ); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(true); + $this->select->where("sdh.actual_end_time > '1970-01-02 00:00:00'"); + $this->columnMap['history']['type'] = "('dt_end')"; + $this->columnMap['history']['timestamp'] = str_replace( + 'actual_start_time', + 'actual_end_time', + $this->columnMap['history']['timestamp'] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php new file mode 100644 index 000000000..deaa4ae9b --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php @@ -0,0 +1,175 @@ + array( + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'history' => array( + 'type' => "('dt_start')", + 'timestamp' => 'UNIX_TIMESTAMP(sdh.actual_start_time)', + 'object_id' => 'sdh.object_id', + 'state' => '(-1)', + 'output' => "('[' || sdh.author_name || '] ' || sdh.comment_data)", + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ) + ); + + /** + * {@inheritdoc} + */ + public function whereToSql($col, $sign, $expression) + { + if ($col === 'UNIX_TIMESTAMP(sdh.actual_start_time)') { + return 'sdh.actual_start_time ' . $sign . ' ' . $this->timestampForSql( + $this->valueToTimestamp($expression) + ); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sdh' => $this->prefix . 'downtimehistory'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sdh.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + + if (@func_get_arg(0) === false) { + $this->select->where( + "sdh.actual_start_time > '1970-01-02 00:00:00'" + ); + } + + $this->joinedVirtualTables['downtimehistory'] = true; + $this->joinedVirtualTables['history'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('servicegroups')) { + $group = array('sdh.downtimehistory_id', 'so.object_id'); + if ($this->hasJoinedVirtualTable('services')) { + $group[] = 'h.host_id'; + $group[] = 's.service_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php index 84ad94b86..7c2aafc36 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php @@ -1,25 +1,47 @@ array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', + ), 'servicegroups' => array( - 'servicegroup_name' => 'sgo.name1 COLLATE latin1_general_ci', - 'servicegroup_alias' => 'sg.alias', + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1' + ), + 'serviceobjects' => array( + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2' ), 'services' => array( - 'host' => 'so.name1 COLLATE latin1_general_ci', - 'host_name' => 'so.name1 COLLATE latin1_general_ci', - 'service' => 'so.name2 COLLATE latin1_general_ci', - 'service_host_name' => 'so.name1 COLLATE latin1_general_ci', - 'service_description' => 'so.name2 COLLATE latin1_general_ci' + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', ) ); + /** + * {@inheritdoc} + */ protected function joinBaseTables() { $this->select->from( @@ -27,24 +49,85 @@ class ServicegroupQuery extends IdoQuery array() )->join( array('sgo' => $this->prefix . 'objects'), - 'sg.servicegroup_object_id = sgo.' . $this->object_id - . ' AND sgo.is_active = 1', + 'sg.servicegroup_object_id = sgo.object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', array() ); - $this->joinedVirtualTables = array('servicegroups' => true); } - protected function joinServices() + /** + * Join host groups + */ + protected function joinHostgroups() { - $this->select->join( - array('sgm' => $this->prefix . 'servicegroup_members'), - 'sgm.' . $this->servicegroup_id . ' = sg.' . $this->servicegroup_id, + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', array() - )->join( - array('so' => $this->prefix . 'objects'), - 'sgm.service_object_id = so.' . $this->object_id . ' AND so.is_active = 1', + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', array() ); } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service objects + */ + protected function joinServiceobjects() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.' . $this->servicegroup_id . ' = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'sgm.service_object_id = so.object_id', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->requireVirtualTable('serviceobjects'); + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('serviceobjects')) { + $group = array('sg.servicegroup_id', 'sgo.object_id'); + } + + return $group; + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php new file mode 100644 index 000000000..a4c763562 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php @@ -0,0 +1,138 @@ + array( + 'servicegroup' => 'servicegroup COLLATE latin1_general_ci', + 'servicegroup_alias' => 'servicegroup_alias COLLATE latin1_general_ci', + 'servicegroup_name' => 'servicegroup_name', + 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state > 0 THEN 1 ELSE 0 END)', + 'services_critical_handled_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state > 0 THEN state_change ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state = 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_state = 0 THEN state_change ELSE 0 END)', + 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', + 'services_ok_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 0 THEN state_change ELSE 0 END)', + 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', + 'services_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 99 THEN state_change ELSE 0 END)', + 'services_severity' => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)', + 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', + 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state > 0 THEN 1 ELSE 0 END)', + 'services_unknown_handled_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state > 0 THEN state_change ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state = 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_state = 0 THEN state_change ELSE 0 END)', + 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state > 0 THEN 1 ELSE 0 END)', + 'services_warning_handled_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state > 0 THEN state_change ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state = 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_state = 0 THEN state_change ELSE 0 END)' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $summaryQuery; + + /** + * Subqueries used for the summary query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + // TODO(el): Allow to switch between hard and soft states + $hosts = $this->createSubQuery( + 'Hoststatus', + array( + 'handled' => 'host_handled', + 'host_state' => new Zend_Db_Expr('NULL'), + 'servicegroup_alias', + 'servicegroup_name', + 'object_type', + 'severity' => new Zend_Db_Expr('NULL'), + 'state' => 'host_state', + 'state_change' => 'host_last_state_change' + ) + ); + $hosts->select()->where('sgo.name1 IS NOT NULL'); // TODO(9458): Should be possible using our filters! + $this->subQueries[] = $hosts; + $services = $this->createSubQuery( + 'Servicestatus', + array( + 'handled' => 'service_handled', + 'host_state' => 'host_state', + 'servicegroup_alias', + 'servicegroup_name', + 'object_type', + 'severity' => 'service_severity', + 'state' => 'service_state', + 'state_change' => 'service_last_state_change' + ) + ); + $services->select()->where('sgo.name1 IS NOT NULL'); // TODO(9458): Should be possible using our filters! + $this->subQueries[] = $services; + $this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); + $this->select->from(array('statussummary' => $this->summaryQuery), array()); + $this->group(array('servicegroup_name', 'servicegroup_alias')); + $this->joinedVirtualTables['servicestatussummary'] = true; + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + if (! $this->hasAliasName($columnOrAlias)) { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php new file mode 100644 index 000000000..c5f68db85 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php @@ -0,0 +1,260 @@ + array( + 'notification_output' => 'sn.output', + 'notification_start_time' => 'UNIX_TIMESTAMP(sn.start_time)', + 'notification_state' => 'sn.state', + 'notification_object_id' => 'sn.object_id', + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'history' => array( + 'type' => "('notify')", + 'timestamp' => 'UNIX_TIMESTAMP(sn.start_time)', + 'object_id' => 'sn.object_id', + 'state' => 'sn.state', + 'output' => null + ), + 'contactnotifications' => array( + 'contact' => 'cno.name1 COLLATE latin1_general_ci', + 'notification_contact_name' => 'cno.name1', + 'contact_object_id' => 'cno.object_id' + ), + 'acknowledgements' => array( + 'acknowledgement_entry_time' => 'UNIX_TIMESTAMP(a.entry_time)', + 'acknowledgement_author_name' => 'a.author_name', + 'acknowledgement_comment_data' => 'a.comment_data' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ) + ); + + /** + * {@inheritdoc} + */ + public function whereToSql($col, $sign, $expression) + { + if ($col === 'UNIX_TIMESTAMP(sn.start_time)') { + return 'sn.start_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + switch ($this->ds->getDbType()) { + case 'mysql': + $concattedContacts = "GROUP_CONCAT(" + . "DISTINCT cno.name1 ORDER BY cno.name1 SEPARATOR ', '" + . ") COLLATE latin1_general_ci"; + break; + case 'pgsql': + // TODO: Find a way to order the contact alias list: + $concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(DISTINCT cno.name1), ', ')"; + break; + case 'oracle': + // TODO: This is only valid for Oracle >= 11g Release 2 + $concattedContacts = "LISTAGG(cno.name1, ', ') WITHIN GROUP (ORDER BY cno.name1)"; + // Alternatives: + // + // RTRIM(XMLAGG(XMLELEMENT(e, column_name, ',').EXTRACT('//text()')), + // + // not supported and not documented but works since 10.1, + // however it is NOT always present: + // WM_CONCAT(c.alias) + break; + } + + $this->columnMap['history']['output'] = "('[' || $concattedContacts || '] ' || sn.output)"; + + $this->select->from( + array('sn' => $this->prefix . 'notifications'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sn.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['notifications'] = true; + } + + /** + * Join virtual table history + */ + protected function joinHistory() + { + $this->requireVirtualTable('contactnotifications'); + } + + /** + * Join contact notifications + */ + protected function joinContactnotifications() + { + $this->select->joinLeft( + array('cn' => $this->prefix . 'contactnotifications'), + 'cn.notification_id = sn.notification_id', + array() + ); + $this->select->joinLeft( + array('cno' => $this->prefix . 'objects'), + 'cno.object_id = cn.contact_object_id', + array() + ); + } + + /** + * Join acknowledgements + */ + protected function joinAcknowledgements() + { + $this->select->joinLeft( + array('a' => $this->prefix . 'acknowledgements'), + 'a.object_id = sn.object_id', + array() + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ( + $this->hasJoinedVirtualTable('history') + || $this->hasJoinedVirtualTable('hostgroups') + || $this->hasJoinedVirtualTable('servicegroups') + ) { + $group = array('sn.notification_id', 'so.object_id'); + if ($this->hasJoinedVirtualTable('contactnotifications') && !$this->hasJoinedVirtualTable('history')) { + $group[] = 'cno.object_id'; + } + } elseif ($this->hasJoinedVirtualTable('contactnotifications')) { + $group = array('sn.notification_id', 'cno.object_id', 'so.object_id'); + } + + if (! empty($group)) { + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('services')) { + $group[] = 's.service_id'; + } + + if ($this->hasJoinedVirtualTable('acknowledgements')) { + $group[] = 'a.acknowledgement_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php new file mode 100644 index 000000000..4c8ce7082 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php @@ -0,0 +1,182 @@ + 0, + 'hard_state' => 1 + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'statehistory' => array( + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'history' => array( + 'type' => "(CASE WHEN sh.state_type = 1 THEN 'hard_state' ELSE 'soft_state' END)", + 'timestamp' => 'UNIX_TIMESTAMP(sh.state_time)', + 'object_id' => 'sh.object_id', + 'state' => 'sh.state', + 'output' => "('[ ' || sh.current_check_attempt || '/' || sh.max_check_attempts || ' ] ' || sh.output)", + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ) + ); + + /** + * {@inheritdoc} + */ + public function whereToSql($col, $sign, $expression) + { + if ($col === 'UNIX_TIMESTAMP(sh.state_time)') { + return 'sh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); + } elseif ( + $col === $this->columnMap['history']['type'] + && ! is_array($expression) + && array_key_exists($expression, $this->types) + ) { + return 'sh.state_type ' . $sign . ' ' . $this->types[$expression]; + } else { + return parent::whereToSql($col, $sign, $expression); + } + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sh' => $this->prefix . 'statehistory'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sh.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['statehistory'] = true; + $this->joinedVirtualTables['history'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('servicegroups')) { + $group = array('sh.statehistory_id', 'so.object_id'); + if ($this->hasJoinedVirtualTable('services')) { + $group[] = 'h.host_id'; + $group[] = 's.service_id'; + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php new file mode 100644 index 000000000..0a03f273d --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php @@ -0,0 +1,367 @@ + array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_action_url' => 'h.action_url', + 'host_address' => 'h.address', + 'host_alias' => 'h.alias COLLATE latin1_general_ci', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', + 'host_icon_image' => 'h.icon_image', + 'host_icon_image_alt' => 'h.icon_image_alt', + 'host_ipv4' => 'INET_ATON(h.address)', + 'host_notes_url' => 'h.notes_url' + ), + 'hoststatus' => array( + 'host_acknowledged' => 'hs.problem_has_been_acknowledged', + 'host_acknowledgement_type' => 'hs.acknowledgement_type', + 'host_active_checks_enabled' => 'hs.active_checks_enabled', + 'host_active_checks_enabled_changed' => 'CASE WHEN hs.active_checks_enabled = h.active_checks_enabled THEN 0 ELSE 1 END', + 'host_attempt' => 'hs.current_check_attempt || \'/\' || hs.max_check_attempts', + 'host_check_command' => 'hs.check_command', + 'host_check_execution_time' => 'hs.execution_time', + 'host_check_latency' => 'hs.latency', + 'host_check_source' => 'hs.check_source', + 'host_check_type' => 'hs.check_type', + 'host_current_check_attempt' => 'hs.current_check_attempt', + 'host_current_notification_number' => 'hs.current_notification_number', + 'host_event_handler' => 'hs.event_handler', + 'host_event_handler_enabled' => 'hs.event_handler_enabled', + 'host_event_handler_enabled_changed' => 'CASE WHEN hs.event_handler_enabled = h.event_handler_enabled THEN 0 ELSE 1 END', + 'host_failure_prediction_enabled' => 'hs.failure_prediction_enabled', + 'host_flap_detection_enabled' => 'hs.flap_detection_enabled', + 'host_flap_detection_enabled_changed' => 'CASE WHEN hs.flap_detection_enabled = h.flap_detection_enabled THEN 0 ELSE 1 END', + 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END', + 'host_hard_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE CASE WHEN hs.state_type = 1 THEN hs.current_state ELSE hs.last_hard_state END END', + 'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', + 'host_is_flapping' => 'hs.is_flapping', + 'host_is_reachable' => 'hs.is_reachable', + 'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)', + 'host_last_hard_state' => 'hs.last_hard_state', + 'host_last_hard_state_change' => 'UNIX_TIMESTAMP(hs.last_hard_state_change)', + 'host_last_notification' => 'UNIX_TIMESTAMP(hs.last_notification)', + 'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)', + 'host_last_time_down' => 'UNIX_TIMESTAMP(hs.last_time_down)', + 'host_last_time_unreachable' => 'UNIX_TIMESTAMP(hs.last_time_unreachable)', + 'host_last_time_up' => 'UNIX_TIMESTAMP(hs.last_time_up)', + 'host_long_output' => 'hs.long_output', + 'host_max_check_attempts' => 'hs.max_check_attempts', + 'host_modified_host_attributes' => 'hs.modified_host_attributes', + 'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END', + 'host_next_notification' => 'UNIX_TIMESTAMP(hs.next_notification)', + 'host_no_more_notifications' => 'hs.no_more_notifications', + 'host_normal_check_interval' => 'hs.normal_check_interval', + 'host_notifications_enabled' => 'hs.notifications_enabled', + 'host_notifications_enabled_changed' => 'CASE WHEN hs.notifications_enabled = h.notifications_enabled THEN 0 ELSE 1 END', + 'host_obsessing' => 'hs.obsess_over_host', + 'host_obsessing_changed' => 'CASE WHEN hs.obsess_over_host = h.obsess_over_host THEN 0 ELSE 1 END', + 'host_output' => 'hs.output', + 'host_passive_checks_enabled' => 'hs.passive_checks_enabled', + 'host_passive_checks_enabled_changed' => 'CASE WHEN hs.passive_checks_enabled = h.passive_checks_enabled THEN 0 ELSE 1 END', + 'host_percent_state_change' => 'hs.percent_state_change', + 'host_perfdata' => 'hs.perfdata', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', + 'host_problem_has_been_acknowledged' => 'hs.problem_has_been_acknowledged', + 'host_process_performance_data' => 'hs.process_performance_data', + 'host_retry_check_interval' => 'hs.retry_check_interval', + 'host_scheduled_downtime_depth' => 'hs.scheduled_downtime_depth', + 'host_severity' => 'CASE WHEN hs.current_state = 0 + THEN + CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL + THEN 16 + ELSE 0 + END + + + CASE WHEN hs.problem_has_been_acknowledged = 1 + THEN 2 + ELSE + CASE WHEN hs.scheduled_downtime_depth > 0 + THEN 1 + ELSE 4 + END + END + ELSE + CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 16 + WHEN hs.current_state = 1 THEN 32 + WHEN hs.current_state = 2 THEN 64 + ELSE 256 + END + + + CASE WHEN hs.problem_has_been_acknowledged = 1 + THEN 2 + ELSE + CASE WHEN hs.scheduled_downtime_depth > 0 + THEN 1 + ELSE 4 + END + END + END + + + CASE WHEN hs.state_type = 1 + THEN 8 + ELSE 0 + END', + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', + 'host_state_type' => 'hs.state_type', + 'host_status_update_time' => 'hs.status_update_time', + 'host_unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 END' + ), + 'services' => array( + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_action_url' => 's.action_url', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1', + 'service_icon_image' => 's.icon_image', + 'service_icon_image_alt' => 's.icon_image_alt', + 'service_notes_url' => 's.notes_url' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'servicestatus' => array( + 'service_active_checks_enabled' => 'ss.active_checks_enabled', + 'service_event_handler_enabled' => 'ss.event_handler_enabled', + 'service_flap_detection_enabled' => 'ss.flap_detection_enabled', + 'service_handled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END', + 'service_hard_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE CASE WHEN ss.state_type = 1 THEN ss.current_state ELSE ss.last_hard_state END END', + 'service_is_flapping' => 'ss.is_flapping', + 'service_is_passive_checked' => 'CASE WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN 1 ELSE 0 END', + 'service_last_hard_state_change' => 'UNIX_TIMESTAMP(ss.last_hard_state_change)', + 'service_last_state_change' => 'UNIX_TIMESTAMP(ss.last_state_change)', + 'service_notifications_enabled' => 'ss.notifications_enabled', + 'service_severity' => 'CASE WHEN ss.current_state = 0 + THEN + CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL + THEN 16 + ELSE 0 + END + + + CASE WHEN ss.problem_has_been_acknowledged = 1 + THEN 2 + ELSE + CASE WHEN ss.scheduled_downtime_depth > 0 + THEN 1 + ELSE 4 + END + END + ELSE + CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 16 + WHEN ss.current_state = 1 THEN 32 + WHEN ss.current_state = 2 THEN 128 + WHEN ss.current_state = 3 THEN 64 + ELSE 256 + END + + + CASE WHEN hs.current_state > 0 + THEN 1024 + ELSE + CASE WHEN ss.problem_has_been_acknowledged = 1 + THEN 512 + ELSE + CASE WHEN ss.scheduled_downtime_depth > 0 + THEN 256 + ELSE 2048 + END + END + END + END + + + CASE WHEN ss.state_type = 1 + THEN 8 + ELSE 0 + END', + 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END', + 'service_unhandled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) = 0 THEN 1 ELSE 0 END', + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('so' => $this->prefix . 'objects'), + array() + )->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['services'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join service status + */ + protected function joinServicestatus() + { + $this->requireVirtualTable('hoststatus'); + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('servicegroups')) { + $group = array('s.service_id', 'so.object_id'); + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('hoststatus')) { + $group[] = 'hs.hoststatus_id'; + } + + if ($this->hasJoinedVirtualTable('servicestatus')) { + $group[] = 'ss.servicestatus_id'; + } + + if ($this->hasJoinedVirtualTable('hostgroups')) { + $selected = false; + foreach ($this->columns as $alias => $column) { + if ($column instanceof Zend_Db_Expr) { + continue; + } + + $table = $this->aliasToTableName( + $this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias) + ); + if ($table === 'hostgroups') { + $selected = true; + break; + } + } + + if ($selected) { + $group[] = 'hg.hostgroup_id'; + $group[] = 'hgo.object_id'; + } + } + + if ($this->hasJoinedVirtualTable('servicegroups')) { + $selected = false; + foreach ($this->columns as $alias => $column) { + if ($column instanceof Zend_Db_Expr) { + continue; + } + + $table = $this->aliasToTableName( + $this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias) + ); + if ($table === 'servicegroups') { + $selected = true; + break; + } + } + + if ($selected) { + $group[] = 'sg.servicegroup_id'; + $group[] = 'sgo.object_id'; + } + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php new file mode 100644 index 000000000..325203717 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php @@ -0,0 +1,87 @@ + array( + 'services_critical' => 'SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN state = 2 AND handled = 1 THEN 1 ELSE 0 END)', +// 'services_critical_handled_last_state_change' => 'MAX(CASE WHEN state = 2 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN state = 2 AND handled = 0 THEN 1 ELSE 0 END)', +// 'services_critical_unhandled_last_state_change' => 'MAX(CASE WHEN state = 2 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_ok' => 'SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END)', +// 'services_ok_last_state_change' => 'MAX(CASE WHEN state = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_pending' => 'SUM(CASE WHEN state = 99 THEN 1 ELSE 0 END)', +// 'services_pending_last_state_change' => 'MAX(CASE WHEN state = 99 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_total' => 'SUM(1)', + 'services_unknown' => 'SUM(CASE WHEN state = 3 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN state = 3 AND handled = 1 THEN 1 ELSE 0 END)', +// 'services_unknown_handled_last_state_change' => 'MAX(CASE WHEN state = 3 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN state = 3 AND handled = 0 THEN 1 ELSE 0 END)', +// 'services_unknown_unhandled_last_state_change' => 'MAX(CASE WHEN state = 3 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_warning' => 'SUM(CASE WHEN state = 1 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN state = 1 AND handled = 1 THEN 1 ELSE 0 END)', +// 'services_warning_handled_last_state_change' => 'MAX(CASE WHEN state = 1 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN state = 1 AND handled = 0 THEN 1 ELSE 0 END)', +// 'services_warning_unhandled_last_state_change' => 'MAX(CASE WHEN state = 1 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)' + ) + ); + + /** + * The service status sub select + * + * @var ServiceStatusQuery + */ + protected $subSelect; + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + $this->subSelect->applyFilter(clone $filter); + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + // TODO(el): Allow to switch between hard and soft states + $this->subSelect = $this->createSubQuery( + 'servicestatus', + array( + 'handled' => 'service_handled', + 'state' => 'service_state', + 'state_change' => 'service_last_state_change' + ) + ); + $this->select->from( + array('servicestatussummary' => $this->subSelect->setIsSubQuery(true)), + array() + ); + $this->joinedVirtualTables['servicestatussummary'] = true; + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->subSelect->where($condition, $value); + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php index e979cdc08..41f18572b 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php @@ -1,63 +1,153 @@ 0, - 'hard_state' => 1, - ); - + /** + * {@inheritdoc} + */ protected $columnMap = array( 'statehistory' => array( - 'raw_timestamp' => 'sh.state_time', - 'timestamp' => 'UNIX_TIMESTAMP(sh.state_time)', - 'state_time' => 'sh.state_time', - 'object_id' => 'sho.object_id', - 'type' => "(CASE WHEN sh.state_type = 1 THEN 'hard_state' ELSE 'soft_state' END)", - 'state' => 'sh.state', - 'state_type' => 'sh.state_type', - 'output' => 'sh.output', - 'attempt' => 'sh.current_check_attempt', - 'max_attempts' => 'sh.max_check_attempts', - - 'host' => 'sho.name1 COLLATE latin1_general_ci', - 'service' => 'sho.name2 COLLATE latin1_general_ci', - 'host_name' => 'sho.name1 COLLATE latin1_general_ci', - 'service_description' => 'sho.name2 COLLATE latin1_general_ci', - 'service_host_name' => 'sho.name1 COLLATE latin1_general_ci', - 'service_description' => 'sho.name2 COLLATE latin1_general_ci', - 'object_type' => "CASE WHEN sho.objecttype_id = 1 THEN 'host' ELSE 'service' END" + 'object_type' => 'sth.object_type' + ), + 'history' => array( + 'type' => 'sth.type', + 'timestamp' => 'sth.timestamp', + 'object_id' => 'sth.object_id', + 'state' => 'sth.state', + 'output' => 'sth.output' + ), + 'hosts' => array( + 'host_display_name' => 'sth.host_display_name', + 'host_name' => 'sth.host_name' + ), + 'services' => array( + 'service_description' => 'sth.service_description', + 'service_display_name' => 'sth.service_display_name', + 'service_host_name' => 'sth.service_host_name' ) ); - public function whereToSql($col, $sign, $expression) - { - if ($col === 'UNIX_TIMESTAMP(sh.state_time)') { - return 'sh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); - } elseif ($col === $this->columnMap['statehistory']['type'] - && is_array($expression) === false - && array_key_exists($expression, $this->types) === true - ) { - return 'sh.state_type ' . $sign . ' ' . $this->types[$expression]; - } else { - return parent::whereToSql($col, $sign, $expression); - } - } + /** + * The union + * + * @var Zend_Db_Select + */ + protected $stateHistoryQuery; + /** + * Subqueries used for the state history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { + $this->stateHistoryQuery = $this->db->select(); $this->select->from( - array('sho' => $this->prefix . 'objects'), - array() - )->join( - array('sh' => $this->prefix . 'statehistory'), - 'sho.' . $this->object_id . ' = sh.' . $this->object_id . ' AND sho.is_active = 1', + array('sth' => $this->stateHistoryQuery), array() ); - $this->joinedVirtualTables = array('statehistory' => true); + $this->joinedVirtualTables['statehistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['statehistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hoststatehistory', $columns); + $this->subQueries[] = $hosts; + $this->stateHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['statehistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicestatehistory', $columns); + $this->subQueries[] = $services; + $this->stateHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index e390aa114..1a929a006 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -1,11 +1,8 @@ array( 'host' => 'ho.name1 COLLATE latin1_general_ci', - 'host_name' => 'ho.name1 COLLATE latin1_general_ci', - 'host_display_name' => 'h.display_name', + 'host_name' => 'ho.name1', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', 'host_alias' => 'h.alias', 'host_address' => 'h.address', 'host_ipv4' => 'INET_ATON(h.address)', 'host_icon_image' => 'h.icon_image', + 'host_icon_image_alt' => 'h.icon_image_alt', 'host_action_url' => 'h.action_url', + 'host_notes' => 'h.notes', 'host_notes_url' => 'h.notes_url' ), 'hoststatus' => array( @@ -118,11 +117,11 @@ class StatusQuery extends IdoQuery 'host_modified_host_attributes' => 'hs.modified_host_attributes', 'host_event_handler' => 'hs.event_handler', - 'host_check_command' => 'hs.check_command', 'host_normal_check_interval' => 'hs.normal_check_interval', 'host_retry_check_interval' => 'hs.retry_check_interval', 'host_check_timeperiod_object_id' => 'hs.check_timeperiod_object_id', 'host_status_update_time' => 'hs.status_update_time', + 'host_is_reachable' => 'hs.is_reachable', 'host_severity' => 'CASE WHEN hs.current_state = 0 THEN CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL @@ -161,18 +160,25 @@ class StatusQuery extends IdoQuery END' ), 'hostgroups' => array( - 'hostgroup' => 'hgo.name1', + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci' ), 'servicegroups' => array( - 'servicegroup' => 'sgo.name1', + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' ), 'services' => array( - 'service_host_name' => 'so.name1 COLLATE latin1_general_ci', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1', 'service' => 'so.name2 COLLATE latin1_general_ci', - 'service_description' => 'so.name2 COLLATE latin1_general_ci', - 'service_display_name' => 's.display_name', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', 'service_icon_image' => 's.icon_image', + 'service_icon_image_alt' => 's.icon_image_alt', 'service_action_url' => 's.action_url', + 'service_notes' => 's.notes', 'service_notes_url' => 's.notes_url', 'object_type' => '(\'service\')' ), @@ -259,6 +265,7 @@ class StatusQuery extends IdoQuery 'service_check_timeperiod_object_id' => 'ss.check_timeperiod_object_id', 'service_status_update_time' => 'ss.status_update_time', 'service_problem' => 'CASE WHEN ss.current_state = 0 THEN 0 ELSE 1 END', + 'service_is_reachable' => 'ss.is_reachable', 'service_severity' => 'CASE WHEN ss.current_state = 0 THEN CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL @@ -301,42 +308,6 @@ class StatusQuery extends IdoQuery ELSE 0 END' ), - - 'serviceproblemsummary' => array( - 'host_unhandled_services' => 'sps.unhandled_services_count' - ), - - 'lasthostcommentgeneric' => array( - 'host_last_comment' => 'hlcg.last_comment_data' - ), - - 'lasthostcommentdowntime' => array( - 'host_last_downtime' => 'hlcd.last_downtime_data' - ), - - 'lasthostcommentflapping' => array( - 'host_last_flapping' => 'hlcf.last_flapping_data' - ), - - 'lasthostcommentack' => array( - 'host_last_ack' => 'hlca.last_ack_data' - ), - - 'lastservicecommentgeneric' => array( - 'service_last_comment' => 'slcg.last_comment_data' - ), - - 'lastservicecommentdowntime' => array( - 'service_last_downtime' => 'slcd.last_downtime_data' - ), - - 'lastservicecommentflapping' => array( - 'service_last_flapping' => 'slcf.last_flapping_data' - ), - - 'lastservicecommentack' => array( - 'service_last_ack' => 'slca.last_ack_data' - ) ); protected function joinBaseTables() @@ -345,6 +316,10 @@ class StatusQuery extends IdoQuery $this->columnMap['hoststatus']['host_check_source'] = '(NULL)'; $this->columnMap['servicestatus']['service_check_source'] = '(NULL)'; } + if (version_compare($this->getIdoVersion(), '1.13.0', '<')) { + $this->columnMap['hoststatus']['host_is_reachable'] = '(NULL)'; + $this->columnMap['servicestatus']['service_is_reachable'] = '(NULL)'; + } $this->select->from(array('ho' => $this->prefix . 'objects'), array()) ->join( array('hs' => $this->prefix . 'hoststatus'), @@ -368,7 +343,7 @@ class StatusQuery extends IdoQuery case 'CASE WHEN ss.current_state = 0 THEN 0 ELSE 1 END': if ($sign !== '=') break; - + if ($expression) { return 'ss.current_state > 0'; } else { @@ -421,7 +396,7 @@ class StatusQuery extends IdoQuery array('so' => $this->prefix . 'objects'), 'so.' . $this->object_id . ' = s.service_object_id AND so.is_active = 1', array() - )->joinLeft( + )->join( array('ss' => $this->prefix . 'servicestatus'), 'so.' . $this->object_id . ' = ss.service_object_id', array() @@ -439,15 +414,15 @@ class StatusQuery extends IdoQuery protected function joinHostHostgroups() { - $this->select->join( + $this->select->joinLeft( array('hgm' => $this->prefix . 'hostgroup_members'), 'hgm.host_object_id = h.host_object_id', array() - )->join( + )->joinLeft( array('hg' => $this->prefix . 'hostgroups'), 'hgm.hostgroup_id = hg.' . $this->hostgroup_id, array() - )->join( + )->joinLeft( array('hgo' => $this->prefix . 'objects'), 'hgo.' . $this->object_id . ' = hg.hostgroup_object_id AND hgo.is_active = 1', array() @@ -464,15 +439,15 @@ class StatusQuery extends IdoQuery protected function joinServiceHostgroups() { - $this->select->join( + $this->select->joinLeft( array('hgm' => $this->prefix . 'hostgroup_members'), 'hgm.host_object_id = s.host_object_id', array() - )->join( + )->joinLeft( array('hg' => $this->prefix . 'hostgroups'), 'hgm.hostgroup_id = hg.' . $this->hostgroup_id, array() - )->join( + )->joinLeft( array('hgo' => $this->prefix . 'objects'), 'hgo.' . $this->object_id . ' = hg.hostgroup_object_id' . ' AND hgo.is_active = 1', @@ -489,15 +464,15 @@ class StatusQuery extends IdoQuery protected function joinServicegroups() { $this->requireVirtualTable('services'); - $this->select->join( + $this->select->joinLeft( array('sgm' => $this->prefix . 'servicegroup_members'), 'sgm.service_object_id = s.service_object_id', array() - )->join( + )->joinLeft( array('sg' => $this->prefix . 'servicegroups'), 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, array() - )->join( + )->joinLeft( array('sgo' => $this->prefix . 'objects'), 'sgo.' . $this->object_id. ' = sg.servicegroup_object_id' . ' AND sgo.is_active = 1', @@ -514,149 +489,4 @@ class StatusQuery extends IdoQuery return $this; } - - protected function joinServiceproblemsummary() - { - $sub = new Zend_Db_Expr('(SELECT' - . ' SUM(CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth) > 0 THEN 0 ELSE 1 END) AS unhandled_services_count,' - . ' SUM(CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END) AS handled_services_count,' - . ' s.host_object_id FROM icinga_servicestatus ss' - . ' JOIN icinga_services s' - . ' ON s.service_object_id = ss.service_object_id' - . ' AND ss.current_state > 0' - . ' GROUP BY s.host_object_id)'); - $this->select->joinLeft( - array('sps' => $sub), - 'sps.host_object_id = hs.host_object_id', - array() - ); - return; - - $this->select->joinleft( - array ('sps' => new \Zend_Db_Expr( - '(SELECT COUNT(s.service_object_id) as unhandled_service_count, s.host_object_id as host_object_id '. - 'FROM icinga_services s INNER JOIN icinga_servicestatus ss ON ss.current_state != 0 AND '. - 'ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0 AND '. - 'ss.service_object_id = s.service_object_id '. - 'GROUP BY s.host_object_id'. - ')')), - 'sps.host_object_id = hs.host_object_id', - array() - ); - } - - /** - * Create a subquery to join comments into status query - * @param int $entryType - * @param string $fieldName - * @return Zend_Db_Expr - */ - protected function getLastCommentSubQuery($entryType, $fieldName) - { - $sub = '(SELECT' - . ' c.object_id,' - . " '[' || c.author_name || '] ' || c.comment_data AS $fieldName" - . ' FROM icinga_comments c JOIN (' - . ' SELECT MAX(comment_id) AS comment_id, object_id FROM icinga_comments' - . ' WHERE entry_type = ' . $entryType . ' GROUP BY object_id' - . ' ) lc ON c.comment_id = lc.comment_id)'; - - return new Zend_Db_Expr($sub); - } - - /** - * Join last host comment - */ - protected function joinLasthostcommentgeneric() - { - $this->select->joinLeft( - array('hlcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')), - 'hlcg.object_id = hs.host_object_id', - array() - ); - } - - /** - * Join last host downtime comment - */ - protected function joinLasthostcommentdowntime() - { - $this->select->joinLeft( - array('hlcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')), - 'hlcg.object_id = hs.host_object_id', - array() - ); - } - - /** - * Join last host flapping comment - */ - protected function joinLastHostcommentflapping() - { - $this->select->joinLeft( - array('hlcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')), - 'hlcg.object_id = hs.host_object_id', - array() - ); - } - - /** - * Join last host acknowledgement comment - */ - protected function joinLasthostcommentack() - { - $this->select->joinLeft( - array('hlca' => $this->getLastCommentSubQuery(4, 'last_ack_data')), - 'hlca.object_id = hs.host_object_id', - array() - ); - } - - /** - * Join last service comment - */ - protected function joinLastservicecommentgeneric() - { - $this->select->joinLeft( - array('slcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')), - 'slcg.object_id = ss.service_object_id', - array() - ); - } - - /** - * Join last service downtime comment - */ - protected function joinLastservicecommentdowntime() - { - $this->select->joinLeft( - array('slcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')), - 'slcd.object_id = ss.service_object_id', - array() - ); - } - - /** - * Join last service flapping comment - */ - protected function joinLastservicecommentflapping() - { - $this->select->joinLeft( - array('slcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')), - 'slcf.object_id = ss.service_object_id', - array() - ); - } - - /** - * Join last service acknowledgement comment - */ - protected function joinLastservicecommentack() - { - $this->select->joinLeft( - array('slca' => $this->getLastCommentSubQuery(4, 'last_ack_data')), - 'slca.object_id = ss.service_object_id', - array() - ); - } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php similarity index 66% rename from modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php rename to modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php index 29337e65f..9d94e8149 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php @@ -1,22 +1,21 @@ array( - 'service_host_name' => 'so.name1', - 'service_description' => 'so.name2', - ), 'hoststatussummary' => array( 'hosts_total' => 'SUM(CASE WHEN object_type = \'host\' THEN 1 ELSE 0 END)', 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', @@ -24,13 +23,13 @@ class StatusSummaryQuery extends IdoQuery 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)', 'hosts_pending_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)', - 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime > 0 THEN 1 ELSE 0 END)', - 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)', 'hosts_down_passive' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'hosts_down_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)', - 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime > 0 THEN 1 ELSE 0 END)', - 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)', 'hosts_unreachable_passive' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'hosts_unreachable_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'hosts_active' => 'SUM(CASE WHEN object_type = \'host\' AND is_active_checked = 1 THEN 1 ELSE 0 END)', @@ -44,25 +43,25 @@ class StatusSummaryQuery extends IdoQuery 'servicestatussummary' => array( 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', 'services_problem' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 THEN 1 ELSE 0 END)', - 'services_problem_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND acknowledged + in_downtime + host_problem > 0 THEN 1 ELSE 0 END)', - 'services_problem_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND acknowledged + in_downtime + host_problem = 0 THEN 1 ELSE 0 END)', + 'services_problem_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND handled + host_problem > 0 THEN 1 ELSE 0 END)', + 'services_problem_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND handled + host_problem = 0 THEN 1 ELSE 0 END)', 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', 'services_ok_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', 'services_pending_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', - 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_problem > 0 THEN 1 ELSE 0 END)', - 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_problem = 0 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_problem > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_problem = 0 THEN 1 ELSE 0 END)', 'services_warning_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_warning_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)', - 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_problem > 0 THEN 1 ELSE 0 END)', - 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_problem = 0 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_problem > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_problem = 0 THEN 1 ELSE 0 END)', 'services_critical_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_critical_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)', - 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_problem > 0 THEN 1 ELSE 0 END)', - 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_problem = 0 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_problem > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_problem = 0 THEN 1 ELSE 0 END)', 'services_unknown_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_unknown_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_active' => 'SUM(CASE WHEN object_type = \'service\' AND is_active_checked = 1 THEN 1 ELSE 0 END)', @@ -86,114 +85,134 @@ We have to find a better solution here. 'services_ok_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_pending_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 99 THEN 1 ELSE 0 END)', 'services_pending_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', - 'services_warning_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND acknowledged + in_downtime > 0 THEN 1 ELSE 0 END)', - 'services_warning_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'services_warning_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)', 'services_warning_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_warning_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', - 'services_critical_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND acknowledged + in_downtime > 0 THEN 1 ELSE 0 END)', - 'services_critical_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'services_critical_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)', 'services_critical_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_critical_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', - 'services_unknown_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND acknowledged + in_downtime > 0 THEN 1 ELSE 0 END)', - 'services_unknown_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'services_unknown_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND handled = 0 THEN 1 ELSE 0 END)', 'services_unknown_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_unknown_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_ok_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 0 THEN 1 ELSE 0 END)', 'services_ok_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_pending_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 99 THEN 1 ELSE 0 END)', 'services_pending_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', - 'services_warning_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND acknowledged + in_downtime > 0 THEN 1 ELSE 0 END)', - 'services_warning_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'services_warning_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)', 'services_warning_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_warning_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', - 'services_critical_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND acknowledged + in_downtime > 0 THEN 1 ELSE 0 END)', - 'services_critical_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'services_critical_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)', 'services_critical_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_critical_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', - 'services_unknown_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND acknowledged + in_downtime > 0 THEN 1 ELSE 0 END)', - 'services_unknown_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'services_unknown_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND handled = 0 THEN 1 ELSE 0 END)', 'services_unknown_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_unknown_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)' ) ); - protected function joinBaseTables() + /** + * The union + * + * @var Zend_Db_Select + */ + protected $summaryQuery; + + /** + * Subqueries used for the summary query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) { - $hosts = $this->db->select()->from( - array('ho' => $this->prefix . 'objects'), - array() - )->join( - array('hs' => $this->prefix . 'hoststatus'), - 'ho.object_id = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', - array( - '' - ) - )->join( - array('h' => $this->prefix . 'hosts'), - 'hs.host_object_id = h.host_object_id', - array() - ); - $services = clone $hosts; - $services->join( - array('s' => $this->prefix . 'services'), - 's.host_object_id = h.host_object_id', - array() - )->join( - array('so' => $this->prefix . 'objects'), - 'so.' . $this->object_id . ' = s.service_object_id AND so.is_active = 1', - array() - )->joinLeft( - array('ss' => $this->prefix . 'servicestatus'), - 'so.' . $this->object_id . ' = ss.service_object_id', - array() - ); - $hosts->columns(array( - 'state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', - 'acknowledged' => 'hs.problem_has_been_acknowledged', - 'in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', - 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', - 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', - 'is_passive_checked' => 'CASE WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN 1 ELSE 0 END', - 'is_active_checked' => 'hs.active_checks_enabled', - 'is_processing_events' => 'hs.event_handler_enabled', - 'is_triggering_notifications' => 'hs.notifications_enabled', - 'is_allowed_to_flap' => 'hs.flap_detection_enabled', - 'is_flapping' => 'hs.is_flapping', - 'object_type' => '(\'host\')' - )); - $services->columns(array( - 'state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END', - 'acknowledged' => 'ss.problem_has_been_acknowledged', - 'in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', - 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', - 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', - 'is_passive_checked' => 'CASE WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN 1 ELSE 0 END', - 'is_active_checked' => 'ss.active_checks_enabled', - 'is_processing_events' => 'ss.event_handler_enabled', - 'is_triggering_notifications' => 'ss.notifications_enabled', - 'is_allowed_to_flap' => 'ss.flap_detection_enabled', - 'is_flapping' => 'ss.is_flapping', - 'object_type' => '(\'service\')' - )); - $union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); - $this->subHosts = $hosts; - $this->subServices = $services; - $this->select->from(array('statussummary' => $union), array()); - $this->joinedVirtualTables = array( - 'services' => true, - 'servicestatussummary' => true, - 'hoststatussummary' => true - ); + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; } - public function whereToSql($col, $sign, $expression) + /** + * {@inheritdoc} + */ + protected function joinBaseTables() { - if ($col === 'so.name1') { - $this->subServices->where('so.name1 ' . $sign . ' ?', $expression); - return ''; - return 'sh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); - } else { - return parent::whereToSql($col, $sign, $expression); + // TODO(el): Allow to switch between hard and soft states + $hosts = $this->createSubQuery( + 'Hoststatus', + array( + 'handled' => 'host_handled', + 'host_problem', + 'host_state' => new Zend_Db_Expr('NULL'), + 'is_active_checked' => 'host_active_checks_enabled', + 'is_allowed_to_flap' => 'host_flap_detection_enabled', + 'is_flapping' => 'host_is_flapping', + 'is_passive_checked' => 'host_is_passive_checked', + 'is_processing_events' => 'host_event_handler_enabled', + 'is_triggering_notifications' => 'host_notifications_enabled', + 'object_type', + 'severity' => 'host_severity', + 'state_change' => 'host_last_state_change', + 'state' => 'host_state' + ) + ); + $this->subQueries[] = $hosts; + $services = $this->createSubQuery( + 'Servicestatus', + array( + 'handled' => 'service_handled', + 'host_problem', + 'host_state' => 'host_hard_state', + 'is_active_checked' => 'service_active_checks_enabled', + 'is_allowed_to_flap' => 'service_flap_detection_enabled', + 'is_flapping' => 'service_is_flapping', + 'is_passive_checked' => 'service_is_passive_checked', + 'is_processing_events' => 'service_event_handler_enabled', + 'is_triggering_notifications' => 'service_notifications_enabled', + 'object_type', + 'severity' => 'service_severity', + 'state_change' => 'service_last_state_change', + 'state' => 'service_state' + ) + ); + $this->subQueries[] = $services; + $this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); + $this->select->from(array('statussummary' => $this->summaryQuery), array()); + $this->joinedVirtualTables['hoststatussummary'] = true; + $this->joinedVirtualTables['servicestatussummary'] = true; + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + if (! $this->hasAliasName($columnOrAlias)) { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; } } diff --git a/modules/monitoring/library/Monitoring/Backend/Livestatus/LivestatusBackend.php b/modules/monitoring/library/Monitoring/Backend/Livestatus/LivestatusBackend.php index 12de5a9ee..c7ed2740d 100644 --- a/modules/monitoring/library/Monitoring/Backend/Livestatus/LivestatusBackend.php +++ b/modules/monitoring/library/Monitoring/Backend/Livestatus/LivestatusBackend.php @@ -1,4 +1,5 @@ 'host_name', - + 'services_total' => 'state != 9999', 'services_problem' => 'state > 0', 'services_problem_handled' => 'state > 0 & (scheduled_downtime_depth > 0 | acknowledged = 1 | host_state > 0)', @@ -50,7 +49,7 @@ if (! array_key_exists($col, $this->available_columns)) { throw new ProgrammingError('No such column: %s', $col); } $filter = $this->filterStringToFilter($this->available_columns[$col]); - + //Filter::fromQueryString(str_replace(' ', '', $this->available_columns[$col])); $parts[] = $this->renderFilter( $filter, 'Stats', 0, false); } diff --git a/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php b/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php index c8f75f540..6ee534b17 100644 --- a/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php +++ b/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php @@ -1,4 +1,5 @@ type = lcfirst(substr($class, 0, -7)); } else { throw new ProgrammingError( - '%s is not a valid monitoring backend class name', + '%s is not a valid monitoring backend class name', $class ); } @@ -238,7 +239,7 @@ class MonitoringBackend implements Selectable, Queryable, ConnectionInterface /** * Backend entry point * - * @return self + * @return $this */ public function select() { @@ -251,7 +252,7 @@ class MonitoringBackend implements Selectable, Queryable, ConnectionInterface * @param string $name * @param array $columns * - * @return DataView + * @return \Icinga\Module\Monitoring\DataView\DataView */ public function from($name, array $columns = null) { @@ -262,21 +263,21 @@ class MonitoringBackend implements Selectable, Queryable, ConnectionInterface /** * View name to class name resolution * - * @param string $viewName + * @param string $view * * @return string - * @throws ProgrammingError When the view does not exist + * + * @throws ProgrammingError In case the view does not exist */ protected function buildViewClassName($view) { - $class = '\\Icinga\\Module\\Monitoring\\DataView\\' . ucfirst($view); - if (!class_exists($class)) { - throw new ProgrammingError( - 'DataView %s does not exist', - ucfirst($view) - ); + $class = ucfirst(strtolower($view)); + $classPath = '\\Icinga\\Module\\Monitoring\\DataView\\' . $class; + if (! class_exists($classPath)) { + throw new ProgrammingError('DataView %s does not exist', $class); } - return $class; + + return $classPath; } /** @@ -286,6 +287,8 @@ class MonitoringBackend implements Selectable, Queryable, ConnectionInterface * @param array $columns Optional column list * * @return Icinga\Data\QueryInterface + * + * @throws ProgrammingError When the query does not exist for this backend */ public function query($name, $columns = null) { @@ -317,16 +320,25 @@ class MonitoringBackend implements Selectable, Queryable, ConnectionInterface /** * Query name to class name resolution * - * @param string $query + * @param string $query * * @return string - * @throws ProgrammingError When the query does not exist for this backend */ protected function buildQueryClassName($query) { $parts = preg_split('~\\\~', get_class($this)); array_pop($parts); - array_push($parts, 'Query', ucfirst($query) . 'Query'); + array_push($parts, 'Query', ucfirst(strtolower($query)) . 'Query'); return implode('\\', $parts); } + + /** + * Fetch and return the program version of the current instance + * + * @return string + */ + public function getProgramVersion() + { + return $this->select()->from('programstatus', array('program_version'))->fetchOne(); + } } diff --git a/modules/monitoring/library/Monitoring/BackendStep.php b/modules/monitoring/library/Monitoring/BackendStep.php index c0dc3d4ea..a6d03b0e5 100644 --- a/modules/monitoring/library/Monitoring/BackendStep.php +++ b/modules/monitoring/library/Monitoring/BackendStep.php @@ -1,13 +1,12 @@ Config::fromArray($config), - 'filename' => Config::resolvePath('modules/monitoring/backends.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('modules/monitoring/backends.ini')) + ->saveIni(); } catch (Exception $e) { $this->backendIniError = $e; return false; @@ -61,13 +58,7 @@ class BackendStep extends Step try { $config = Config::app('resources', true); $config->setSection($resourceName, $resourceConfig); - - $writer = new IniWriter(array( - 'config' => $config, - 'filename' => Config::resolvePath('resources.ini'), - 'filemode' => 0660 - )); - $writer->write(); + $config->saveIni(); } catch (Exception $e) { $this->resourcesIniError = $e; return false; @@ -146,28 +137,35 @@ class BackendStep extends Step public function getReport() { - $report = ''; + $report = array(); + if ($this->backendIniError === false) { - $message = mt('monitoring', 'Monitoring backend configuration has been successfully written to: %s'); - $report .= '

    ' . sprintf($message, Config::resolvePath('modules/monitoring/backends.ini')) . '

    '; - } elseif ($this->backendIniError !== null) { - $message = mt( - 'monitoring', - 'Monitoring backend configuration could not be written to: %s; An error occured:' - ); - $report .= '

    ' . sprintf( - $message, + $report[] = sprintf( + mt('monitoring', 'Monitoring backend configuration has been successfully written to: %s'), Config::resolvePath('modules/monitoring/backends.ini') - ) . '

    ' . $this->backendIniError->getMessage() . '

    '; + ); + } elseif ($this->backendIniError !== null) { + $report[] = sprintf( + mt( + 'monitoring', + 'Monitoring backend configuration could not be written to: %s. An error occured:' + ), + Config::resolvePath('modules/monitoring/backends.ini') + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->backendIniError)); } if ($this->resourcesIniError === false) { - $message = mt('monitoring', 'Resource configuration has been successfully updated: %s'); - $report .= '

    ' . sprintf($message, Config::resolvePath('resources.ini')) . '

    '; + $report[] = sprintf( + mt('monitoring', 'Resource configuration has been successfully updated: %s'), + Config::resolvePath('resources.ini') + ); } elseif ($this->resourcesIniError !== null) { - $message = mt('monitoring', 'Resource configuration could not be udpated: %s; An error occured:'); - $report .= '

    ' . sprintf($message, Config::resolvePath('resources.ini')) . '

    ' - . '

    ' . $this->resourcesIniError->getMessage() . '

    '; + $report[] = sprintf( + mt('monitoring', 'Resource configuration could not be udpated: %s. An error occured:'), + Config::resolvePath('resources.ini') + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->resourcesIniError)); } return $report; diff --git a/modules/monitoring/library/Monitoring/Cli/CliUtils.php b/modules/monitoring/library/Monitoring/Cli/CliUtils.php index 5b443c714..955941a70 100644 --- a/modules/monitoring/library/Monitoring/Cli/CliUtils.php +++ b/modules/monitoring/library/Monitoring/Cli/CliUtils.php @@ -1,6 +1,5 @@ commentId; } + + /** + * Whether the command affects a service comment + * + * @return boolean + */ + public function getIsService() + { + return $this->isService; + } + + /** + * Set whether the command affects a service + * + * @param bool $isService + * + * @return $this + */ + public function setIsService($isService = true) + { + $this->isService = (bool) $isService; + return $this; + } } diff --git a/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php index 67e271dbb..2be50ed18 100644 --- a/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php +++ b/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php @@ -1,23 +1,15 @@ isService = (bool) $isService; + return $this; + } + + /** + * Return whether the command affects a service + * + * @return bool + */ + public function getIsService() + { + return $this->isService; + } + /** * Set the ID of the downtime that is to be deleted * diff --git a/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php index 1c8910066..0fc07e0d3 100644 --- a/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php +++ b/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php @@ -1,6 +1,5 @@ forced; + } + + /** + * Set whether to force the notification + * + * @param bool $forced + * + * @return $this + */ + public function setForced($forced = true) + { + $this->forced = $forced; + return $this; + } + + /** + * Get whether to broadcast the notification + * + * @return bool + */ + public function getBroadcast() + { + return $this->broadcast; + } + + /** + * Set whether to broadcast the notification + * + * @param bool $broadcast + * + * @return $this + */ + public function setBroadcast($broadcast = true) + { + $this->broadcast = $broadcast; + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php index c6cbeaa50..e6b29303e 100644 --- a/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php +++ b/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php @@ -1,6 +1,5 @@ $renderMethod($command)); + return sprintf('[%u] %s', $now, $this->escape($this->$renderMethod($command))); } public function renderAddComment(AddCommentCommand $command) @@ -126,7 +127,7 @@ class IcingaCommandFileCommandRenderer implements IcingaCommandRendererInterface } else { /** @var \Icinga\Module\Monitoring\Object\Service $object */ $commandString = sprintf( - 'PROCESS_SVC_CHECK_RESULT;%s;%s', + 'PROCESS_SERVICE_CHECK_RESULT;%s;%s', $object->getHost()->getName(), $object->getName() ); @@ -322,28 +323,18 @@ class IcingaCommandFileCommandRenderer implements IcingaCommandRendererInterface public function renderDeleteComment(DeleteCommentCommand $command) { - if ($command->getObject()->getType() === $command::TYPE_HOST) { - $commandString = 'DEL_HOST_COMMENT'; - } else { - $commandString = 'DEL_SVC_COMMENT'; - } return sprintf( '%s;%u', - $commandString, + $command->getIsService() ? 'DEL_SVC_COMMENT' : 'DEL_HOST_COMMENT', $command->getCommentId() ); } public function renderDeleteDowntime(DeleteDowntimeCommand $command) { - if ($command->getObject()->getType() === $command::TYPE_HOST) { - $commandString = 'DEL_HOST_DOWNTIME'; - } else { - $commandString = 'DEL_SVC_DOWNTIME'; - } return sprintf( '%s;%u', - $commandString, + $command->getIsService() ? 'DEL_SVC_DOWNTIME' : 'DEL_HOST_DOWNTIME', $command->getDowntimeId() ); } diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php index f901e3850..552b49a63 100644 --- a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php +++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php @@ -1,4 +1,5 @@ transport)) { case RemoteCommandFile::TRANSPORT: $transport = new RemoteCommandFile(); @@ -74,7 +74,10 @@ abstract class CommandTransport foreach ($config as $key => $value) { $method = 'set' . ucfirst($key); if (! method_exists($transport, $method)) { - throw new ConfigurationError(); + // Ignore settings from config that don't have a setter on the transport instead of throwing an + // exception here because the transport should throw an exception if it's not fully set up + // when being about to send a command + continue; } $transport->$method($value); } @@ -105,7 +108,8 @@ abstract class CommandTransport */ public static function first() { - $config = self::getConfig()->current(); - return self::fromConfig($config); + $config = self::getConfig(); + $config->rewind(); + return self::fromConfig($config->current()); } } diff --git a/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php index 21b532d49..bc496a00b 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php @@ -1,12 +1,22 @@ path)) { - throw new LogicException('Can\'t send external Icinga Command. Path to the local command file is missing'); + throw new ConfigurationError( + 'Can\'t send external Icinga Command. Path to the local command file is missing' + ); } $commandString = $this->renderer->render($command, $now); Logger::debug( @@ -120,13 +122,16 @@ class LocalCommandFile implements CommandTransportInterface try { $file = new File($this->path, $this->openMode); $file->fwrite($commandString . "\n"); - $file->fflush(); } catch (Exception $e) { - throw new TransportException( - 'Can\'t send external Icinga command "%s" to the local command file "%s": %s', - $commandString, + $message = $e->getMessage(); + if ($e instanceof RuntimeException && ($pos = strrpos($message, ':')) !== false) { + // Assume RuntimeException thrown by SplFileObject in the format: __METHOD__ . "({$filename}): Message" + $message = substr($message, $pos + 1); + } + throw new CommandTransportException( + 'Can\'t send external Icinga command to the local command file "%s": %s', $this->path, - $e + $message ); } } diff --git a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php index 0e447a4f3..383970b76 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php @@ -1,14 +1,14 @@ user; } + /** + * Set the path to the private key file + * + * @param string $privateKey + * + * @return $this + */ + public function setPrivateKey($privateKey) + { + $this->privateKey = (string) $privateKey; + return $this; + } + + /** + * Get the path to the private key + * + * @return string + */ + public function getPrivateKey() + { + return $this->privateKey; + } + + /** + * Use a given resource to set the user and the key + * + * @param string + * + * @throws ConfigurationError + */ + public function setResource($resource = null) + { + $config = ResourceFactory::getResourceConfig($resource); + + if (! isset($config->user)) { + throw new ConfigurationError( + t("Can't send external Icinga Command. Remote user is missing") + ); + } + if (! isset($config->private_key)) { + throw new ConfigurationError( + t("Can't send external Icinga Command. The private key for the remote user is missing") + ); + } + + $this->setUser($config->user); + $this->setPrivateKey($config->private_key); + } + /** * Set the path to the Icinga command file on the remote host * * @param string $path * - * @return self + * @return $this */ public function setPath($path) { @@ -167,16 +223,18 @@ class RemoteCommandFile implements CommandTransportInterface * @param IcingaCommand $command * @param int|null $now * - * @throws LogicException - * @throws TransportException + * @throws ConfigurationError + * @throws CommandTransportException */ public function send(IcingaCommand $command, $now = null) { if (! isset($this->path)) { - throw new LogicException('Can\'t send external Icinga Command. Path to the remote command file is missing'); + throw new ConfigurationError( + 'Can\'t send external Icinga Command. Path to the remote command file is missing' + ); } if (! isset($this->host)) { - throw new LogicException('Can\'t send external Icinga Command. Remote host is missing'); + throw new ConfigurationError('Can\'t send external Icinga Command. Remote host is missing'); } $commandString = $this->renderer->render($command, $now); Logger::debug( @@ -191,6 +249,9 @@ class RemoteCommandFile implements CommandTransportInterface if (isset($this->user)) { $ssh .= sprintf(' -l %s', escapeshellarg($this->user)); } + if (isset($this->privateKey)) { + $ssh .= sprintf(' -o StrictHostKeyChecking=no -i %s', escapeshellarg($this->privateKey)); + } $ssh .= sprintf( ' %s "echo %s > %s" 2>&1', // Redirect stderr to stdout escapeshellarg($this->host), @@ -199,9 +260,8 @@ class RemoteCommandFile implements CommandTransportInterface ); exec($ssh, $output, $status); if ($status !== 0) { - throw new TransportException( - 'Can\'t send external Icinga command "%s": %s', - $ssh, + throw new CommandTransportException( + 'Can\'t send external Icinga command: %s', implode(' ', $output) ); } diff --git a/modules/monitoring/library/Monitoring/Controller.php b/modules/monitoring/library/Monitoring/Controller.php index 32bafb8b2..9d9e486cd 100644 --- a/modules/monitoring/library/Monitoring/Controller.php +++ b/modules/monitoring/library/Monitoring/Controller.php @@ -1,17 +1,20 @@ backend = Backend::createBackend($this->_getParam('backend')); @@ -38,10 +31,6 @@ class Controller extends ModuleActionController protected function handleFormatRequest($query) { - if ($this->compactView !== null && ($this->_getParam('view', false) === 'compact')) { - $this->_helper->viewRenderer($this->compactView); - } - if ($this->_getParam('format') === 'sql') { echo '
    '
                     . htmlspecialchars(wordwrap($query->dump()))
    @@ -60,5 +49,55 @@ class Controller extends ModuleActionController
                 exit;
             }
         }
    +
    +    /**
    +     * Apply a restriction on the given data view
    +     *
    +     * @param   string      $restriction    The name of restriction
    +     * @param   Filterable  $view           The filterable to restrict
    +     *
    +     * @return  Filterable  The filterable
    +     */
    +    protected function applyRestriction($restriction, Filterable $view)
    +    {
    +        $restrictions = Filter::matchAny();
    +        $restrictions->setAllowedFilterColumns(array(
    +            'host_name',
    +            'hostgroup_name',
    +            'service_description',
    +            'servicegroup_name',
    +            function ($c) {
    +                return preg_match('/^_(?:host|service)_/', $c);
    +            }
    +        ));
    +
    +        foreach ($this->getRestrictions($restriction) as $filter) {
    +            if ($filter === '*') {
    +                return $view;
    +            }
    +            try {
    +                $restrictions->addFilter(Filter::fromQueryString($filter));
    +            } catch (QueryException $e) {
    +                throw new ConfigurationError(
    +                    $this->translate(
    +                        'Cannot apply restriction %s using the filter %s. You can only use the following columns: %s'
    +                    ),
    +                    $restriction,
    +                    $filter,
    +                    implode(', ', array(
    +                        'host_name',
    +                        'hostgroup_name',
    +                        'service_description',
    +                        'servicegroup_name',
    +                        '_(host|service)_'
    +                    )),
    +                    $e
    +                );
    +            }
    +        }
    +
    +        $view->applyFilter($restrictions);
    +        return $view;
    +    }
     }
     
    diff --git a/modules/monitoring/library/Monitoring/DataView/Command.php b/modules/monitoring/library/Monitoring/DataView/Command.php
    index a21c48ee0..06e233b75 100644
    --- a/modules/monitoring/library/Monitoring/DataView/Command.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Command.php
    @@ -1,6 +1,5 @@
      array(
                     'order' => self::SORT_DESC
                 ),
    -            'comment_host' => array(
    +            'host_display_name' => array(
                     'columns' => array(
    -                    'comment_host',
    -                    'comment_service'
    +                    'host_display_name',
    +                    'service_display_name'
                     ),
                     'order' => self::SORT_ASC
                 ),
    +            'service_display_name' => array(
    +                'columns' => array(
    +                    'service_display_name',
    +                    'host_display_name'
    +                ),
    +                'order' => self::SORT_ASC
    +            )
             );
         }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/Contact.php b/modules/monitoring/library/Monitoring/DataView/Contact.php
    index 27639b0c6..d64a80e86 100644
    --- a/modules/monitoring/library/Monitoring/DataView/Contact.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Contact.php
    @@ -1,30 +1,34 @@
      array(
    -                'order' => self::SORT_DESC
    +            'contact_name' => array(
    +                'order' => self::SORT_ASC
                 )
             );
         }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getFilterColumns()
    +    {
    +        return array(
    +            'contact',
    +            'host', 'host_name', 'host_display_name', 'host_alias',
    +            'hostgroup', 'hostgroup_alias', 'hostgroup_name',
    +            'service', 'service_description', 'service_display_name',
    +            'servicegroup', 'servicegroup_alias', 'servicegroup_name'
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSearchColumns()
    +    {
    +        return array('contact_alias');
    +    }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/Contactgroup.php b/modules/monitoring/library/Monitoring/DataView/Contactgroup.php
    index 7dd7a1cd4..8560b55fb 100644
    --- a/modules/monitoring/library/Monitoring/DataView/Contactgroup.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Contactgroup.php
    @@ -1,37 +1,57 @@
      array(
    -                'varname'  => self::SORT_ASC,
    -                'varvalue' => self::SORT_ASC,
    +                'columns' => array(
    +                    'varname',
    +                    'varvalue'
    +                )
                 )
             );
         }
    +
    +    public function getFilterColumns()
    +    {
    +        return array('host', 'service', 'contact');
    +    }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php
    index 1fdd8b4f9..31ca426d5 100644
    --- a/modules/monitoring/library/Monitoring/DataView/DataView.php
    +++ b/modules/monitoring/library/Monitoring/DataView/DataView.php
    @@ -1,31 +1,30 @@
     getQuery();
    +    }
    +
    +    /**
    +     * Return the current position of the result set's iterator
    +     *
    +     * @return  int
    +     */
    +    public function getIteratorPosition()
    +    {
    +        return $this->query->getIteratorPosition();
    +    }
    +
         /**
          * Get the query name this data view relies on
          *
    @@ -83,6 +102,7 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
     
         public function dump()
         {
    +        $this->order();
             return $this->query->dump();
         }
     
    @@ -192,12 +212,15 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
          *
          * @param   string  $xAxisColumn    The column to use for the x axis
          * @param   string  $yAxisColumn    The column to use for the y axis
    +     * @param   Filter  $xAxisFilter    The filter to apply on a query for the x axis
    +     * @param   Filter  $yAxisFilter    The filter to apply on a query for the y axis
          *
          * @return  PivotTable
          */
    -    public function pivot($xAxisColumn, $yAxisColumn)
    +    public function pivot($xAxisColumn, $yAxisColumn, Filter $xAxisFilter = null, Filter $yAxisFilter = null)
         {
    -        return new PivotTable($this->query, $xAxisColumn, $yAxisColumn);
    +        $pivot = new PivotTable($this->query, $xAxisColumn, $yAxisColumn);
    +        return $pivot->setXAxisFilter($xAxisFilter)->setYAxisFilter($yAxisFilter);
         }
     
         /**
    @@ -242,6 +265,7 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
             $order = (strtoupper($order) === static::SORT_ASC) ? 'ASC' : 'DESC';
     
             foreach ($sortColumns['columns'] as $column) {
    +            list($column, $direction) = $this->query->splitOrder($column);
                 if (! $this->isValidFilterTarget($column)) {
                     throw new QueryException(
                         mt('monitoring', 'The sort column "%s" is not allowed in "%s".'),
    @@ -249,7 +273,7 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
                         get_class($this)
                     );
                 }
    -            $this->query->order($column, $order);
    +            $this->query->order($column, $direction !== null ? $direction : $order);
             }
             $this->isSorted = true;
             return $this;
    @@ -354,12 +378,31 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
             return $this;
         }
     
    +    /**
    +     * @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing
    +     * column validation. Filter::matchAny() for the IdoQuery (or the DbQuery or the SimpleQuery I didn't have a look)
    +     * is required for the filter to work properly.
    +     */
         public function setFilter(Filter $filter)
         {
             $this->query->setFilter($filter);
             return $this;
         }
     
    +    /**
    +     * Get the view's search columns
    +     *
    +     * @return string[]
    +     */
    +    public function getSearchColumns()
    +    {
    +        return array();
    +    }
    +
    +    /**
    +     * @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing
    +     * column validation.
    +     */
         public function addFilter(Filter $filter)
         {
             $this->query->addFilter(clone($filter));
    @@ -367,22 +410,6 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
             return $this;
         }
     
    -    /**
    -     * Paginate data
    -     *
    -     * @param   int $itemsPerPage   Number of items per page
    -     * @param   int $pageNumber     Current page number
    -     *
    -     * @return  Zend_Paginator
    -     */
    -    public function paginate($itemsPerPage = null, $pageNumber = null)
    -    {
    -        if (! $this->isSorted) {
    -            $this->order();
    -        }
    -        return $this->query->paginate($itemsPerPage, $pageNumber);
    -    }
    -
         /**
          * Count result set
          *
    @@ -390,6 +417,145 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
          */
         public function count()
         {
    -        return count($this->query);
    +        return $this->query->count();
    +    }
    +
    +    /**
    +     * Set whether the query should peek ahead for more results
    +     *
    +     * Enabling this causes the current query limit to be increased by one. The potential extra row being yielded will
    +     * be removed from the result set. Note that this only applies when fetching multiple results of limited queries.
    +     *
    +     * @return  $this
    +     */
    +    public function peekAhead($state = true)
    +    {
    +        return $this->query->peekAhead($state);
    +    }
    +
    +    /**
    +     * Return whether the query did not yield all available results
    +     *
    +     * @return  bool
    +     */
    +    public function hasMore()
    +    {
    +        return $this->query->hasMore();
    +    }
    +
    +    /**
    +     * Return whether this query will or has yielded any result
    +     *
    +     * @return  bool
    +     */
    +    public function hasResult()
    +    {
    +        return $this->query->hasResult();
    +    }
    +
    +    /**
    +     * Set a limit count and offset
    +     *
    +     * @param   int $count  Number of rows to return
    +     * @param   int $offset Start returning after this many rows
    +     *
    +     * @return  self
    +     */
    +    public function limit($count = null, $offset = null)
    +    {
    +        $this->query->limit($count, $offset);
    +        return $this;
    +    }
    +
    +    /**
    +     * Whether a limit is set
    +     *
    +     * @return bool
    +     */
    +    public function hasLimit()
    +    {
    +        return $this->query->hasLimit();
    +    }
    +
    +    /**
    +     * Get the limit if any
    +     *
    +     * @return int|null
    +     */
    +    public function getLimit()
    +    {
    +        return $this->query->getLimit();
    +    }
    +
    +    /**
    +     * Whether an offset is set
    +     *
    +     * @return bool
    +     */
    +    public function hasOffset()
    +    {
    +        return $this->query->hasOffset();
    +    }
    +
    +    /**
    +     * Get the offset if any
    +     *
    +     * @return int|null
    +     */
    +    public function getOffset()
    +    {
    +        return $this->query->getOffset();
    +    }
    +
    +    /**
    +     * Retrieve an array containing all rows of the result set
    +     *
    +     * @return  array
    +     */
    +    public function fetchAll()
    +    {
    +        return $this->getQuery()->fetchAll();
    +    }
    +
    +    /**
    +     * Fetch the first row of the result set
    +     *
    +     * @return  mixed
    +     */
    +    public function fetchRow()
    +    {
    +        return $this->getQuery()->fetchRow();
    +    }
    +
    +    /**
    +     * Fetch the first column of all rows of the result set as an array
    +     *
    +     * @return  array
    +     */
    +    public function fetchColumn()
    +    {
    +        return $this->getQuery()->fetchColumn();
    +    }
    +
    +    /**
    +     * Fetch the first column of the first row of the result set
    +     *
    +     * @return  string
    +     */
    +    public function fetchOne()
    +    {
    +        return $this->getQuery()->fetchOne();
    +    }
    +
    +    /**
    +     * Fetch all rows of the result set as an array of key-value pairs
    +     *
    +     * The first column is the key, the second column is the value.
    +     *
    +     * @return  array
    +     */
    +    public function fetchPairs()
    +    {
    +        return $this->getQuery()->fetchPairs();
         }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/Downtime.php b/modules/monitoring/library/Monitoring/DataView/Downtime.php
    index e075d658c..767fc577d 100644
    --- a/modules/monitoring/library/Monitoring/DataView/Downtime.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Downtime.php
    @@ -1,57 +1,107 @@
      array(
    +                'columns' => array(
    +                    'downtime_is_in_effect',
    +                    'downtime_scheduled_start'
    +                ),
                     'order' => self::SORT_DESC
                 ),
                 'downtime_start' => array(
                     'order' => self::SORT_DESC
                 ),
    -            'downtime_host' => array(
    +            'host_display_name' => array(
                     'columns' => array(
    -                    'downtime_host',
    -                    'downtime_service'
    +                    'host_display_name',
    +                    'service_display_name'
                     ),
                     'order' => self::SORT_ASC
                 ),
    +            'service_display_name' => array(
    +                'columns' => array(
    +                    'service_display_name',
    +                    'host_display_name'
    +                ),
    +                'order' => self::SORT_ASC
    +            )
             );
         }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/EventHistory.php b/modules/monitoring/library/Monitoring/DataView/EventHistory.php
    deleted file mode 100644
    index 09779b27d..000000000
    --- a/modules/monitoring/library/Monitoring/DataView/EventHistory.php
    +++ /dev/null
    @@ -1,53 +0,0 @@
    - array(
    -                'columns' => array('timestamp'),
    -                'order' => 'DESC'
    -            )
    -        );
    -    }
    -
    -    public function getFilterColumns()
    -    {
    -        return array(
    -            'hostgroups'
    -        );
    -    }
    -}
    diff --git a/modules/monitoring/library/Monitoring/DataView/Eventgrid.php b/modules/monitoring/library/Monitoring/DataView/Eventgrid.php
    index 726f1ae60..0dafe7405 100644
    --- a/modules/monitoring/library/Monitoring/DataView/Eventgrid.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Eventgrid.php
    @@ -1,11 +1,22 @@
      array(
    +                'order' => self::SORT_DESC
    +            )
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getFilterColumns()
    +    {
    +        return array(
    +            'host', 'host_alias',
    +            'hostgroup', 'hostgroup_alias', 'hostgroup_name',
    +            'service',
    +            'servicegroup', 'servicegroup_alias', 'servicegroup_name'
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSearchColumns()
    +    {
    +        return array('host_display_name', 'service_display_name');
    +    }
    +}
    diff --git a/modules/monitoring/library/Monitoring/DataView/Groupsummary.php b/modules/monitoring/library/Monitoring/DataView/Groupsummary.php
    deleted file mode 100644
    index 7fe6d2e6d..000000000
    --- a/modules/monitoring/library/Monitoring/DataView/Groupsummary.php
    +++ /dev/null
    @@ -1,60 +0,0 @@
    - array(
    -                'columns'   => array('services_severity'),
    -                'order'     => self::SORT_DESC
    -            )
    -        );
    -    }
    -}
    diff --git a/modules/monitoring/library/Monitoring/DataView/Hostcomment.php b/modules/monitoring/library/Monitoring/DataView/Hostcomment.php
    new file mode 100644
    index 000000000..d1afbdd74
    --- /dev/null
    +++ b/modules/monitoring/library/Monitoring/DataView/Hostcomment.php
    @@ -0,0 +1,56 @@
    + array(
    -                'order' => self::SORT_ASC
    -            )
    +            'host', 'host_alias', 'host_display_name', 'host_name',
    +            'hostgroup',
    +            'service', 'service_description', 'service_display_name',
    +            'servicegroup', 'servicegroup_alias', 'servicegroup_name'
             );
         }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isValidFilterTarget($column)
    +    {
    +        if ($column[0] === '_'
    +            && preg_match('/^_(?:host|service)_/', $column)
    +        ) {
    +            return true;
    +        } else {
    +            return parent::isValidFilterTarget($column);
    +        }
    +    }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php b/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php
    new file mode 100644
    index 000000000..7f10d072e
    --- /dev/null
    +++ b/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php
    @@ -0,0 +1,112 @@
    + array(
    +                'order' => self::SORT_ASC
    +            ),
    +            'hosts_severity' => array(
    +                'columns' => array(
    +                    'hosts_severity',
    +                    'hostgroup_alias ASC'
    +                ),
    +                'order' => self::SORT_DESC
    +            ),
    +            'hosts_total' => array(
    +                'columns' => array(
    +                    'hosts_total',
    +                    'hostgroup_alias ASC'
    +                ),
    +                'order' => self::SORT_ASC
    +            ),
    +            'services_total' => array(
    +                'columns' => array(
    +                    'services_total',
    +                    'hostgroup_alias ASC'
    +                ),
    +                'order' => self::SORT_ASC
    +            )
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isValidFilterTarget($column)
    +    {
    +        if ($column[0] === '_'
    +            && preg_match('/^_(?:host|service)_/', $column)
    +        ) {
    +            return true;
    +        } else {
    +            return parent::isValidFilterTarget($column);
    +        }
    +    }
    +}
    diff --git a/modules/monitoring/library/Monitoring/DataView/HostStatus.php b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
    similarity index 81%
    rename from modules/monitoring/library/Monitoring/DataView/HostStatus.php
    rename to modules/monitoring/library/Monitoring/DataView/Hoststatus.php
    index e9b5b60dd..c9f98ec43 100644
    --- a/modules/monitoring/library/Monitoring/DataView/HostStatus.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
    @@ -1,29 +1,18 @@
     query->setMode('host');
    -    }
    -
    -    /**
    -     * Retrieve columns provided by this view
    -     *
    -     * @return array
    +     * {@inheritdoc}
          */
         public function getColumns()
         {
             return array(
    -            'host',
                 'host_name',
    +            'host_display_name',
                 'host_alias',
                 'host_address',
                 'host_state',
    @@ -44,7 +33,6 @@ class HostStatus extends DataView
                 'host_check_command',
                 'host_perfdata',
                 'host_check_source',
    -            'host_unhandled_services',
                 'host_passive_checks_enabled',
                 'host_passive_checks_enabled_changed',
                 'host_obsessing',
    @@ -63,10 +51,6 @@ class HostStatus extends DataView
                 'host_current_notification_number',
                 'host_percent_state_change',
                 'host_is_flapping',
    -            'host_last_comment',
    -            'host_last_downtime',
    -            'host_last_ack',
    -            'host_last_flapping',
                 'host_action_url',
                 'host_notes_url',
                 'host_percent_state_change',
    @@ -77,9 +61,25 @@ class HostStatus extends DataView
             );
         }
     
    -    public static function getQueryName()
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getFilterColumns()
         {
    -        return 'status';
    +        return array(
    +            'host',
    +            'hostgroup', 'hostgroup_alias', 'hostgroup_name',
    +            'service', 'service_description', 'service_display_name',
    +            'servicegroup', 'servicegroup_alias', 'servicegroup_name'
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSearchColumns()
    +    {
    +        return array('host_display_name');
         }
     
         /**
    @@ -90,9 +90,17 @@ class HostStatus extends DataView
         public function getSortRules()
         {
             return array(
    -            'host_name' => array(
    +            'host_display_name' => array(
                     'order' => self::SORT_ASC
                 ),
    +            'host_severity' => array(
    +                'columns' => array(
    +                    'host_severity',
    +                    'host_last_state_change DESC',
    +                    'host_display_name ASC'
    +                ),
    +                'order' => self::SORT_DESC
    +            ),
                 'host_address' => array(
                     'columns' => array(
                         'host_ipv4'
    @@ -101,22 +109,13 @@ class HostStatus extends DataView
                 ),
                 'host_last_state_change' => array(
                     'order' => self::SORT_ASC
    -            ),
    -            'host_severity' => array(
    -                'columns' => array(
    -                    'host_severity',
    -                    'host_last_state_change',
    -                ),
    -                'order' => self::SORT_DESC
                 )
             );
         }
     
    -    public function getFilterColumns()
    -    {
    -        return array('hostgroup', 'service_problems', 'servicegroup');
    -    }
    -
    +    /**
    +     * {@inheritdoc}
    +     */
         public function isValidFilterTarget($column)
         {
             if ($column[0] === '_'
    diff --git a/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php b/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php
    new file mode 100644
    index 000000000..292698376
    --- /dev/null
    +++ b/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php
    @@ -0,0 +1,53 @@
    +getFilterColumns());
    +        }
    +    }
    +}
    diff --git a/modules/monitoring/library/Monitoring/DataView/Notification.php b/modules/monitoring/library/Monitoring/DataView/Notification.php
    index 6156a5f15..5c2500843 100644
    --- a/modules/monitoring/library/Monitoring/DataView/Notification.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Notification.php
    @@ -1,11 +1,23 @@
      array(
                     'order' => self::SORT_DESC,
                     'title' => 'Notification Start'
    +            ),
    +            'host_display_name' => array(
    +                'columns' => array(
    +                    'host_display_name',
    +                    'service_display_name'
    +                ),
    +                'order' => self::SORT_ASC
    +            ),
    +            'service_display_name' => array(
    +                'columns' => array(
    +                    'service_display_name',
    +                    'host_display_name'
    +                ),
    +                'order' => self::SORT_ASC
                 )
             );
         }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getFilterColumns()
    +    {
    +        return array(
    +            'contact',
    +            'host', 'host_alias',
    +            'hostgroup', 'hostgroup_alias', 'hostgroup_name',
    +            'service',
    +            'servicegroup', 'servicegroup_alias', 'servicegroup_name'
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSearchColumns()
    +    {
    +        return array('host_display_name', 'service_display_name');
    +    }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/Programstatus.php b/modules/monitoring/library/Monitoring/DataView/Programstatus.php
    index f42858bf8..88bf0800b 100644
    --- a/modules/monitoring/library/Monitoring/DataView/Programstatus.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Programstatus.php
    @@ -1,6 +1,5 @@
      array(
    -                'order' => self::SORT_ASC
    -            )
    +            'host', 'host_alias', 'host_display_name', 'host_name',
    +            'hostgroup', 'hostgroup_alias', 'hostgroup_name',
    +            'service', 'service_description', 'service_display_name',
    +            'servicegroup'
             );
         }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isValidFilterTarget($column)
    +    {
    +        if ($column[0] === '_'
    +            && preg_match('/^_(?:host|service)_/', $column)
    +        ) {
    +            return true;
    +        } else {
    +            return parent::isValidFilterTarget($column);
    +        }
    +    }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php b/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php
    new file mode 100644
    index 000000000..205aa56de
    --- /dev/null
    +++ b/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php
    @@ -0,0 +1,100 @@
    + array(
    +                'order' => self::SORT_ASC
    +            ),
    +            'services_severity' => array(
    +                'columns' => array(
    +                    'services_severity',
    +                    'servicegroup_alias ASC'
    +                ),
    +                'order' => self::SORT_DESC
    +            ),
    +            'services_total' => array(
    +                'columns' => array(
    +                    'services_total',
    +                    'servicegroup_alias ASC'
    +                ),
    +                'order' => self::SORT_ASC
    +            )
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isValidFilterTarget($column)
    +    {
    +        if ($column[0] === '_'
    +            && preg_match('/^_(?:host|service)_/', $column)
    +        ) {
    +            return true;
    +        } else {
    +            return parent::isValidFilterTarget($column);
    +        }
    +    }
    +}
    diff --git a/modules/monitoring/library/Monitoring/DataView/ServiceStatus.php b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
    similarity index 79%
    rename from modules/monitoring/library/Monitoring/DataView/ServiceStatus.php
    rename to modules/monitoring/library/Monitoring/DataView/Servicestatus.php
    index 9edcabc87..00997bced 100644
    --- a/modules/monitoring/library/Monitoring/DataView/ServiceStatus.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
    @@ -1,6 +1,5 @@
      array(
    +            'service_display_name' => array(
                     'columns' => array(
    -                    'service_host_name',
    -                    'service_description'
    -                ),
    -                'order' => self::SORT_ASC
    -            ),
    -            'host_address' => array(
    -                'columns' => array(
    -                    'host_ipv4',
    -                    'service_description'
    -                ),
    -                'order' => self::SORT_ASC
    -            ),
    -            'host_last_state_change' => array(
    -                'order' => self::SORT_ASC
    -            ),
    -            'host_severity' => array(
    -                'columns' => array(
    -                    'host_severity',
    -                    'host_last_state_change',
    +                    'service_display_name',
    +                    'host_display_name'
                     ),
                     'order' => self::SORT_ASC
                 ),
                 'service_severity' => array(
                     'columns' => array(
                         'service_severity',
    -                    'service_last_state_change',
    +                    'service_last_state_change DESC',
    +                    'service_display_name ASC',
    +                    'host_display_name ASC'
                     ),
                     'order' => self::SORT_DESC
    +            ),
    +            'host_severity' => array(
    +                'columns' => array(
    +                    'host_severity',
    +                    'host_last_state_change DESC',
    +                    'host_display_name ASC',
    +                    'service_display_name ASC'
    +                ),
    +                'order' => self::SORT_DESC
    +            ),
    +            'host_display_name' => array(
    +                'columns' => array(
    +                    'host_display_name',
    +                    'service_display_name'
    +                ),
    +                'order' => self::SORT_ASC
    +            ),
    +            'host_address' => array(
    +                'columns' => array(
    +                    'host_ipv4',
    +                    'service_display_name'
    +                ),
    +                'order' => self::SORT_ASC
                 )
             );
         }
     
         public function getFilterColumns()
         {
    -        return array('hostgroup', 'servicegroup', 'service_problems');
    +        return array(
    +            'host',
    +            'hostgroup',
    +            'hostgroup_alias',
    +            'hostgroup_name',
    +            'service',
    +            'service_host',
    +            'servicegroup',
    +            'servicegroup_alias',
    +            'servicegroup_name'
    +        );
         }
     
         public function isValidFilterTarget($column)
    @@ -171,4 +180,12 @@ class ServiceStatus extends DataView
             }
             return parent::isValidFilterTarget($column);
         }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSearchColumns()
    +    {
    +        return array('host_display_name', 'service_display_name');
    +    }
     }
    diff --git a/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php b/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php
    new file mode 100644
    index 000000000..5cb6b137f
    --- /dev/null
    +++ b/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php
    @@ -0,0 +1,58 @@
    +getFilterColumns());
    +        }
    +    }
    +}
    diff --git a/modules/monitoring/library/Monitoring/DataView/StatusSummary.php b/modules/monitoring/library/Monitoring/DataView/Statussummary.php
    similarity index 83%
    rename from modules/monitoring/library/Monitoring/DataView/StatusSummary.php
    rename to modules/monitoring/library/Monitoring/DataView/Statussummary.php
    index 74574bc72..e725f76d1 100644
    --- a/modules/monitoring/library/Monitoring/DataView/StatusSummary.php
    +++ b/modules/monitoring/library/Monitoring/DataView/Statussummary.php
    @@ -1,6 +1,5 @@
     getFilterColumns());
    +        }
    +    }
     }
    diff --git a/modules/monitoring/library/Monitoring/Environment.php b/modules/monitoring/library/Monitoring/Environment.php
    index f9ad28654..3d9b7553d 100644
    --- a/modules/monitoring/library/Monitoring/Environment.php
    +++ b/modules/monitoring/library/Monitoring/Environment.php
    @@ -1,6 +1,5 @@
      Config::fromArray(array($instanceName => $instanceConfig)),
    -                'filename'  => Config::resolvePath('modules/monitoring/instances.ini')
    -            ));
    -            $writer->write();
    +            Config::fromArray(array($instanceName => $instanceConfig))
    +                ->setConfigFile(Config::resolvePath('modules/monitoring/instances.ini'))
    +                ->saveIni();
             } catch (Exception $e) {
                 $this->error = $e;
                 return false;
    @@ -89,15 +86,21 @@ class InstanceStep extends Step
         public function getReport()
         {
             if ($this->error === false) {
    -            $message = mt('monitoring', 'Monitoring instance configuration has been successfully created: %s');
    -            return '

    ' . sprintf($message, Config::resolvePath('modules/monitoring/instances.ini')) . '

    '; + return array(sprintf( + mt('monitoring', 'Monitoring instance configuration has been successfully created: %s'), + Config::resolvePath('modules/monitoring/instances.ini') + )); } elseif ($this->error !== null) { - $message = mt( - 'monitoring', - 'Monitoring instance configuration could not be written to: %s; An error occured:' + return array( + sprintf( + mt( + 'monitoring', + 'Monitoring instance configuration could not be written to: %s. An error occured:' + ), + Config::resolvePath('modules/monitoring/instances.ini') + ), + sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error)) ); - return '

    ' . sprintf($message, Config::resolvePath('modules/monitoring/instances.ini')) - . '

    ' . $this->error->getMessage() . '

    '; } } } diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php index 0ac4c7f63..0a224610c 100644 --- a/modules/monitoring/library/Monitoring/MonitoringWizard.php +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -1,6 +1,5 @@ addPage(new LivestatusResourcePage()); $this->addPage(new InstancePage()); $this->addPage(new SecurityPage()); - $this->addPage(new SummaryPage()); + $this->addPage(new SummaryPage(array('name' => 'setup_monitoring_summary'))); } /** - * @see Wizard::setupPage() + * Setup the given page that is either going to be displayed or validated + * + * @param Form $page The page to setup + * @param Request $request The current request */ public function setupPage(Form $page, Request $request) { if ($page->getName() === 'setup_requirements') { $page->setRequirements($this->getRequirements()); - } elseif ($page->getName() === 'setup_summary') { + } elseif ($page->getName() === 'setup_monitoring_summary') { $page->setSummary($this->getSetup()->getSummary()); $page->setSubjectTitle(mt('monitoring', 'the monitoring module', 'setup.summary.subject')); } elseif ( $this->getDirection() === static::FORWARD && ($page->getName() === 'setup_monitoring_ido' || $page->getName() === 'setup_monitoring_livestatus') ) { - if ((($dbResourceData = $this->getPageData('setup_db_resource')) !== null - && $dbResourceData['name'] === $request->getPost('name')) - || (($ldapResourceData = $this->getPageData('setup_ldap_resource')) !== null - && $ldapResourceData['name'] === $request->getPost('name')) + if ( + (($authDbResourceData = $this->getPageData('setup_auth_db_resource')) !== null + && $authDbResourceData['name'] === $request->getPost('name')) + || (($configDbResourceData = $this->getPageData('setup_config_db_resource')) !== null + && $configDbResourceData['name'] === $request->getPost('name')) + || (($ldapResourceData = $this->getPageData('setup_ldap_resource')) !== null + && $ldapResourceData['name'] === $request->getPost('name')) ) { - $page->addError(mt('monitoring', 'The given resource name is already in use.')); + $page->error(mt('monitoring', 'The given resource name is already in use.')); } } } /** - * @see Wizard::getNewPage() + * Return the new page to set as current page + * + * {@inheritdoc} Runs additional checks related to some registered pages. + * + * @param string $requestedPage The name of the requested page + * @param Form $originPage The origin page + * + * @return Form The new page + * + * @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet */ protected function getNewPage($requestedPage, Form $originPage) { @@ -78,27 +92,13 @@ class MonitoringWizard extends Wizard implements SetupWizard $skip = $backendData['type'] !== 'livestatus'; } - if ($skip) { - if ($this->hasPageData($newPage->getName())) { - $pageData = & $this->getPageData(); - unset($pageData[$newPage->getName()]); - } - - $pages = $this->getPages(); - if ($this->getDirection() === static::FORWARD) { - $nextPage = $pages[array_search($newPage, $pages, true) + 1]; - $newPage = $this->getNewPage($nextPage->getName(), $newPage); - } else { // $this->getDirection() === static::BACKWARD - $previousPage = $pages[array_search($newPage, $pages, true) - 1]; - $newPage = $this->getNewPage($previousPage->getName(), $newPage); - } - } - - return $newPage; + return $skip ? $this->skipPage($newPage) : $newPage; } /** - * @see Wizard::addButtons() + * Add buttons to the given page based on its position in the page-chain + * + * @param Form $page The page to add the buttons to */ protected function addButtons(Form $page) { @@ -114,18 +114,31 @@ class MonitoringWizard extends Wizard implements SetupWizard mt('monitoring', 'Setup the monitoring module for Icinga Web 2', 'setup.summary.btn.finish') ); } + + if ($page->getName() === 'setup_monitoring_ido') { + $page->addElement( + 'submit', + 'backend_validation', + array( + 'ignore' => true, + 'label' => t('Validate Configuration'), + 'decorators' => array('ViewHelper') + ) + ); + $page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation')); + } } /** - * @see SetupWizard::getSetup() + * Return the setup for this wizard + * + * @return Setup */ public function getSetup() { $pageData = $this->getPageData(); $setup = new Setup(); - $setup->addStep(new MakeDirStep(array($this->getConfigDir() . '/modules/monitoring'), 0775)); - $setup->addStep( new BackendStep(array( 'backendConfig' => $pageData['setup_monitoring_backend'], @@ -147,33 +160,72 @@ class MonitoringWizard extends Wizard implements SetupWizard )) ); - $setup->addStep(new EnableModuleStep('monitoring')); - return $setup; } /** - * @see SetupWizard::getRequirements() + * Return the requirements of this wizard + * + * @return RequirementSet */ public function getRequirements() { - return new Requirements(); - } + $set = new RequirementSet(); - /** - * Return the configuration directory of Icinga Web 2 - * - * @return string - */ - protected function getConfigDir() - { - if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { - $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; - } else { - $configDir = '/etc/icingaweb'; - } + // TODO(8254): Add this to the $backendSet + $set->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'Sockets', + 'description' => mt( + 'monitoring', + 'In case it\'s desired that a TCP connection is being used by Icinga Web 2 to' + . ' access a Livestatus interface, the Sockets module for PHP is required.' + ) + ))); - $canonical = realpath($configDir); - return $canonical ? $canonical : $configDir; + $backendSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mysqlSet = new RequirementSet(true); + $mysqlSet->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'mysql', + 'alias' => 'PDO-MySQL', + 'description' => mt( + 'monitoring', + 'To access the IDO stored in a MySQL database the PDO-MySQL module for PHP is required.' + ) + ))); + $mysqlSet->add(new ClassRequirement(array( + 'optional' => true, + 'condition' => 'Zend_Db_Adapter_Pdo_Mysql', + 'alias' => mt('monitoring', 'Zend database adapter for MySQL'), + 'description' => mt( + 'monitoring', + 'The Zend database adapter for MySQL is required to access a MySQL database.' + ) + ))); + $backendSet->merge($mysqlSet); + $pgsqlSet = new RequirementSet(true); + $pgsqlSet->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'pgsql', + 'alias' => 'PDO-PostgreSQL', + 'description' => mt( + 'monitoring', + 'To access the IDO stored in a PostgreSQL database the PDO-PostgreSQL module for PHP is required.' + ) + ))); + $pgsqlSet->add(new ClassRequirement(array( + 'optional' => true, + 'condition' => 'Zend_Db_Adapter_Pdo_Pgsql', + 'alias' => mt('monitoring', 'Zend database adapter for PostgreSQL'), + 'description' => mt( + 'monitoring', + 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.' + ) + ))); + $backendSet->merge($pgsqlSet); + $set->merge($backendSet); + + return $set; } } diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php index fd08f6a20..3bb10d77e 100644 --- a/modules/monitoring/library/Monitoring/Object/Host.php +++ b/modules/monitoring/library/Monitoring/Object/Host.php @@ -1,6 +1,5 @@ 'host_process_performance_data' + 'host_notifications_enabled', + 'host_notifications_enabled_changed', + 'host_obsessing', + 'host_obsessing_changed', + 'host_output', + 'host_passive_checks_enabled', + 'host_passive_checks_enabled_changed', + 'host_percent_state_change', + 'host_perfdata', + 'host_process_perfdata' => 'host_process_performance_data', + 'host_state', + 'host_state_type' ); if ($this->backend->getType() === 'livestatus') { $columns[] = 'host_contacts'; } - return $this->backend->select()->from('hostStatus', $columns) + return $this->backend->select()->from('hoststatus', $columns) ->where('host_name', $this->host); } @@ -148,10 +149,10 @@ class Host extends MonitoredObject public function fetchServices() { $services = array(); - foreach ($this->backend->select()->from('serviceStatus', array('service_description')) + foreach ($this->backend->select()->from('servicestatus', array('service_description')) ->where('host_name', $this->host) - ->getQuery() - ->fetchAll() as $service) { + ->applyFilter($this->getFilter()) + ->getQuery() as $service) { $services[] = new Service($this->backend, $this->host, $service->service_description); } $this->services = $services; @@ -172,20 +173,32 @@ class Host extends MonitoredObject $translate = (bool) $translate; switch ((int) $state) { case self::STATE_UP: - $text = $translate ? mt('monitoring', 'up') : 'up'; + $text = $translate ? mt('monitoring', 'UP') : 'up'; break; case self::STATE_DOWN: - $text = $translate ? mt('monitoring', 'down') : 'down'; + $text = $translate ? mt('monitoring', 'DOWN') : 'down'; break; case self::STATE_UNREACHABLE: - $text = $translate ? mt('monitoring', 'unreachable') : 'unreachable'; + $text = $translate ? mt('monitoring', 'UNREACHABLE') : 'unreachable'; break; case self::STATE_PENDING: - $text = $translate ? mt('monitoring', 'pending') : 'pending'; + $text = $translate ? mt('monitoring', 'PENDING') : 'pending'; break; default: throw new InvalidArgumentException('Invalid host state \'%s\'', $state); } return $text; } + + public function getNotesUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->host_notes_url) + ); + } + + public function getNotes() + { + return $this->host_notes; + } } diff --git a/modules/monitoring/library/Monitoring/Object/HostList.php b/modules/monitoring/library/Monitoring/Object/HostList.php index 2d3f57495..2066b5150 100644 --- a/modules/monitoring/library/Monitoring/Object/HostList.php +++ b/modules/monitoring/library/Monitoring/Object/HostList.php @@ -1,13 +1,20 @@ problem === true && (bool) $host->handled === false; + + $stateName = 'hosts_' . $host::getStateText($host->state); + ++$hostStates[$stateName]; + ++$hostStates[$stateName. ($unhandled ? '_unhandled' : '_handled')]; + } + + $hostStates['hosts_total'] = count($this); + + $ds = new ArrayDatasource(array((object) $hostStates)); + return $ds->select(); + } + + /** + * Return an empty array with all possible host state names + * + * @return array An array containing all possible host states as keys and 0 as values. + */ + public static function getHostStatesSummaryEmpty() + { + return String::cartesianProduct( + array( + array('hosts'), + array( + Host::getStateText(Host::STATE_UP), + Host::getStateText(Host::STATE_DOWN), + Host::getStateText(Host::STATE_UNREACHABLE), + Host::getStateText(Host::STATE_PENDING) + ), + array(null, 'handled', 'unhandled') + ), + '_' + ); + } + + /** + * Returns a Filter that matches all hosts in this list + * + * @return Filter + */ + public function objectsFilter($columns = array('host' => 'host')) + { + $filterExpression = array(); + foreach ($this as $host) { + $filterExpression[] = Filter::where($columns['host'], $host->getName()); + } + return FilterOr::matchAny($filterExpression); + } + + /** + * Get the comments + * + * @return \Icinga\Module\Monitoring\DataView\Hostcomment + */ + public function getComments() + { + return $this->backend + ->select() + ->from('hostcomment', array('host_name')) + ->applyFilter(clone $this->filter); + } + + /** + * Get the scheduled downtimes + * + * @return \Icinga\Module\Monitoring\DataView\Hostdowntime + */ + public function getScheduledDowntimes() + { + return $this->backend + ->select() + ->from('hostdowntime', array('host_name')) + ->applyFilter(clone $this->filter); + } + + /** + * @return ObjectList + */ + public function getUnacknowledgedObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if (! in_array((int) $object->state, array(0, 99)) && + (bool) $object->host_acknowledged === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } } diff --git a/modules/monitoring/application/views/helpers/ResolveMacros.php b/modules/monitoring/library/Monitoring/Object/Macro.php similarity index 52% rename from modules/monitoring/application/views/helpers/ResolveMacros.php rename to modules/monitoring/library/Monitoring/Object/Macro.php index 385962a85..4a9045b58 100644 --- a/modules/monitoring/application/views/helpers/ResolveMacros.php +++ b/modules/monitoring/library/Monitoring/Object/Macro.php @@ -1,37 +1,41 @@ 'host_name', - 'HOSTADDRESS' => 'host_address', - 'SERVICEDESC' => 'service_description' + private static $icingaMacros = array( + 'HOSTNAME' => 'host_name', + 'HOSTADDRESS' => 'host_address', + 'SERVICEDESC' => 'service_description', + 'host.name' => 'host_name', + 'host.address' => 'host_address', + 'service.description' => 'service_description' ); /** * Return the given string with macros being resolved * * @param string $input The string in which to look for macros - * @param MonitoredObject|stdClass $object The host or service used to resolve macros + * @param MonitoredObject|stdClass $object The host or service used to resolve macros * * @return string The substituted or unchanged string */ - public function resolveMacros($input, $object) + public static function resolveMacros($input, $object) { $matches = array(); if (preg_match_all('@\$([^\$\s]+)\$@', $input, $matches)) { foreach ($matches[1] as $key => $value) { - $newValue = $this->resolveMacro($value, $object); + $newValue = self::resolveMacro($value, $object); if ($newValue !== $value) { $input = str_replace($matches[0][$key], $newValue, $input); } @@ -45,14 +49,14 @@ class Zend_View_Helper_ResolveMacros extends Zend_View_Helper_Abstract * Resolve a macro based on the given object * * @param string $macro The macro to resolve - * @param MonitoredObject|stdClass $object The object used to resolve the macro + * @param MonitoredObject|stdClass $object The object used to resolve the macro * * @return string The new value or the macro if it cannot be resolved */ - public function resolveMacro($macro, $object) + public static function resolveMacro($macro, $object) { - if (array_key_exists($macro, $this->icingaMacros) && $object->{$this->icingaMacros[$macro]} !== false) { - return $object->{$this->icingaMacros[$macro]}; + if (array_key_exists($macro, self::$icingaMacros) && $object->{self::$icingaMacros[$macro]} !== false) { + return $object->{self::$icingaMacros[$macro]}; } if (array_key_exists($macro, $object->customvars)) { return $object->customvars[$macro]; diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 4be004d99..5f278c92e 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -1,19 +1,20 @@ getFilter()->addFilter($filter); + return $this; + } + + public function setFilter(Filter $filter) + { + // Left out on purpose. Interface is deprecated. + } + + public function getFilter() + { + if ($this->filter === null) { + $this->filter = Filter::matchAll(); + } + return $this->filter; + } + + public function addFilter(Filter $filter) + { + // Left out on purpose. Interface is deprecated. + } + + public function where($condition, $value = null) + { + // Left out on purpose. Interface is deprecated. + } + /** * Fetch the object's properties * @@ -140,7 +177,7 @@ abstract class MonitoredObject */ public function fetch() { - $this->properties = $this->getDataView()->getQuery()->fetchRow(); + $this->properties = $this->getDataView()->applyFilter($this->getFilter())->getQuery()->fetchRow(); if ($this->properties === false) { return false; } @@ -211,15 +248,18 @@ abstract class MonitoredObject $comments = $this->backend->select()->from('comment', array( 'id' => 'comment_internal_id', 'timestamp' => 'comment_timestamp', - 'author' => 'comment_author', + 'author' => 'comment_author_name', 'comment' => 'comment_data', 'type' => 'comment_type', )) ->where('comment_type', array('comment', 'ack')) - ->where('comment_objecttype', $this->type) - ->where('comment_host', $this->host_name); + ->where('object_type', $this->type); if ($this->type === self::TYPE_SERVICE) { - $comments->where('comment_service', $this->service_description); + $comments + ->where('service_host_name', $this->host_name) + ->where('service_description', $this->service_description); + } else { + $comments->where('host_name', $this->host_name); } $this->comments = $comments->getQuery()->fetchAll(); return $this; @@ -234,26 +274,29 @@ abstract class MonitoredObject { $downtimes = $this->backend->select()->from('downtime', array( 'id' => 'downtime_internal_id', - 'objecttype' => 'downtime_objecttype', + 'objecttype' => 'object_type', 'comment' => 'downtime_comment', - 'author' => 'downtime_author', + 'author_name' => 'downtime_author_name', 'start' => 'downtime_start', 'scheduled_start' => 'downtime_scheduled_start', + 'scheduled_end' => 'downtime_scheduled_end', 'end' => 'downtime_end', 'duration' => 'downtime_duration', 'is_flexible' => 'downtime_is_flexible', 'is_fixed' => 'downtime_is_fixed', 'is_in_effect' => 'downtime_is_in_effect', - 'entry_time' => 'downtime_entry_time', - 'host' => 'downtime_host', - 'service' => 'downtime_service' + 'entry_time' => 'downtime_entry_time' )) - ->where('downtime_objecttype', $this->type) - ->where('downtime_host', $this->host_name) + ->where('object_type', $this->type) ->order('downtime_is_in_effect', 'DESC') ->order('downtime_scheduled_start', 'ASC'); if ($this->type === self::TYPE_SERVICE) { - $downtimes->where('downtime_service', $this->service_description); + $downtimes + ->where('service_host_name', $this->host_name) + ->where('service_description', $this->service_description); + } else { + $downtimes + ->where('host_name', $this->host_name); } $this->downtimes = $downtimes->getQuery()->fetchAll(); return $this; @@ -266,12 +309,11 @@ abstract class MonitoredObject */ public function fetchHostgroups() { - $hostGroups = $this->backend->select()->from('hostgroup', array( - 'hostgroup_name', - 'hostgroup_alias' - )) - ->where('host_name', $this->host); - $this->hostgroups = $hostGroups->getQuery()->fetchPairs(); + $this->hostgroups = $this->backend->select() + ->from('hostgroup', array('hostgroup_name', 'hostgroup_alias')) + ->where('host_name', $this->host_name) + ->applyFilter($this->getFilter()) + ->fetchPairs(); return $this; } @@ -348,12 +390,13 @@ abstract class MonitoredObject 'contact_pager', )); if ($this->type === self::TYPE_SERVICE) { - $contacts->where('service_host_name', $this->host_name); - $contacts->where('service_description', $this->service_description); + $contacts + ->where('service_host_name', $this->host_name) + ->where('service_description', $this->service_description); } else { $contacts->where('host_name', $this->host_name); } - $this->contacts = $contacts->getQuery()->fetchAll(); + $this->contacts = $contacts->applyFilter($this->getFilter())->getQuery()->fetchAll(); return $this; } @@ -364,13 +407,12 @@ abstract class MonitoredObject */ public function fetchServicegroups() { - $serviceGroups = $this->backend->select()->from('servicegroup', array( - 'servicegroup_name', - 'servicegroup_alias' - )) - ->where('service_host_name', $this->host_name) - ->where('service_description', $this->service_description); - $this->servicegroups = $serviceGroups->getQuery()->fetchPairs(); + $this->servicegroups = $this->backend->select() + ->from('servicegroup', array('servicegroup_name', 'servicegroup_alias')) + ->where('host_name', $this->host_name) + ->where('service_description', $this->service_description) + ->applyFilter($this->getFilter()) + ->fetchPairs(); return $this; } @@ -389,12 +431,15 @@ abstract class MonitoredObject $contactsGroups = $this->backend->select()->from('contactgroup', array( 'contactgroup_name', 'contactgroup_alias' - )) - ->where('host_name', $this->host_name); + )); if ($this->type === self::TYPE_SERVICE) { - $contactsGroups->where('service_description', $this->service_description); + $contactsGroups + ->where('service_host_name', $this->host_name) + ->where('service_description', $this->service_description); + } else { + $contactsGroups->where('host_name', $this->host_name); } - $this->contactgroups = $contactsGroups->getQuery()->fetchAll(); + $this->contactgroups = $contactsGroups->applyFilter($this->getFilter())->getQuery()->fetchAll(); return $this; } @@ -405,23 +450,22 @@ abstract class MonitoredObject */ public function fetchEventhistory() { - $eventHistory = $this->backend->select()->from('eventHistory', array( + $eventHistory = $this->backend->select()->from('eventhistory', array( 'object_type', 'host_name', + 'host_display_name', 'service_description', + 'service_display_name', 'timestamp', 'state', - 'attempt', - 'max_attempts', 'output', 'type' )) - ->order('timestamp', 'DESC') ->where('host_name', $this->host_name); if ($this->type === self::TYPE_SERVICE) { $eventHistory->where('service_description', $this->service_description); } - $this->eventhistory = $eventHistory->getQuery(); + $this->eventhistory = $eventHistory->applyFilter($this->getFilter()); return $this; } @@ -432,12 +476,9 @@ abstract class MonitoredObject */ public function fetchStats() { - $this->stats = $this->backend->select()->from('statusSummary', array( + $this->stats = $this->backend->select()->from('servicestatussummary', array( 'services_total', 'services_ok', - 'services_problem', - 'services_problem_handled', - 'services_problem_unhandled', 'services_critical', 'services_critical_unhandled', 'services_critical_handled', @@ -450,7 +491,7 @@ abstract class MonitoredObject 'services_pending', )) ->where('service_host_name', $this->host_name) - ->getQuery() + ->applyFilter($this->getFilter()) ->fetchRow(); return $this; } @@ -516,4 +557,73 @@ abstract class MonitoredObject } return null; } + + /** + * The notes for this monitored object + * + * @return string The notes as a string + */ + public abstract function getNotes(); + + /** + * Get all note urls configured for this monitored object + * + * @return array All note urls as a string + */ + public abstract function getNotesUrls(); + + /** + * Get all action urls configured for this monitored object + * + * @return array All note urls as a string + */ + public function getActionUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->action_url) + ); + } + + /** + * Resolve macros in all given strings in the current object context + * + * @param array $strs An array of urls as string + * @return type + */ + protected function resolveAllStrings(array $strs) + { + foreach ($strs as $i => $str) { + $strs[$i] = Macro::resolveMacros($str, $this); + } + return $strs; + } + + /** + * Parse the content of the action_url or notes_url attributes + * + * Find all occurences of http links, separated by whitespaces and quoted + * by single or double-ticks. + * + * @link http://docs.icinga.org/latest/de/objectdefinitions.html + * + * @param string $urlString A string containing one or more urls + * @return array Array of urls as strings + */ + public static function parseAttributeUrls($urlString) + { + if (empty($urlString)) { + return array(); + } + if (strpos($urlString, "' ") === false) { + $links[] = $urlString; + } else { + // parse notes-url format + foreach (explode("' ", $urlString) as $url) { + $url = strpos($url, "'") === 0 ? substr($url, 1) : $url; + $url = strrpos($url, "'") === strlen($url) - 1 ? substr($url, 0, strlen($url) - 1) : $url; + $links[] = $url; + } + } + return $links; + } } diff --git a/modules/monitoring/library/Monitoring/Object/ObjectList.php b/modules/monitoring/library/Monitoring/Object/ObjectList.php index 6eb1e4db3..f65f4f109 100644 --- a/modules/monitoring/library/Monitoring/Object/ObjectList.php +++ b/modules/monitoring/library/Monitoring/Object/ObjectList.php @@ -1,24 +1,45 @@ backend = $backend; } + /** + * @param array $columns + * + * @return $this + */ public function setColumns(array $columns) { $this->columns = $columns; return $this; } + /** + * @return array + */ public function getColumns() { return $this->columns; } - public function setFilter($filter) + /** + * @param Filter $filter + * + * @return $this + */ + public function setFilter(Filter $filter) { $this->filter = $filter; return $this; } + /** + * @return Filter + */ public function getFilter() { + if ($this->filter === null) { + $this->filter = Filter::matchAll(); + } + return $this->filter; } + public function applyFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + return $this; + } + + public function addFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + } + + public function where($condition, $value = null) + { + $this->getFilter()->addFilter(Filter::where($condition, $value)); + } + abstract protected function fetchObjects(); + /** + * @return array + */ public function fetch() { if ($this->objects === null) { @@ -58,12 +118,20 @@ abstract class ObjectList implements Countable, IteratorAggregate return $this->objects; } + /** + * @return int + */ public function count() { if ($this->count === null) { - $this->count = (int) $this->backend->select()->from($this->dataViewName)->applyFilter($this->filter) - ->getQuery()->count(); + $this->count = (int) $this->backend + ->select() + ->from($this->dataViewName, $this->columns) + ->applyFilter($this->filter) + ->getQuery() + ->count(); } + return $this->count; } @@ -84,4 +152,99 @@ abstract class ObjectList implements Countable, IteratorAggregate { return $this->backend->select()->from('comment')->applyFilter($this->filter); } + + /** + * Get the scheduled downtimes + * + * @return type + */ + public function getScheduledDowntimes() + { + return $this->backend->select()->from('downtime')->applyFilter($this->filter); + } + + /** + * @return ObjectList + */ + public function getAcknowledgedObjects() + { + $acknowledgedObjects = array(); + foreach ($this as $object) { + if ((bool) $object->acknowledged === true) { + $acknowledgedObjects[] = $object; + } + } + return $this->newFromArray($acknowledgedObjects); + } + + /** + * @return ObjectList + */ + public function getObjectsInDowntime() + { + $objectsInDowntime = array(); + foreach ($this as $object) { + if ((bool) $object->in_downtime === true) { + $objectsInDowntime[] = $object; + } + } + return $this->newFromArray($objectsInDowntime); + } + + /** + * @return ObjectList + */ + public function getUnhandledObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if ((bool) $object->problem === true && (bool) $object->handled === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } + + /** + * @return ObjectList + */ + public function getProblemObjects() + { + $handledObjects = array(); + foreach ($this as $object) { + if ((bool) $object->problem === true) { + $handledObjects[] = $object; + } + } + return $this->newFromArray($handledObjects); + } + + /** + * @return ObjectList + */ + public abstract function getUnacknowledgedObjects(); + + /** + * Create a ObjectList from an array of hosts without querying a backend + * + * @return ObjectList + */ + protected function newFromArray(array $objects) + { + $class = get_called_class(); + $list = new $class($this->backend); + $list->objects = $objects; + $list->count = count($objects); + $list->filter = $list->objectsFilter(); + return $list; + } + + /** + * Create a filter that matches exactly the elements of this object list + * + * @param array $columns Override default column names. + * + * @return Filter + */ + abstract function objectsFilter($columns = array()); } diff --git a/modules/monitoring/library/Monitoring/Object/Service.php b/modules/monitoring/library/Monitoring/Object/Service.php index 6d5245f0d..855e62882 100644 --- a/modules/monitoring/library/Monitoring/Object/Service.php +++ b/modules/monitoring/library/Monitoring/Object/Service.php @@ -1,6 +1,5 @@ backend->select()->from('serviceStatus', array( - 'host_name', - 'host_state', - 'host_state_type', - 'host_last_state_change', - 'host_address', - 'host_problem', - 'host_handled', - 'service_description', - 'service_display_name', - 'service_state', - 'service_in_downtime', - 'service_acknowledged', - 'service_handled', - 'service_unhandled', - 'service_output', - 'service_last_state_change', - 'service_icon_image', - 'service_long_output', - 'service_is_flapping', - 'service_state_type', - 'service_severity', - 'service_last_check', - 'service_notifications_enabled', - 'service_notifications_enabled_changed', - 'service_action_url', - 'service_notes_url', - 'service_last_check', - 'service_next_check', - 'service_attempt', - 'service_last_notification', - 'service_check_command', - 'service_check_source', - 'service_current_notification_number', + return $this->backend->select()->from('servicestatus', array( 'host_icon_image', + 'host_icon_image_alt', 'host_acknowledged', - 'host_output', - 'host_long_output', - 'host_in_downtime', - 'host_is_flapping', - 'host_last_check', - 'host_notifications_enabled', - 'host_unhandled_services', - 'host_action_url', - 'host_notes_url', - 'host_display_name', - 'host_alias', - 'host_ipv4', - 'host_severity', - 'host_perfdata', 'host_active_checks_enabled', + 'host_address', + 'host_alias', + 'host_display_name', + 'host_handled', + 'host_in_downtime', + 'host_last_state_change', + 'host_name', + 'host_notifications_enabled', 'host_passive_checks_enabled', - 'host_last_hard_state', - 'host_last_hard_state_change', - 'host_last_time_up', - 'host_last_time_down', - 'host_last_time_unreachable', - 'host_modified_host_attributes', - 'host', - 'service', - 'service_hard_state', - 'service_problem', - 'service_perfdata', + 'host_state', + 'service_icon_image', + 'service_icon_image_alt', + 'service_acknowledged', + 'service_action_url', 'service_active_checks_enabled', 'service_active_checks_enabled_changed', - 'service_passive_checks_enabled', - 'service_passive_checks_enabled_changed', - 'service_last_hard_state', - 'service_last_hard_state_change', - 'service_last_time_ok', - 'service_last_time_warning', - 'service_last_time_critical', - 'service_last_time_unknown', + 'service_attempt', + 'service_check_command', 'service_check_execution_time', 'service_check_latency', - 'service_current_check_attempt', - 'service_max_check_attempts', - 'service_obsessing', - 'service_obsessing_changed', + 'service_check_source', + 'service_current_notification_number', + 'service_description', + 'service_display_name', 'service_event_handler_enabled', 'service_event_handler_enabled_changed', 'service_flap_detection_enabled', 'service_flap_detection_enabled_changed', - 'service_modified_service_attributes', - 'service_process_performance_data', - 'process_perfdata' => 'service_process_performance_data', + 'service_handled', + 'service_in_downtime', + 'service_is_flapping', + 'service_is_reachable', + 'service_last_check', + 'service_last_notification', + 'service_last_state_change', + 'service_long_output', + 'service_next_check', + 'service_notes', + 'service_notes_url', + 'service_notifications_enabled', + 'service_notifications_enabled_changed', + 'service_obsessing', + 'service_obsessing_changed', + 'service_output', + 'service_passive_checks_enabled', + 'service_passive_checks_enabled_changed', 'service_percent_state_change', - 'service_host_name' + 'service_perfdata', + 'service_process_perfdata' => 'service_process_performance_data', + 'service_state', + 'service_state_type' )) ->where('host_name', $this->host->getName()) ->where('service_description', $this->service); @@ -213,23 +180,35 @@ class Service extends MonitoredObject $translate = (bool) $translate; switch ((int) $state) { case self::STATE_OK: - $text = $translate ? mt('monitoring', 'ok') : 'ok'; + $text = $translate ? mt('monitoring', 'OK') : 'ok'; break; case self::STATE_WARNING: - $text = $translate ? mt('monitoring', 'warning') : 'warning'; + $text = $translate ? mt('monitoring', 'WARNING') : 'warning'; break; case self::STATE_CRITICAL: - $text = $translate ? mt('monitoring', 'critical') : 'critical'; + $text = $translate ? mt('monitoring', 'CRITICAL') : 'critical'; break; case self::STATE_UNKNOWN: - $text = $translate ? mt('monitoring', 'unknown') : 'unknown'; + $text = $translate ? mt('monitoring', 'UNKNOWN') : 'unknown'; break; case self::STATE_PENDING: - $text = $translate ? mt('monitoring', 'pending') : 'pending'; + $text = $translate ? mt('monitoring', 'PENDING') : 'pending'; break; default: throw new InvalidArgumentException('Invalid service state \'%s\'', $state); } return $text; } + + public function getNotesUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->service_notes_url) + ); + } + + public function getNotes() + { + return $this->service_notes; + } } diff --git a/modules/monitoring/library/Monitoring/Object/ServiceList.php b/modules/monitoring/library/Monitoring/Object/ServiceList.php index 858d04b5b..0dffd9464 100644 --- a/modules/monitoring/library/Monitoring/Object/ServiceList.php +++ b/modules/monitoring/library/Monitoring/Object/ServiceList.php @@ -1,13 +1,24 @@ serviceStateSummary) { + $this->initStateSummaries(); + } + + $ds = new ArrayDatasource(array((object) $this->serviceStateSummary)); + return $ds->select(); + } + + /** + * Create a state summary of all hosts that can be consumed by hostsummary.phtml + * + * @return SimpleQuery + */ + public function getHostStateSummary() + { + if (! $this->hostStateSummary) { + $this->initStateSummaries(); + } + + $ds = new ArrayDatasource(array((object) $this->hostStateSummary)); + return $ds->select(); + } + + /** + * Calculate the current state summary and populate hostStateSummary and serviceStateSummary + * properties + */ + protected function initStateSummaries() + { + $serviceStates = array_fill_keys(self::getServiceStatesSummaryEmpty(), 0); + $hostStates = array_fill_keys(HostList::getHostStatesSummaryEmpty(), 0); + + foreach ($this as $service) { + $unhandled = false; + if ((bool) $service->problem === true && (bool) $service->handled === false) { + $unhandled = true; + } + + $stateName = 'services_' . $service::getStateText($service->state); + ++$serviceStates[$stateName]; + ++$serviceStates[$stateName . ($unhandled ? '_unhandled' : '_handled')]; + + if (! isset($knownHostStates[$service->getHost()->getName()])) { + $unhandledHost = (bool) $service->host_problem === true && (bool) $service->host_handled === false; + ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)]; + ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state) + . ($unhandledHost ? '_unhandled' : '_handled')]; + $knownHostStates[$service->getHost()->getName()] = true; + } + } + + $serviceStates['services_total'] = count($this); + $this->hostStateSummary = $hostStates; + $this->serviceStateSummary = $serviceStates; + } + + /** + * Return an empty array with all possible host state names + * + * @return array An array containing all possible host states as keys and 0 as values. + */ + public static function getServiceStatesSummaryEmpty() + { + return String::cartesianProduct( + array( + array('services'), + array( + Service::getStateText(Service::STATE_OK), + Service::getStateText(Service::STATE_WARNING), + Service::getStateText(Service::STATE_CRITICAL), + Service::getStateText(Service::STATE_UNKNOWN), + Service::getStateText(Service::STATE_PENDING) + ), + array(null, 'handled', 'unhandled') + ), + '_' + ); + } + + /** + * Returns a Filter that matches all hosts in this HostList + * + * @param array $columns Override filter column names + * + * @return Filter + */ + public function objectsFilter($columns = array('host' => 'host', 'service' => 'service')) + { + $filterExpression = array(); + foreach ($this as $service) { + $filterExpression[] = Filter::matchAll( + Filter::where($columns['host'], $service->getHost()->getName()), + Filter::where($columns['service'], $service->getName()) + ); + } + return FilterOr::matchAny($filterExpression); + } + + /** + * Get the comments + * + * @return \Icinga\Module\Monitoring\DataView\Hostcomment + */ + public function getComments() + { + return $this->backend + ->select() + ->from('servicecomment', array('host_name', 'service_description')) + ->applyFilter(clone $this->filter); + } + + /** + * Get the scheduled downtimes + * + * @return \Icinga\Module\Monitoring\DataView\Servicedowntime + */ + public function getScheduledDowntimes() + { + return $this->backend + ->select() + ->from('servicedowntime', array('host_name', 'service_description')) + ->applyFilter(clone $this->filter); + } + + /** + * @return ObjectList + */ + public function getUnacknowledgedObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if (! in_array((int) $object->state, array(0, 99)) && + (bool) $object->service_acknowledged === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } } diff --git a/modules/monitoring/library/Monitoring/Plugin.php b/modules/monitoring/library/Monitoring/Plugin.php index ad53f7756..78b709d2c 100644 --- a/modules/monitoring/library/Monitoring/Plugin.php +++ b/modules/monitoring/library/Monitoring/Plugin.php @@ -1,6 +1,5 @@ unit === 'c'; } + /** + * Returns whether it is possible to display a visual representation + * + * @return bool True when the perfdata is visualizable + */ + public function isVisualizable() + { + return isset($this->minValue) && isset($this->maxValue) && isset($this->value); + } + /** * Return this perfomance data's label */ @@ -223,7 +245,7 @@ class Perfdata /** * Return the minimum value or null if it is not available * - * @return null|float + * @return null|string */ public function getMinimumValue() { @@ -247,7 +269,7 @@ class Perfdata */ public function __toString() { - return sprintf(strpos($this->label, ' ') === false ? '%s=%s' : "'%s'=%s", $this->label, $this->perfdataValue); + return $this->formatLabel(); } /** @@ -278,10 +300,8 @@ class Perfdata $this->minValue = self::convert($parts[3], $this->unit); } case 3: - // TODO(#6123): Tresholds have the same UOM and need to be converted as well! $this->criticalThreshold = trim($parts[2]) ? trim($parts[2]) : null; case 2: - // TODO(#6123): Tresholds have the same UOM and need to be converted as well! $this->warningThreshold = trim($parts[1]) ? trim($parts[1]) : null; } } @@ -316,4 +336,178 @@ class Perfdata } } } + + protected function calculatePieChartData() + { + $rawValue = $this->getValue(); + $minValue = $this->getMinimumValue() !== null ? $this->getMinimumValue() : 0; + $maxValue = $this->getMaximumValue(); + $usedValue = ($rawValue - $minValue); + $unusedValue = ($maxValue - $minValue) - $usedValue; + + $warningThreshold = $this->convert($this->warningThreshold, $this->unit); + $criticalThreshold = $this->convert($this->criticalThreshold, $this->unit); + + $gray = $unusedValue; + $green = $orange = $red = 0; + + $pieState = self::PERFDATA_OK; + if ($warningThreshold > $criticalThreshold) { + // inverted threshold parsing OK > warning > critical + if (isset($warningThreshold) && $this->value <= $warningThreshold) { + $pieState = self::PERFDATA_WARNING; + } + if (isset($criticalThreshold) && $this->value <= $criticalThreshold) { + $pieState = self::PERFDATA_CRITICAL; + } + + } else { + // TODO: Use standard perfdata range format to decide the state #8194 + + // regular threshold parsing OK < warning < critical + if (isset($warningThreshold) && $rawValue > $warningThreshold) { + $pieState = self::PERFDATA_WARNING; + } + if (isset($criticalThreshold) && $rawValue > $criticalThreshold) { + $pieState = self::PERFDATA_CRITICAL; + } + } + + switch ($pieState) { + case self::PERFDATA_OK: + $green = $usedValue; + break; + + case self::PERFDATA_CRITICAL: + $red = $usedValue; + break; + + case self::PERFDATA_WARNING: + $orange = $usedValue; + break; + } + + return array($green, $orange, $red, $gray); + } + + + public function asInlinePie() + { + if (! $this->isVisualizable()) { + throw new ProgrammingError('Cannot calculate piechart data for unvisualizable perfdata entry.'); + } + + $data = $this->calculatePieChartData(); + $pieChart = new InlinePie($data, $this); + $pieChart->setColors(array('#44bb77', '#ffaa44', '#ff5566', '#ddccdd')); + $pieChart->setSparklineClass('sparkline-perfdata'); + + if (Zend_Controller_Front::getInstance()->getRequest()->isXmlHttpRequest()) { + $pieChart->disableNoScript(); + } + return $pieChart; + } + + /** + * Format the given value depending on the currently used unit + */ + protected function format($value) + { + if ($this->isPercentage()) { + return (string)$value . '%'; + } + if ($this->isBytes()) { + return Format::bytes($value); + } + if ($this->isSeconds()) { + return Format::seconds($value); + } + return number_format($value, 2); + } + + /** + * Format the title string that represents this perfdata set + * + * @param bool $html + * + * @return string + */ + public function formatLabel($html = false) + { + return sprintf( + $html ? '%s %s (%s%%)' : '%s %s (%s%%)', + htmlspecialchars($this->getLabel()), + $this->format($this->value), + number_format($this->getPercentage(), 2) + ); + } + + public function toArray() + { + $parts = array( + 'label' => $this->getLabel(), + 'value' => $this->format($this->getvalue()), + 'min' => isset($this->minValue) && !$this->isPercentage() ? $this->format($this->minValue) : '', + 'max' => isset($this->maxValue) && !$this->isPercentage() ? $this->format($this->maxValue) : '', + 'warn' => isset($this->warningThreshold) ? $this->format(self::convert($this->warningThreshold, $this->unit)) : '', + 'crit' => isset($this->criticalThreshold) ? $this->format(self::convert($this->criticalThreshold, $this->unit)) : '' + ); + return $parts; + } + + /** + * Return the state indicated by this perfdata + * + * @see Service + * + * @return int + */ + public function getState() + { + if ($this->value === null) { + return Service::STATE_UNKNOWN; + } + + if (! ($this->criticalThreshold === null + || $this->value < $this->criticalThreshold)) { + return Service::STATE_CRITICAL; + } + + if (! ($this->warningThreshold === null + || $this->value < $this->warningThreshold)) { + return Service::STATE_WARNING; + } + + return Service::STATE_OK; + } + + /** + * Return whether the state indicated by this perfdata is worse than + * the state indicated by the other perfdata + * CRITICAL > UNKNOWN > WARNING > OK + * + * @param Perfdata $rhs the other perfdata + * + * @return bool + */ + public function worseThan(Perfdata $rhs) + { + if (($state = $this->getState()) === ($rhsState = $rhs->getState())) { + return $this->getPercentage() > $rhs->getPercentage(); + } + + if ($state === Service::STATE_CRITICAL) { + return true; + } + + if ($state === Service::STATE_UNKNOWN) { + return $rhsState !== Service::STATE_CRITICAL; + } + + if ($state === Service::STATE_WARNING) { + return $rhsState === Service::STATE_OK; + } + + return false; + } } diff --git a/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php index c62904bb3..d503bc19f 100644 --- a/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php +++ b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php @@ -1,6 +1,5 @@ readLabel()); $value = trim($this->readUntil(' ')); - if ($label && $value) { + if ($label) { $this->perfdata[] = new Perfdata($label, $value); } } diff --git a/modules/monitoring/library/Monitoring/SecurityStep.php b/modules/monitoring/library/Monitoring/SecurityStep.php index 4a2c0f846..cf22990cf 100644 --- a/modules/monitoring/library/Monitoring/SecurityStep.php +++ b/modules/monitoring/library/Monitoring/SecurityStep.php @@ -1,13 +1,12 @@ data['securityConfig']; try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('modules/monitoring/config.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('modules/monitoring/config.ini')) + ->saveIni(); } catch (Exception $e) { $this->error = $e; return false; @@ -67,15 +64,21 @@ class SecurityStep extends Step public function getReport() { if ($this->error === false) { - $message = mt('monitoring', 'Monitoring security configuration has been successfully created: %s'); - return '

    ' . sprintf($message, Config::resolvePath('modules/monitoring/config.ini')) . '

    '; + return array(sprintf( + mt('monitoring', 'Monitoring security configuration has been successfully created: %s'), + Config::resolvePath('modules/monitoring/config.ini') + )); } elseif ($this->error !== null) { - $message = mt( - 'monitoring', - 'Monitoring security configuration could not be written to: %s; An error occured:' + return array( + sprintf( + mt( + 'monitoring', + 'Monitoring security configuration could not be written to: %s. An error occured:' + ), + Config::resolvePath('modules/monitoring/config.ini') + ), + sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error)) ); - return '

    ' . sprintf($message, Config::resolvePath('modules/monitoring/config.ini')) - . '

    ' . $this->error->getMessage() . '

    '; } } -} \ No newline at end of file +} diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php b/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php index bd83a42cd..4e942cda2 100644 --- a/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php +++ b/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php @@ -1,6 +1,5 @@ setAutorefreshInterval(10); - $checkNowForm = new CheckNowCommandForm(); - $checkNowForm - ->setObjects($this->object) - ->handleRequest(); - $this->view->checkNowForm = $checkNowForm; - if ( ! in_array((int) $this->object->state, array(0, 99))) { + $auth = $this->Auth(); + if ($auth->hasPermission('monitoring/command/schedule-check')) { + $checkNowForm = new CheckNowCommandForm(); + $checkNowForm + ->setObjects($this->object) + ->handleRequest(); + $this->view->checkNowForm = $checkNowForm; + } + if (! in_array((int) $this->object->state, array(0, 99))) { if ((bool) $this->object->acknowledged) { - $removeAckForm = new RemoveAcknowledgementCommandForm(); - $removeAckForm - ->setObjects($this->object) - ->handleRequest(); - $this->view->removeAckForm = $removeAckForm; - } else { - $ackForm = new AcknowledgeProblemCommandForm(); - $ackForm - ->setObjects($this->object) - ->handleRequest(); - $this->view->ackForm = $ackForm; + if ($auth->hasPermission('monitoring/command/remove-acknowledgement')) { + $removeAckForm = new RemoveAcknowledgementCommandForm(); + $removeAckForm + ->setObjects($this->object) + ->handleRequest(); + $this->view->removeAckForm = $removeAckForm; + } } } - if (count($this->object->comments) > 0) { - $delCommentForm = new DeleteCommentCommandForm(); - $delCommentForm - ->setObjects($this->object) - ->handleRequest(); - $this->view->delCommentForm = $delCommentForm; - } - if (count($this->object->downtimes > 0)) { - $delDowntimeForm = new DeleteDowntimeCommandForm(); - $delDowntimeForm - ->setObjects($this->object) - ->handleRequest(); - $this->view->delDowntimeForm = $delDowntimeForm; - } + $this->object->populate(); $toggleFeaturesForm = new ToggleObjectFeaturesCommandForm(); $toggleFeaturesForm + ->setBackend($this->backend) ->load($this->object) ->setObjects($this->object) ->handleRequest(); $this->view->toggleFeaturesForm = $toggleFeaturesForm; - $this->view->object = $this->object->populate(); + if (! empty($this->object->comments) && $auth->hasPermission('monitoring/command/comment/delete')) { + $delCommentForm = new DeleteCommentCommandForm(); + $delCommentForm->handleRequest(); + $this->view->delCommentForm = $delCommentForm; + } + if (! empty($this->object->downtimes) && $auth->hasPermission('monitoring/command/downtime/delete')) { + $delDowntimeForm = new DeleteDowntimeCommandForm(); + $delDowntimeForm->handleRequest(); + $this->view->delDowntimeForm = $delDowntimeForm; + } + $this->view->object = $this->object; + } + + /** + * Show the history for a host or service + */ + public function historyAction() + { + $this->getTabs()->activate('history'); + $this->view->history = $this->object->fetchEventHistory()->eventhistory; + $this->applyRestriction('monitoring/filter/objects', $this->view->history); + + $this->setupLimitControl(50); + $this->setupPaginationControl($this->view->history, 50); + $this->view->object = $this->object; } /** @@ -114,7 +122,9 @@ abstract class MonitoredObjectController extends Controller ->setRedirectUrl(Url::fromPath($this->commandRedirectUrl)->setParams($this->params)) ->handleRequest(); $this->view->form = $form; - $this->_helper->viewRenderer('partials/command-form', null, true); + $this->view->object = $this->object; + $this->view->tabs->remove('dashboard'); + $this->_helper->viewRenderer('partials/command/object-command-form', null, true); return $form; } @@ -138,34 +148,6 @@ abstract class MonitoredObjectController extends Controller */ abstract public function scheduleDowntimeAction(); - /** - * Remove a comment - */ - public function removeCommentAction() - { - /* - * TODO(el): This is here because monitoring/list/comments has buttons to remove comments. Because of the nature - * of an action, the form is accessible via GET which does not make much sense because the form requires - * us to populate the ID of the comment which is to be deleted. We may introduce a combo box for choosing - * the comment ID on GET or deny GET access. - */ - $this->handleCommandForm(new DeleteCommentCommandForm()); - } - - /** - * Remove a downtime - */ - public function deleteDowntimeAction() - { - /* - * TODO(el): This is here because monitoring/list/downtimes has buttons to remove comments. Because of the - * nature of an action, the form is accessible via GET which does not make much sense because the form requires - * us to populate the ID of the downtime which is to be deleted. We may introduce a combo box for choosing - * the downtime ID on GET or deny GET access. - */ - $this->handleCommandForm(new DeleteDowntimeCommandForm()); - } - /** * Create tabs */ @@ -174,10 +156,15 @@ abstract class MonitoredObjectController extends Controller $tabs = $this->getTabs(); $object = $this->object; if ($object->getType() === $object::TYPE_HOST) { + $isService = false; $params = array( 'host' => $object->getName() ); + if ($this->params->has('service')) { + $params['service'] = $this->params->get('service'); + } } else { + $isService = true; $params = array( 'host' => $object->getHost()->getName(), 'service' => $object->getName() @@ -186,17 +173,26 @@ abstract class MonitoredObjectController extends Controller $tabs->add( 'host', array( - 'title' => 'Host', + 'title' => sprintf( + $this->translate('Show detailed information for host %s'), + $isService ? $object->getHost()->getName() : $object->getName() + ), + 'label' => $this->translate('Host'), 'icon' => 'host', 'url' => 'monitoring/host/show', 'urlParams' => $params ) ); - if (isset($params['service'])) { + if ($isService || $this->params->has('service')) { $tabs->add( 'service', array( - 'title' => 'Service', + 'title' => sprintf( + $this->translate('Show detailed information for service %s on host %s'), + $isService ? $object->getName() : $this->params->get('service'), + $isService ? $object->getHost()->getName() : $object->getName() + ), + 'label' => $this->translate('Service'), 'icon' => 'service', 'url' => 'monitoring/service/show', 'urlParams' => $params @@ -206,25 +202,35 @@ abstract class MonitoredObjectController extends Controller $tabs->add( 'services', array( - 'title' => 'Services', + 'title' => sprintf( + $this->translate('List all services on host %s'), + $isService ? $object->getHost()->getName() : $object->getName() + ), + 'label' => $this->translate('Services'), 'icon' => 'services', - 'url' => 'monitoring/show/services', + 'url' => 'monitoring/host/services', 'urlParams' => $params ) ); - if ($this->backend->hasQuery('eventHistory')) { + if ($this->backend->hasQuery('eventhistory')) { $tabs->add( 'history', array( - 'title' => 'History', + 'title' => $isService + ? sprintf( + $this->translate('Show all event records of service %s on host %s'), + $object->getName(), + $object->getHost()->getName() + ) + : sprintf($this->translate('Show all event records of host %s'), $object->getName()) + , + 'label' => $this->translate('History'), 'icon' => 'rewind', - 'url' => 'monitoring/show/history', + 'url' => $isService ? 'monitoring/service/history' : 'monitoring/host/history', 'urlParams' => $params ) ); } - $tabs - ->extend(new OutputFormat()) - ->extend(new DashboardAction()); + $tabs->extend(new DashboardAction()); } } diff --git a/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php new file mode 100644 index 000000000..18c00563e --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php @@ -0,0 +1,46 @@ + url, where title will + * be used as link caption. Url should be an Icinga\Web\Url object when + * the link should point to an Icinga Web url - otherwise a string would + * be fine. + * + * Mixed example: + * + * return array( + * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($host->host_name), + * 'Logstash' => Url::fromPath( + * 'logstash/search/syslog', + * array('host' => $host->host_name) + * ) + * ); + * + * + * One might also provide ssh:// or rdp:// urls if equipped with fitting + * (safe) URL handlers for his browser(s). + * + * TODO: I'd love to see some kind of a Link/LinkSet object implemented + * for this and similar hooks. + * + * @param Host $host Monitoring host object + * + * @return array An array containing a list of host action links + */ + abstract public function getActionsForHost(Host $host); +} diff --git a/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php new file mode 100644 index 000000000..2df3c4f55 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php @@ -0,0 +1,46 @@ + url, where title will + * be used as link caption. Url should be an Icinga\Web\Url object when + * the link should point to an Icinga Web url - otherwise a string would + * be fine. + * + * Mixed example: + * + * return array( + * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($service->service_name), + * 'Logstash' => Url::fromPath( + * 'logstash/search/syslog', + * array('service' => $service->host_name) + * ) + * ); + * + * + * One might also provide ssh:// or rdp:// urls if equipped with fitting + * (safe) URL handlers for his browser(s). + * + * TODO: I'd love to see some kind of a Link/LinkSet object implemented + * for this and similar hooks. + * + * @param Service $service Monitoring service object + * + * @return array An array containing a list of service action links + */ + abstract public function getActionsForService(Service $service); +} diff --git a/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php index e265086ab..0af65d303 100644 --- a/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php +++ b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php @@ -1,6 +1,5 @@ getQuery()->fetchRow(); - - $serviceSummary = StatusSummaryView::fromRequest( - $request, - array( - 'services_ok', - 'services_critical_handled', - 'services_critical_unhandled', - 'services_warning_handled', - 'services_warning_unhandled', - 'services_unknown_handled', - 'services_unknown_unhandled', - 'services_pending' - ) - )->getQuery()->fetchRow(); - - return $this->getView()->partial( - 'layout/topbar.phtml', - 'monitoring', - array( - 'hostSummary' => $hostSummary, - 'serviceSummary' => $serviceSummary - ) - ); - } -} diff --git a/modules/monitoring/library/Monitoring/Web/Menu/BackendAvailabilityMenuItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Menu/BackendAvailabilityMenuItemRenderer.php new file mode 100644 index 000000000..4359d5da5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Menu/BackendAvailabilityMenuItemRenderer.php @@ -0,0 +1,73 @@ +select() + ->from( + 'programstatus', + array('is_currently_running') + ) + ->fetchRow(); + return $programStatus !== false ? (bool) $programStatus : false; + } + + /** + * {@inheritdoc} + */ + public function render(Menu $menu) + { + return $this->getBadge() . $this->createLink($menu); + } + + /** + * Get the problem badge HTML + * + * @return string + */ + protected function getBadge() + { + if (! $this->isCurrentlyRunning()) { + return sprintf( + '
    %d
    ', + sprintf( + mt('monitoring', 'Monitoring backend %s is not running'), MonitoringBackend::instance()->getName() + ), + 1 + ); + } + return ''; + } + + /** + * Get the problem data for the summary + * + * @return array|null + */ + public function getSummary() + { + if (! $this->isCurrentlyRunning()) { + return array( + 'problems' => 1, + 'title' => sprintf( + mt('monitoring', 'Monitoring backend %s is not running'), MonitoringBackend::instance()->getName() + ) + ); + } + return null; + } +} diff --git a/library/Icinga/Web/Menu/MonitoringMenuItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Menu/MonitoringMenuItemRenderer.php similarity index 56% rename from library/Icinga/Web/Menu/MonitoringMenuItemRenderer.php rename to modules/monitoring/library/Monitoring/Web/Menu/MonitoringMenuItemRenderer.php index 3bccb9327..df901b165 100644 --- a/library/Icinga/Web/Menu/MonitoringMenuItemRenderer.php +++ b/modules/monitoring/library/Monitoring/Web/Menu/MonitoringMenuItemRenderer.php @@ -1,29 +1,51 @@ getRestrictions($restriction) as $filter) { + $restrictions->addFilter(Filter::fromQueryString($filter)); + } + $filterable->applyFilter($restrictions); + return $filterable; + } + protected static function summary($column = null) { if (self::$summary === null) { - self::$summary = MonitoringBackend::instance()->select()->from( - 'statusSummary', + $summary = MonitoringBackend::instance()->select()->from( + 'statussummary', array( 'hosts_down_unhandled', 'services_critical_unhandled' ) - )->getQuery()->fetchRow(); + ); + static::applyRestriction('monitoring/filter/objects', $summary); + self::$summary = $summary->fetchRow(); } if ($column === null) { @@ -70,31 +92,18 @@ class MonitoringMenuItemRenderer implements MenuItemRenderer { public function render(Menu $menu) { - $count = $this->countItems(); - $badge = ''; - if ($count) { - $badge = sprintf( + return $this->getBadge() . $this->createLink($menu); + } + + protected function getBadge() + { + if ($count = $this->countItems()) { + return sprintf( '
    %s
    ', $this->getBadgeTitle(), $count ); } - if ($menu->getIcon() && strpos($menu->getIcon(), '.') === false) { - return sprintf( - '%s %s', - $badge, - $menu->getUrl() ?: '#', - $menu->getIcon(), - htmlspecialchars($menu->getTitle()) - ); - } - - return sprintf( - '%s%s%s', - $badge, - $menu->getUrl() ?: '#', - $menu->getIcon() ? ' ' : '', - htmlspecialchars($menu->getTitle()) - ); + return ''; } } diff --git a/modules/monitoring/library/Monitoring/Web/Menu/ProblemMenuItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Menu/ProblemMenuItemRenderer.php new file mode 100644 index 000000000..d17431398 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Menu/ProblemMenuItemRenderer.php @@ -0,0 +1,12 @@ + a { + .state > a { color: white; - font-size: 0.8em; + font-size: 0.857em; padding: 2px 5px; -} - -h1.tinystatesummary .state.handled a { + } } /* State badges */ @@ -121,7 +89,6 @@ span.state.active { } span.state span.state { - font-size: 1em; margin: 0 -3px 0 5px; } @@ -129,14 +96,26 @@ span.state.ok { background: @colorOk; } +span.state.up { + background: @colorOk; +} + span.state.critical { background: @colorCritical; } +span.state.down { + background: @colorCritical; +} + span.state.handled.critical { background: @colorCriticalHandled; } +span.state.handled.down { + background: @colorCriticalHandled; +} + span.state.warning { background: @colorWarning; } @@ -161,6 +140,70 @@ form.instance-features span.description, form.object-features span.description { display: inline; } +.boxview div.box form.instance-features div.header { + border-bottom: 1px solid #d9d9d9; + margin-bottom: 0.5em; + + h2 { + border: 0; + padding-bottom: 0; + } +} + +table.avp form.object-features div.header h4 { + margin: 0; +} + +table.avp { + th { + font-weight: normal; + font-size: 0.875em; + padding-top: 0.25em; + } + + h2 { + font-size: 0.875em; + line-height: 1.2em; + padding-bottom: 0.1em; + } + + td { + color: #666; + padding-bottom: 0.3em; + line-height: 1.5em; + th, td { + padding: 0; + } + } + + a, button.link-like { + color: @mainLayoutColor; + } + + .go-ahead { + a, button.link-like { + color: #222; + } + } + + .autosubmit-warning { + display: none; + } + + .object-features { + label { + font-weight: normal; + margin-right: 0; + width: 14em; + font-size: 0.875em; + } + + input { + margin: 0; + } + } +} + table.avp .customvar ul { list-style-type: none; margin: 0; @@ -172,7 +215,7 @@ div.selection-info { padding-top: 0.4em; float: right; cursor: help; - font-size: 0.8em; + font-size: 0.857em; } .optionbox { @@ -183,10 +226,1025 @@ div.selection-info { .optionbox label { max-width: 6.5em; text-align: left; - vertical-align: middle; + vertgical-align: middle; margin-right: 0em; } .optionbox input { vertical-align: middle; } + +.object-command form h1, .objects-command form h1 { + border: none; +} + +hr.command-separator { + border: none; + border-bottom: 2px solid @colorPetrol; +} + +div.backend-not-running { + background: @colorCritical; + color: white; + text-align: center; + padding: 0.1em; +} + +td.state { + .time-ago, + .time-since, + .time-until { + text-transform: capitalize; + } +} + +.inline-comments { + padding: 0; + margin: 0; + font-size: 0.857em; + + .time-ago { + font-style: italic; + color: #919191; + } + + li { + list-style-type: none; + margin-bottom: 8px; + } + + h3 { + border: none; + border-bottom: 1px solid gray; + font-weight: normal; + font-size: inherit; + color: inherit; + margin: 0; + padding-bottom: 0.1em; + } + + h3 .author { + font-weight: bold; + } + + h3 form { + display: none; + } + + h3 form { + float: right; + } + + li:hover h3 { + background: #F9F9F9; + position: relative; + + form { + display: inline; + } + } + + p { + margin: 0; + + a { + color: #222; + } + } +} + +/* Special tables and states */ + +table.colors { + font-size: 0.8em; + width: 98%; + margin: 0 1%; +} + +table.colors td { + text-align: center; + vertical-align: middle; + width: 10%; + height: 1.6em; + font-weight: normal; + border: 0.079em solid white; +} + +table.action td.state, table.objectstate td.state { + font-size: 0.857em; + text-align: center; +} + + +/* State row behaviour */ + +tr.state img.icon { + margin-right: 2px; +} + +tr.state a { + color: #222; +} + +/* Hostgroup badge quickfix */ +tr.state span a { + color: inherit; +} + +tr.state:hover a { + color: inherit; +} + +tr.state a.active { +} + +tr.state.new td.state { + font-weight: bold; +} + +tr.state td.state { + width: 9em; + color: white; + border-bottom: none; +} + +tr.state.handled td.state, tr.state.ok td.state, tr.state.up td.state, tr.state.pending td.state { + border-left-style: solid; + border-left-width: 1.5em; + padding-left: 0em; + padding-right: 0.5em; + color: black; + background-color: transparent; +} + +tr.state.ok td.state, tr.state.up td.state { + border-left-color: @colorOk; +} + +tr.state.warning td.state { + background-color: @colorWarning; +} + +tr.state.warning.handled td.state { + border-left-color: @colorWarningHandled; +} + +tr.state.critical td.state, tr.state.down td.state { + background-color: @colorCritical; +} + +tr.state.critical.handled td.state, tr.state.down.handled td.state { + border-left-color: @colorCriticalHandled; +} + +tr.state.unreachable td.state { + background-color: @colorUnreachable; +} + +tr.state.unreachable.handled td.state { + border-left-color: @colorUnreachableHandled; +} + +tr.state.unknown td.state { + background-color: @colorUnknown; +} + +tr.state.unknown.handled td.state { + border-left-color: @colorUnknownHandled; +} + +tr.state.pending td.state { + border-left-color: @colorPending; +} + +tr.state.invalid td.state { + background-color: @colorInvalid; +} + +tr.state.unreachable td.state { + background-color: @colorUnreachable; +} + +tr.state.unreachable.handled td.state { + border-left-color: @colorUnreachableHandled; +} + +tr.state.handled td.state { + color: inherit; + background-color: transparent !important; +} + +/* HOVER colors */ + +tr.state[href]:hover td.state { + background-color: #eee; +} + +tr.state.ok[href]:hover, tr.state.up[href]:hover { + background-color: @colorOk; +} + +tr.state.handled[href]:hover, tr.state.handled[href]:hover td.state { + color: #121212 !important; +} + +tr.state.warning[href]:hover { + background-color: @colorWarning; + color: white; +} + +tr.state.warning.handled[href]:hover { + background-color: @colorWarningHandled; +} + +tr.state.critical[href]:hover, tr.state.down[href]:hover { + background-color: @colorCritical; + color: white; +} + +tr.state.critical.handled[href]:hover, tr.state.down.handled[href]:hover { + background-color: @colorCriticalHandled; + color: #333; +} + +tr.state.unknown[href]:hover { + background-color: @colorUnknown; + color: white; +} + +tr.state.unknown.handled[href]:hover { + background-color: @colorUnknownHandled; +} + +tr.state.pending[href]:hover { + background-color: @colorPending; +} + +tr.state.invalid[href]:hover { + background-color: @colorInvalid; + color: white; +} + +tr.state.unreachable[href]:hover { + background-color: @colorUnreachable; +} + +tr.state.unreachable.handled[href]:hover { + background-color: @colorUnreachableHandled; +} + +tr.state[href]:hover td.state { + background-color: inherit !important; +} + +/* END of HOVER colors */ + +/* END of special tables and states */ + + +/* Generic colors */ + +a.critical { + color: @colorCritical; +} + +/* END of Generic colors */ + + +/* Generic box element */ + +.boxview a { + text-decoration: none; +} + +.boxview > div.box { + text-align: center; + vertical-align: top; + display: inline-block; + padding: 0.4em; + margin: 0.4em; + border: 1px solid #d9d9d9; + background: #eee; +} + +/* Box header */ +.boxview div.box.header { + padding-bottom: 0.5em; + margin-bottom: 0.5em; + border-bottom: 1px solid #888; +} + +.boxview div.box.header h2 { + margin-top: 0.1em; + margin-bottom: 0; + font-size: 0.8em; + border-bottom: none; + color: @colorTextDefault; +} + +.boxview div.box.header h2:first-child { + margin-top: 0.2em; + font-size: inherit; + color: @colorTextDefault; +} + +.boxview div.box.header h2 > a { + color: inherit; +} + +.boxview div.box.header h2 > a:hover { + text-decoration: underline; +} + +.boxview div.box.header h3 { + line-height: 1.5em; + font-size: 0.9em; + color: #555; +} + +/* Box body of contents */ +.boxview div.box.contents { + padding: 0.2em; +} + +.boxview div.box.contents table { + width: 100%; +} + +.boxview div.box.contents td { + width: 13em; + vertical-align: top; +} + +/* Box separator */ +.boxview div.box-separator:first-child { + border-top-width: 0; +} + +.boxview div.box-separator { + font-size: 0.8em; + padding: 0.4em 0 0.4em; + border: 1px solid #d9d9d9; + + font-weight: bold; + letter-spacing: 0.1em; +} + +/* Box entry */ +.boxview div.box.entry { + min-height: 2.7em; + margin: 0.2em; + font-size: 0.9em; + white-space: nowrap; + + color: @colorTextDefault; +} + +/* Any line of a box entry */ +.boxview div.box.entry a { + display: block; + + color: inherit; +} + +.boxview div.box.entry a:hover { + color: @colorTextDefault; +} + +/* First line of a box entry */ +.boxview div.box.entry a:first-child { + font-size: 1em; +} + +/* End of generic box element */ + + +/* Monitoring box element styles */ + +/* Host- and Servicegroup element styles */ + +div.box.entry.state_up, div.box.entry.state_ok { + border: 1px solid @colorOk; + border-left: 1em solid @colorOk; +} + +div.box.entry.state_pending { + border: 1px solid @colorPending; + border-left: 1em solid @colorPending; +} + +div.box.entry.state_down, div.box.entry.state_critical { + border: 1px solid @colorCritical; + border-left: 1em solid @colorCritical; + background-color: @colorCritical; + color: white; +} + +div.box.entry.state_down a:hover, div.box.entry.state_critical a:hover { + color: #dcdcdc; +} + +div.box.entry.state_warning { + border: 1px solid @colorWarning; + border-left: 1em solid @colorWarning; + background-color: @colorWarning; + color: white; +} + +div.box.entry.state_warning a:hover { + color: #dcdcdc; +} + +div.box.entry.state_unreachable, div.box.entry.state_unknown { + border: 1px solid @colorUnknown; + border-left: 1em solid @colorUnknown; + background-color: @colorUnknown; + color: white; +} + +div.box.entry.state_unreachable a:hover, div.box.entry.state_unknown a:hover { + color: #dcdcdc; +} + +div.box.entry.handled { + background-color: transparent; + color: inherit; +} + +div.box.entry.handled a:hover { + color: @colorTextDefault; +} + +/* Tactical overview element styles */ + +.tactical > .boxview > div.box { + min-height: 20em; + min-width: 12.1em; +} + +.tactical div.box.contents { + min-height: 14.5em; +} + +div.box.contents.zero { + min-width: 11.1em; + + background-color: transparent; +} + +div.box.contents.zero span { + font-weight: bold; + line-height: 2em; + + color: #666; +} + +div.box.contents.zero h3 { + margin: 0; + font-size: 12em; + line-height: 1em; + + color: #666; +} + +div.box.ok_hosts.state_up { + border: 5px solid @colorOk; +} + +div.box.ok_hosts.state_pending { + background-color: @colorPending; +} + +div.box.problem_hosts.state_down { + border: 5px solid @colorCritical; +} + +div.box.problem_hosts.state_down.handled { + background-color: @colorCriticalHandled; +} + +div.box.problem_hosts.state_unreachable { + background-color: @colorUnreachable; +} + +div.box.problem_hosts.state_unreachable.handled { + background-color: @colorUnreachableHandled; +} + +div.box.ok_hosts div.box.entry, div.box.problem_hosts div.box.entry { + min-width: 11.1em; +} + +div.box.monitoringfeatures div.box.contents { + padding: 0 2 0em; +} + +div.box.monitoringfeatures { + border: 5px solid #d9d9d9; +} + +div.box.monitoringfeatures div.box-separator { + color: white; + background-color: @colorOk; +} + +div.box.monitoringfeatures div.feature-highlight { + background-color: @colorCritical; +} + +div.box.monitoringfeatures a.feature-highlight { + font-weight: bold; +} + +div.box.hostservicechecks { + border: 5px solid #d9d9d9; +} + +/* Contactgroup element styles */ + +div.box.contactgroup { + width: 18em; + padding: 0.8em; +} + +div.box.contactgroup div.box.contents { + padding: 0.6em; +} + +div.box.contactgroup div.box.entry { + overflow: hidden; + clear: left; +} + +div.box.contactgroup div.box.entry img { + width: 80px; + height: 80px; + float: left; + +} + +div.box.contactgroup div.box.entry a { + margin-top: 0.4em; + + font-weight: bold; +} + +div.box.contactgroup div.box.entry p { + margin: 0.4em 0 0; +} + +div.circular { + margin-top: 0.5em; + margin-left: 2em; + margin-right: 1em; + width: 80px; + height: 80px; + float: left; + background-size: 100% 100%; +} + +/* End of monitoring box element styles */ + + +/* Monitoring pivot table styles */ + +div.pivot-pagination { + margin: 1em; + + table { + table-layout: fixed; + border-spacing: 1px; + border-collapse: separate; + border: 1px solid LightGrey; + border-radius: 0.3em; + + td { + width: 16px; + height: 16px; + padding: 0; + background-color: #fbfbfb; + + &:hover, &.active { + background-color: #e5e5e5; + } + + a { + width: 16px; + height: 16px; + display: block; + } + } + } +} + +table.joystick-pagination { + margin-top: -1.5em; + + td { + width: 1.25em; + height: 1.3em; + } +} + +table.pivot { + a { + text-decoration: none; + color: black; + + &:hover { + color: @colorTextDefault; + } + } + + & > thead { + th { + height: 6em; + + div { + margin-right: -1.5em; + padding-left: 1.3em; + + span { + width: 1.5em; + margin-right: 0.25em; + margin-top: 4em; + line-height: 2em; + white-space: nowrap; + display: block; + float: left; + + transform: rotate(-45deg); + transform-origin: bottom left; + -o-transform: rotate(-45deg); + -o-transform-origin: bottom left; + -ms-transform: rotate(-45deg); + -ms-transform-origin: bottom left; + -moz-transform: rotate(-45deg); + -moz-transform-origin: bottom left; + -webkit-transform: rotate(-45deg); + -webkit-transform-origin: bottom left; + //filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + + abbr { + border: 0; // Remove highlighting in firefox + font-size: 0.8em; + } + } + } + } + } + + & > tbody { + th { + padding: 0 14px 0 0; + white-space: nowrap; + + a { + font-size: 0.8em; + } + } + + td { + padding: 2px; + min-width: 1.5em; + line-height: 1.5em; + text-align: center; + + a { + width: 1.5em; + height: 1.5em; + display: block; + border-radius: 0.5em; + + &.state_ok { + background-color: @colorOk; + } + + &.state_pending { + background-color: @colorPending; + } + + &.state_warning { + background-color: @colorWarning; + + &.handled { + background-color: @colorWarningHandled; + } + } + + &.state_critical { + background-color: @colorCritical; + + &.handled { + background-color: @colorCriticalHandled; + } + } + + &.state_unknown { + background-color: @colorUnknown; + + &.handled { + background-color: @colorUnknownHandled; + } + } + } + } + } +} + +/* End of monitoring pivot table styles */ + +/* Monitoring timeline styles */ + +div.timeline-legend { + float: left; + padding: 0.5em; + border: 1px solid #d9d9d9; + background-color: #eee; + + h2 { + margin: 0; + margin-left: 0.5em; + line-height: 1.1em; + } + + & > span { + display: inline-block; + padding: 0.5em; + margin: 0.5em; + + span { + color: white; + font-size: 0.8em; + font-weight: bold; + white-space: nowrap; + } + } +} + +div.timeline { + div.timeframe { + height: 7em; + margin-bottom: 1em; + clear: left; + + span { + width: 8em; + margin-top: 2.3em; + margin-right: 1.5em; + display: block; + float: left; + text-align: center; + + a { + color: @colorTextDefault; + font-size: 0.8em; + font-weight: bold; + text-decoration: none; + white-space: nowrap; + + &:hover { + color: @colorTextDefault; + text-decoration: underline; + + } + } + } + + div.circle-box { + // width: inline-style; + height: 100%; + margin-right: 0.5em; + position: relative; + float: left; + + div.outer-circle { + // width: inline-style; + // height: inline-style; + position: absolute; + top: 50%; + // margin-top: inline-style; + + &.extrapolated { + border-width: 2px; + border-style: dotted; + //border-color: inline-style; + border-radius: 100%; + // background-color: inline-style; + } + + a.inner-circle { + // width: inline-style; + // height: inline-style; + display: block; + position: absolute; + top: 50%; + left: 50%; + // margin-top: inline-style; + // margin-left: inline-style; + border-radius: 100%; + // background-color: inline-style; + } + } + } + } + + hr { + border: 0; + height: 1px; + background-image: linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); + background-image: -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); + background-image: -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); + background-image: -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); + background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); + } +} + +/* End of monitoring timeline styles */ + +/* Monitoring groupsummary styles */ + +.dashboard table.groupview { + margin-top: 0; +} + +table.groupview { + width: 100%; + margin-top: 1em; + border-collapse: separate; + border-spacing: 0.1em; + + th { + font-size: 0.9em; + text-align: left; + white-space: nowrap; + } + + td { + &.groupname { + width: 60%; + + a { + color: inherit; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + &.total { + width: 10%; + } + + &.state { + width: 20%; + white-space: nowrap; + + &.change { + width: 10%; + text-align: center; + border-left-width: 1.5em; + border-left-style: solid; + padding: 0.3em 0.5em 0.3em 0.5em; + + strong { + font-size: 0.8em; + } + + &.ok { + border-color: @colorOk; + } + + &.pending { + border-color: @colorPending; + } + + &.warning { + border-color: @colorWarningHandled; + + &.unhandled { + color: white; + border-left-width: 0; + background-color: @colorWarning; + } + } + + &.unknown { + border-color: @colorUnknownHandled; + + &.unhandled { + color: white; + border-left-width: 0; + background-color: @colorUnknown; + } + } + + &.critical { + border-color: @colorCriticalHandled; + + &.unhandled { + color: white; + border-left-width: 0; + background-color: @colorCritical; + } + } + } + + span.state { + &.handled { + margin-right: 2px; + } + + a { + font-size: 0.9em; + color: white; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + } + } +} + +/* End of monitoring groupsummary styles */ + +/* compact table */ +table.statesummary { + text-align: left; + width: auto; + border-collapse: separate; + + tr.state td.state { + width: auto; + font-weight: bold; + } + + td { + font-size: 0.9em; + line-height: 1.2em; + padding-left: 0.2em; + margin: 0; + } + + td.state { + padding: 0.2em; + min-width: 75px; + font-size: 0.75em; + text-align: center; + } + + td.name { + font-weight: bold; + } + + td a { + color: inherit; + text-decoration: none; + } +} + +table.action .objectflags { + float: right; +} + +table.objectstate { + border-collapse: separate; + border-spacing: 1px; +} + +table.objectstate td { + padding-left: 1em; +} + +table.objectstate tr.state td.state { + width: 9em; + text-align: center; + padding-left: 0; + border-radius: 0; +} + +table.avp td.performance-data { + padding: 0.3em 0 0.3em 1em; +} + +table.perfdata { + min-width: 24em; + font-size: 0.9em; + width: 100%; +} + +table.perfdata th { + padding: 0; + text-align: left; + padding-right: 0.5em; +} + +table.perfdata td { + white-space: nowrap; + padding-right: 0.5em; +} diff --git a/modules/monitoring/public/js/module.js b/modules/monitoring/public/js/module.js index b3ba863ff..3421be2cf 100644 --- a/modules/monitoring/public/js/module.js +++ b/modules/monitoring/public/js/module.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ (function(Icinga) { @@ -45,7 +44,8 @@ /** * Prepare the timer to handle the timeline's infinite loading */ - if ($('div.timeline').length) { + var $timeline = $('div.timeline'); + if ($timeline.length && !$timeline.closest('.dashboard').length) { if (this.scrollCheckTimer === null) { this.scrollCheckTimer = this.module.icinga.timer.register( this.checkTimelinePosition, diff --git a/modules/monitoring/run.php b/modules/monitoring/run.php deleted file mode 100644 index 8f4ee1b67..000000000 --- a/modules/monitoring/run.php +++ /dev/null @@ -1,8 +0,0 @@ -registerHook( - 'TopBar', - 'Icinga\\Module\\Monitoring\\Web\\Hook\\TopBar' -); diff --git a/modules/monitoring/test/php/application/views/helpers/MonitoringFlagsTest.php b/modules/monitoring/test/php/application/views/helpers/MonitoringFlagsTest.php index f50b07893..3e8476db3 100644 --- a/modules/monitoring/test/php/application/views/helpers/MonitoringFlagsTest.php +++ b/modules/monitoring/test/php/application/views/helpers/MonitoringFlagsTest.php @@ -1,6 +1,5 @@ host_name = 'test'; - $hostMock->host_address = '1.1.1.1'; - - $helper = new Zend_View_Helper_ResolveMacros(); - $this->assertEquals($helper->resolveMacros('$HOSTNAME$', $hostMock), $hostMock->host_name); - $this->assertEquals($helper->resolveMacros('$HOSTADDRESS$', $hostMock), $hostMock->host_address); - } - - public function testServiceMacros() - { - $svcMock = Mockery::mock('service'); - $svcMock->host_name = 'test'; - $svcMock->host_address = '1.1.1.1'; - $svcMock->service_description = 'a service'; - - $helper = new Zend_View_Helper_ResolveMacros(); - $this->assertEquals($helper->resolveMacros('$HOSTNAME$', $svcMock), $svcMock->host_name); - $this->assertEquals($helper->resolveMacros('$HOSTADDRESS$', $svcMock), $svcMock->host_address); - $this->assertEquals($helper->resolveMacros('$SERVICEDESC$', $svcMock), $svcMock->service_description); - } - - public function testCustomvars() - { - $objectMock = Mockery::mock('object'); - $objectMock->customvars = array( - 'CUSTOMVAR' => 'test' - ); - - $helper = new Zend_View_Helper_ResolveMacros(); - $this->assertEquals($helper->resolveMacros('$CUSTOMVAR$', $objectMock), $objectMock->customvars['CUSTOMVAR']); - } - - public function testFaultyMacros() - { - $hostMock = Mockery::mock('host'); - $hostMock->host_name = 'test'; - $hostMock->customvars = array( - 'HOST' => 'te', - 'NAME' => 'st' - ); - - $helper = new Zend_View_Helper_ResolveMacros(); - $this->assertEquals( - $helper->resolveMacros('$$HOSTNAME$ $ HOSTNAME$ $HOST$NAME$', $hostMock), - '$test $ HOSTNAME$ teNAME$' - ); - } - - public function testMacrosWithSpecialCharacters() - { - $objectMock = Mockery::mock('object'); - $objectMock->customvars = array( - 'V€RY_SP3C|@L' => 'not too special!' - ); - - $helper = new Zend_View_Helper_ResolveMacros(); - $this->assertEquals( - $helper->resolveMacros('$V€RY_SP3C|@L$', $objectMock), - $objectMock->customvars['V€RY_SP3C|@L'] - ); - } -} diff --git a/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php b/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php new file mode 100644 index 000000000..e1b3595e3 --- /dev/null +++ b/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php @@ -0,0 +1,78 @@ +host_name = 'test'; + $hostMock->host_address = '1.1.1.1'; + + $this->assertEquals(Macro::resolveMacros('$HOSTNAME$', $hostMock), $hostMock->host_name); + $this->assertEquals(Macro::resolveMacros('$HOSTADDRESS$', $hostMock), $hostMock->host_address); + $this->assertEquals(Macro::resolveMacros('$host.name$', $hostMock), $hostMock->host_name); + $this->assertEquals(Macro::resolveMacros('$host.address$', $hostMock), $hostMock->host_address); + } + + public function testServiceMacros() + { + $svcMock = Mockery::mock('service'); + $svcMock->host_name = 'test'; + $svcMock->host_address = '1.1.1.1'; + $svcMock->service_description = 'a service'; + + $this->assertEquals(Macro::resolveMacros('$HOSTNAME$', $svcMock), $svcMock->host_name); + $this->assertEquals(Macro::resolveMacros('$HOSTADDRESS$', $svcMock), $svcMock->host_address); + $this->assertEquals(Macro::resolveMacros('$SERVICEDESC$', $svcMock), $svcMock->service_description); + $this->assertEquals(Macro::resolveMacros('$host.name$', $svcMock), $svcMock->host_name); + $this->assertEquals(Macro::resolveMacros('$host.address$', $svcMock), $svcMock->host_address); + $this->assertEquals(Macro::resolveMacros('$service.description$', $svcMock), $svcMock->service_description); + } + + public function testCustomvars() + { + $objectMock = Mockery::mock('object'); + $objectMock->customvars = array( + 'CUSTOMVAR' => 'test' + ); + + $this->assertEquals(Macro::resolveMacros('$CUSTOMVAR$', $objectMock), $objectMock->customvars['CUSTOMVAR']); + } + + public function testFaultyMacros() + { + $hostMock = Mockery::mock('host'); + $hostMock->host_name = 'test'; + $hostMock->customvars = array( + 'HOST' => 'te', + 'NAME' => 'st' + ); + + $this->assertEquals( + Macro::resolveMacros('$$HOSTNAME$ $ HOSTNAME$ $HOST$NAME$', $hostMock), + '$test $ HOSTNAME$ teNAME$' + ); + } + + public function testMacrosWithSpecialCharacters() + { + $objectMock = Mockery::mock('object'); + $objectMock->customvars = array( + 'V€RY_SP3C|@L' => 'not too special!' + ); + + $this->assertEquals( + Macro::resolveMacros('$V€RY_SP3C|@L$', $objectMock), + $objectMock->customvars['V€RY_SP3C|@L'] + ); + } +} diff --git a/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataSetTest.php b/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataSetTest.php index 3edd20857..27c1f8673 100644 --- a/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataSetTest.php +++ b/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataSetTest.php @@ -1,6 +1,5 @@ getBug() . ';' . $command->getParameterWithCarriageReturnAndLineFeed(); + } +} + + +/** + * Class Bug6088 + * + * Multi-line comments don't work + * + * @see https://dev.icinga.org/issues/6088 + */ +class Bug6088Test extends BaseTestCase +{ + public function testWhetherCommandParametersWithMultipleLinesAreProperlyEscaped() + { + $command = new Bug6088Command(); + $renderer = new Bug6088CommandFileCommandRenderer(); + $commandString = $renderer->render($command); + + $this->assertEquals( + 'SOLVE_BUG;6088;foo\r\nbar', + substr($commandString, strpos($commandString, ' ') + 1), + 'Command parameters with multiple lines are not properly escaped' + ); + } +} diff --git a/modules/monitoring/test/php/regression/Bug7043Test.php b/modules/monitoring/test/php/regression/Bug7043Test.php index 94a60a5b6..a63c1b314 100644 --- a/modules/monitoring/test/php/regression/Bug7043Test.php +++ b/modules/monitoring/test/php/regression/Bug7043Test.php @@ -1,4 +1,5 @@ assertEquals('backendName', $defaultBackend->getName(), 'Default backend has name set'); } -} \ No newline at end of file +} diff --git a/modules/setup/application/clicommands/ConfigCommand.php b/modules/setup/application/clicommands/ConfigCommand.php index 676572542..a4a5da12e 100644 --- a/modules/setup/application/clicommands/ConfigCommand.php +++ b/modules/setup/application/clicommands/ConfigCommand.php @@ -1,6 +1,5 @@ [options] + * icingacli setup config directory [options] * * OPTIONS: * - * --mode The access mode to use. Default is: 2770 - * --path The path to the configuration directory. If omitted the default is used. + * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb2] + * + * --mode= The access mode to use [2770] + * + * --group= Owner group for the configuration directory [icingaweb2] * * EXAMPLES: * - * icingacli setup config createDirectory apache - * icingacli setup config createDirectory apache --mode 2775 - * icingacli setup config createDirectory apache --path /some/path + * icingacli setup config directory + * + * icingacli setup config directory --mode=2775 --config=/opt/icingaweb2/etc */ - public function createDirectoryAction() + public function directoryAction() { - $group = $this->params->getStandalone(); - if ($group === null) { - $this->fail($this->translate('The `group\' argument is mandatory.')); - return false; + $configDir = trim($this->params->get('config', $this->app->getConfigDir())); + if (strlen($configDir) === 0) { + $this->fail($this->translate( + 'The argument --config expects a path to Icinga Web 2\'s configuration files' + )); } - $path = $this->params->get('path', $this->app->getConfigDir()); - if (file_exists($path)) { - printf($this->translate("Configuration directory already exists at: %s\n"), $path); - return true; + $group = trim($this->params->get('group', 'icingaweb2')); + if (strlen($group) === 0) { + $this->fail($this->translate( + 'The argument --group expects a owner group for the configuration directory' + )); } - $mode = octdec($this->params->get('mode', '2770')); - if (false === mkdir($path)) { - $this->fail(sprintf($this->translate('Unable to create path: %s'), $path)); - return false; + $mode = trim($this->params->get('mode', '2770')); + if (strlen($mode) === 0) { + $this->fail($this->translate( + 'The argument --mode expects an access mode for the configuration directory' + )); } - $old = umask(0); // Prevent $mode from being mangled by the system's umask ($old) - chmod($path, $mode); - umask($old); - - if (chgrp($path, $group) === false) { - $this->fail(sprintf($this->translate('Unable to change the group of "%s" to "%s".'), $path, $group)); - return false; + if (! file_exists($configDir) && ! @mkdir($configDir)) { + $e = error_get_last(); + $this->fail(sprintf( + $this->translate('Can\'t create configuration directory %s: %s'), + $configDir, + $e['message'] + )); } - printf($this->translate("Successfully created configuration directory at: %s\n"), $path); + if (! @chmod($configDir, octdec($mode))) { + $e = error_get_last(); + $this->fail(sprintf( + $this->translate('Can\'t change the mode of the configuration directory to %s: %s'), + $mode, + $e['message'] + )); + } + + if (! @chgrp($configDir, $group)) { + $e = error_get_last(); + $this->fail(sprintf( + $this->translate('Can\'t change the group of %s to %s: %s'), + $configDir, + $group, + $e['message'] + )); + } + + printf($this->translate('Successfully created configuration directory %s') . PHP_EOL, $configDir); } /** @@ -73,24 +94,23 @@ class ConfigCommand extends Command * * OPTIONS: * - * --path= The URL path to Icinga Web 2 [/icingaweb] + * --path= The URL path to Icinga Web 2 [/icingaweb2] * - * --root/--document-root= The directory from which the webserver will serve files [./public] + * --root|--document-root= The directory from which the webserver will serve files [/path/to/icingaweb2/public] * - * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb] + * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb2] * * --file= Write configuration to file [stdout] * - * * EXAMPLES: * - * icingacli setup config webserver apache + * icingacli setup config webserver apache * - * icingacli setup config webserver apache --path /icingaweb --document-root /usr/share/icingaweb/public --config=/etc/icingaweb + * icingacli setup config webserver apache --path=/icingaweb2 --document-root=/usr/share/icingaweb2/public --config=/etc/icingaweb2 * - * icingacli setup config webserver apache --file /etc/apache2/conf.d/icingaweb.conf + * icingacli setup config webserver apache --file=/etc/apache2/conf.d/icingaweb2.conf * - * icingacli setup config webserver nginx + * icingacli setup config webserver nginx */ public function webserverAction() { @@ -102,18 +122,20 @@ class ConfigCommand extends Command } catch (ProgrammingError $e) { $this->fail($this->translate('Unknown type') . ': ' . $type); } - $urlPath = $this->params->get('path', $webserver->getUrlPath()); - if (! is_string($urlPath) || strlen(trim($urlPath)) === 0) { + $urlPath = trim($this->params->get('path', $webserver->getUrlPath())); + if (strlen($urlPath) === 0) { $this->fail($this->translate('The argument --path expects a URL path')); } - $documentRoot = $this->params->get('root', $this->params->get('document-root', $webserver->getDocumentRoot())); - if (! is_string($documentRoot) || strlen(trim($documentRoot)) === 0) { + $documentRoot = trim( + $this->params->get('root', $this->params->get('document-root', $webserver->getDocumentRoot())) + ); + if (strlen($documentRoot) === 0) { $this->fail($this->translate( 'The argument --root/--document-root expects a directory from which the webserver will serve files' )); } - $configDir = $this->params->get('config', $webserver->getConfigDir()); - if (! is_string($documentRoot) || strlen(trim($documentRoot)) === 0) { + $configDir = trim($this->params->get('config', $webserver->getConfigDir())); + if (strlen($configDir) === 0) { $this->fail($this->translate( 'The argument --config expects a path to Icinga Web 2\'s configuration files' )); diff --git a/modules/setup/application/clicommands/TokenCommand.php b/modules/setup/application/clicommands/TokenCommand.php index ff0229cfe..83aa494e8 100644 --- a/modules/setup/application/clicommands/TokenCommand.php +++ b/modules/setup/application/clicommands/TokenCommand.php @@ -1,6 +1,5 @@ Path to Icinga Web 2's configuration files [/etc/icingaweb2] */ public function showAction() { - $token = file_get_contents($this->app->getConfigDir() . '/setup.token'); + $configDir = $this->params->get('config', $this->app->getConfigDir()); + if (! is_string($configDir) || strlen(trim($configDir)) === 0) { + $this->fail($this->translate( + 'The argument --config expects a path to Icinga Web 2\'s configuration files' + )); + } + + $token = file_get_contents($configDir . '/setup.token'); if (! $token) { $this->fail( $this->translate('Nothing to show. Please create a new setup token using the generateToken action.') @@ -43,24 +53,35 @@ class TokenCommand extends Command * * USAGE: * - * icingacli setup token create + * icingacli setup token create [options] + * + * OPTIONS: + * + * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb2] */ public function createAction() { + $configDir = $this->params->get('config', $this->app->getConfigDir()); + if (! is_string($configDir) || strlen(trim($configDir)) === 0) { + $this->fail($this->translate( + 'The argument --config expects a path to Icinga Web 2\'s configuration files' + )); + } + + $file = $configDir . '/setup.token'; + if (function_exists('openssl_random_pseudo_bytes')) { $token = bin2hex(openssl_random_pseudo_bytes(8)); } else { $token = substr(md5(mt_rand()), 16); } - $filepath = $this->app->getConfigDir() . '/setup.token'; - - if (false === file_put_contents($filepath, $token)) { - $this->fail(sprintf($this->translate('Cannot write setup token "%s" to disk.'), $filepath)); + if (false === file_put_contents($file, $token)) { + $this->fail(sprintf($this->translate('Cannot write setup token "%s" to disk.'), $file)); } - if (false === chmod($filepath, 0660)) { - $this->fail(sprintf($this->translate('Cannot change access mode of "%s" to %o.'), $filepath, 0660)); + if (! chmod($file, 0660)) { + $this->fail(sprintf($this->translate('Cannot change access mode of "%s" to %o.'), $file, 0660)); } printf($this->translate("The newly generated setup token is: %s\n"), $token); diff --git a/modules/setup/application/controllers/IndexController.php b/modules/setup/application/controllers/IndexController.php index 113886c88..eec43a737 100644 --- a/modules/setup/application/controllers/IndexController.php +++ b/modules/setup/application/controllers/IndexController.php @@ -1,11 +1,10 @@ setName('setup_admin_account'); - $this->setViewScript('form/setup-admin-account.phtml'); + $this->setTitle($this->translate('Administration', 'setup.page.title')); + $this->addDescription($this->translate( + 'Now it\'s time to configure your first administrative account or group for Icinga Web 2.' + )); } /** @@ -45,7 +59,7 @@ class AdminAccountPage extends Form * * @param array $config * - * @return self + * @return $this */ public function setResourceConfig(array $config) { @@ -54,11 +68,11 @@ class AdminAccountPage extends Form } /** - * Set the backend configuration to use + * Set the user backend configuration to use * * @param array $config * - * @return self + * @return $this */ public function setBackendConfig(array $config) { @@ -66,102 +80,58 @@ class AdminAccountPage extends Form return $this; } + /** + * Set the user group backend configuration to use + * + * @param array $config + * + * @return $this + */ + public function setGroupConfig(array $config = null) + { + $this->groupConfig = $config; + return $this; + } + /** * @see Form::createElements() */ public function createElements(array $formData) { $choices = array(); - if ($this->backendConfig['backend'] !== 'db') { - $choices['by_name'] = mt('setup', 'By Name', 'setup.admin'); - $this->addElement( - 'text', - 'by_name', - array( - 'required' => isset($formData['user_type']) && $formData['user_type'] === 'by_name', - 'value' => $this->getUsername(), - 'label' => mt('setup', 'Username'), - 'description' => mt( - 'setup', - 'Define the initial administrative account by providing a username that reflects' - . ' a user created later or one that is authenticated using external mechanisms' - ) - ) - ); - } + $choices['by_name'] = $this->translate('By Name', 'setup.admin'); + $choice = isset($formData['user_type']) ? $formData['user_type'] : 'by_name'; - if ($this->backendConfig['backend'] === 'db' || $this->backendConfig['backend'] === 'ldap') { - $users = $this->fetchUsers(); - if (false === empty($users)) { - $choices['existing_user'] = mt('setup', 'Existing User'); - $this->addElement( - 'select', - 'existing_user', - array( - 'required' => isset($formData['user_type']) && $formData['user_type'] === 'existing_user', - 'label' => mt('setup', 'Username'), - 'description' => sprintf( - mt( - 'setup', - 'Choose a user reported by the %s backend as the initial administrative account', - 'setup.admin' - ), - $this->backendConfig['backend'] === 'db' - ? mt('setup', 'database', 'setup.admin.authbackend') - : 'LDAP' - ), - 'multiOptions' => array_combine($users, $users) - ) - ); + if (in_array($this->backendConfig['backend'], array('ldap', 'msldap'))) { + $groups = $this->fetchGroups(); + if (! empty($groups)) { + $choices['user_group'] = $this->translate('User Group', 'setup.admin'); + } } + } else { + $choices['new_user'] = $this->translate('New User', 'setup.admin'); + $choice = isset($formData['user_type']) ? $formData['user_type'] : 'new_user'; } - if ($this->backendConfig['backend'] === 'db') { - $choices['new_user'] = mt('setup', 'New User'); - $required = isset($formData['user_type']) && $formData['user_type'] === 'new_user'; - $this->addElement( - 'text', - 'new_user', - array( - 'required' => $required, - 'label' => mt('setup', 'Username'), - 'description' => mt( - 'setup', - 'Enter the username to be used when creating an initial administrative account' - ) - ) - ); - $this->addElement( - 'password', - 'new_user_password', - array( - 'required' => $required, - 'label' => mt('setup', 'Password'), - 'description' => mt('setup', 'Enter the password to assign to the newly created account') - ) - ); - $this->addElement( - 'password', - 'new_user_2ndpass', - array( - 'required' => $required, - 'label' => mt('setup', 'Repeat password'), - 'description' => mt('setup', 'Please repeat the password given above to avoid typing errors'), - 'validators' => array( - array('identical', false, array('new_user_password')) - ) - ) - ); + if (in_array($this->backendConfig['backend'], array('db', 'ldap', 'msldap'))) { + $users = $this->fetchUsers(); + if (! empty($users)) { + $choices['existing_user'] = $this->translate('Existing User', 'setup.admin'); + } } if (count($choices) > 1) { $this->addElement( - 'radio', + 'select', 'user_type', array( 'required' => true, - 'multiOptions' => $choices + 'autosubmit' => true, + 'label' => $this->translate('Type Of Definition'), + 'description' => $this->translate('Choose how to define the desired account.'), + 'multiOptions' => $choices, + 'value' => $choice ) ); } else { @@ -175,30 +145,100 @@ class AdminAccountPage extends Form ); } - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'Administration', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) + if ($choice === 'by_name') { + $this->addElement( + 'text', + 'by_name', + array( + 'required' => true, + 'value' => $this->getUsername(), + 'label' => $this->translate('Username'), + 'description' => $this->translate( + 'Define the initial administrative account by providing a username that reflects' + . ' a user created later or one that is authenticated using external mechanisms.' + ) ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => tp( - 'Now it\'s time to configure your first administrative account for Icinga Web 2.' - . ' Please follow the instructions below:', - 'Now it\'s time to configure your first administrative account for Icinga Web 2.' - . ' Below are several options you can choose from. Select one and follow its instructions:', - count($choices) + ); + } + + if ($choice === 'user_group') { + $this->addElement( + 'select', + 'user_group', + array( + 'required' => true, + 'label' => $this->translate('Group Name'), + 'description' => $this->translate( + 'Choose a user group reported by the LDAP backend' + . ' to permit its members administrative access.', + 'setup.admin' + ), + 'multiOptions' => array_combine($groups, $groups) ) - ) - ); + ); + } + + if ($choice === 'existing_user') { + $this->addElement( + 'select', + 'existing_user', + array( + 'required' => true, + 'label' => $this->translate('Username'), + 'description' => sprintf( + $this->translate( + 'Choose a user reported by the %s backend as the initial administrative account.', + 'setup.admin' + ), + $this->backendConfig['backend'] === 'db' + ? $this->translate('database', 'setup.admin.authbackend') + : 'LDAP' + ), + 'multiOptions' => array_combine($users, $users) + ) + ); + } + + if ($choice === 'new_user') { + $this->addElement( + 'text', + 'new_user', + array( + 'required' => true, + 'label' => $this->translate('Username'), + 'description' => $this->translate( + 'Enter the username to be used when creating an initial administrative account.' + ) + ) + ); + $this->addElement( + 'password', + 'new_user_password', + array( + 'required' => true, + 'renderPassword' => true, + 'label' => $this->translate('Password'), + 'description' => $this->translate( + 'Enter the password to assign to the newly created account.' + ) + ) + ); + $this->addElement( + 'password', + 'new_user_2ndpass', + array( + 'required' => true, + 'renderPassword' => true, + 'label' => $this->translate('Repeat password'), + 'description' => $this->translate( + 'Please repeat the password given above to avoid typing errors.' + ), + 'validators' => array( + array('identical', false, array('new_user_password')) + ) + ) + ); + } } /** @@ -214,8 +254,8 @@ class AdminAccountPage extends Form return false; } - if ($data['user_type'] === 'new_user' && array_search($data['new_user'], $this->fetchUsers()) !== false) { - $this->getElement('new_user')->addError(mt('setup', 'Username already exists.')); + if ($data['user_type'] === 'new_user' && $this->hasUser($data['new_user'])) { + $this->getElement('new_user')->addError($this->translate('Username already exists.')); return false; } @@ -244,37 +284,131 @@ class AdminAccountPage extends Form } /** - * Return the names of all users this backend currently provides + * Return the names of all users the user backend currently provides * * @return array - * - * @throws LogicException In case the backend to fetch users from is not supported */ protected function fetchUsers() { - if ($this->backendConfig['backend'] === 'db') { - $backend = new DbUserBackend(ResourceFactory::createResource(new ConfigObject($this->resourceConfig))); - } elseif ($this->backendConfig['backend'] === 'ldap') { - $backend = new LdapUserBackend( - ResourceFactory::createResource(new ConfigObject($this->resourceConfig)), - $this->backendConfig['user_class'], - $this->backendConfig['user_name_attribute'], - $this->backendConfig['base_dn'] - ); - } else { - throw new LogicException( - sprintf( - 'Tried to fetch users from an unsupported authentication backend: %s', - $this->backendConfig['backend'] - ) - ); - } - try { - return $backend->listUsers(); - } catch (Exception $e) { + return $this + ->createUserBackend() + ->select(array('user_name')) + ->order('user_name', 'asc', true) + ->fetchColumn(); + } catch (Exception $_) { // No need to handle anything special here. Error means no users found. return array(); } } + + /** + * Return whether the user backend provides a user with the given name + * + * @param string $username + * + * @return bool + */ + protected function hasUser($username) + { + try { + return $this + ->createUserBackend() + ->select() + ->where('user_name', $username) + ->count() > 1; + } catch (Exception $_) { + return false; + } + } + + /** + * Create and return the user backend + * + * @return DbUserBackend|LdapUserBackend + */ + protected function createUserBackend() + { + $resourceConfig = new Config(); + $resourceConfig->setSection($this->resourceConfig['name'], $this->resourceConfig); + ResourceFactory::setConfig($resourceConfig); + + $config = new ConfigObject($this->backendConfig); + $config->resource = $this->resourceConfig['name']; + return UserBackend::create(null, $config); + } + + /** + * Return the names of all user groups the user group backend currently provides + * + * @return array + */ + protected function fetchGroups() + { + try { + return $this + ->createUserGroupBackend() + ->select(array('group_name')) + ->fetchColumn(); + } catch (Exception $_) { + // No need to handle anything special here. Error means no groups found. + return array(); + } + } + + /** + * Return whether the user group backend provides a user group with the given name + * + * @param string $groupname + * + * @return bool + */ + protected function hasGroup($groupname) + { + try { + return $this + ->createUserGroupBackend() + ->select() + ->where('group_name', $groupname) + ->count() > 1; + } catch (Exception $_) { + return false; + } + } + + /** + * Create and return the user group backend + * + * @return LdapUserGroupBackend + */ + protected function createUserGroupBackend() + { + $resourceConfig = new Config(); + $resourceConfig->setSection($this->resourceConfig['name'], $this->resourceConfig); + ResourceFactory::setConfig($resourceConfig); + + $backendConfig = new Config(); + $backendConfig->setSection($this->backendConfig['name'], array_merge( + $this->backendConfig, + array('resource' => $this->resourceConfig['name']) + )); + UserBackend::setConfig($backendConfig); + + if (empty($this->groupConfig)) { + $groupConfig = new ConfigObject(array( + 'backend' => $this->backendConfig['backend'], // _Should_ be "db" or "msldap" + 'resource' => $this->resourceConfig['name'], + 'user_backend' => $this->backendConfig['name'] // Gets ignored if 'backend' is "db" + )); + } else { + $groupConfig = new ConfigObject($this->groupConfig); + } + + $backend = UserGroupBackend::create(null, $groupConfig); + if (! $backend instanceof Selectable) { + throw new NotImplementedError('Unsupported, until #9772 has been resolved'); + } + + return $backend; + } } diff --git a/modules/setup/application/forms/AuthBackendPage.php b/modules/setup/application/forms/AuthBackendPage.php index fac2a7bbd..24ca72b8d 100644 --- a/modules/setup/application/forms/AuthBackendPage.php +++ b/modules/setup/application/forms/AuthBackendPage.php @@ -1,14 +1,15 @@ setName('setup_authentication_backend'); + $this->setTitle($this->translate('Authentication Backend', 'setup.page.title')); + $this->setValidatePartial(true); } /** @@ -35,84 +38,97 @@ class AuthBackendPage extends Form * * @param array $config * - * @return self + * @return $this */ public function setResourceConfig(array $config) { + $resourceConfig = new Config(); + $resourceConfig->setSection($config['name'], $config); + ResourceFactory::setConfig($resourceConfig); + $this->config = $config; return $this; } /** - * Return the resource configuration as Config object + * Create and add elements to this form * - * @return ConfigObject - */ - public function getResourceConfig() - { - return new ConfigObject($this->config); - } - - /** - * @see Form::createElements() + * @param array $formData */ public function createElements(array $formData) { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'Authentication Backend', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - - if ($this->config['type'] === 'db') { - $note = mt( - 'setup', - 'As you\'ve chosen to use a database for authentication all you need ' - . 'to do now is defining a name for your first authentication backend.' - ); - } elseif ($this->config['type'] === 'ldap') { - $note = mt( - 'setup', - 'Before you are able to authenticate using the LDAP connection defined earlier you need to' - . ' provide some more information so that Icinga Web 2 is able to locate account details.' - ); - } else { // if ($this->config['type'] === 'autologin' - $note = mt( - 'setup', - 'You\'ve chosen to authenticate using a web server\'s mechanism so it may be necessary' - . ' to adjust usernames before any permissions, restrictions, etc. are being applied.' - ); - } - - $this->addElement( - 'note', - 'description', - array('value' => $note) - ); - if (isset($formData['skip_validation']) && $formData['skip_validation']) { $this->addSkipValidationCheckbox(); } if ($this->config['type'] === 'db') { + $this->setRequiredCue(null); $backendForm = new DbBackendForm(); - $backendForm->createElements($formData)->removeElement('resource'); + $backendForm->setRequiredCue(null); + $backendForm->create($formData)->removeElement('resource'); + $this->addDescription($this->translate( + 'As you\'ve chosen to use a database for authentication all you need ' + . 'to do now is defining a name for your first authentication backend.' + )); } elseif ($this->config['type'] === 'ldap') { + $type = null; + if (! isset($formData['type']) && isset($formData['backend'])) { + $type = $formData['backend']; + $formData['type'] = $type; + } + $backendForm = new LdapBackendForm(); - $backendForm->createElements($formData)->removeElement('resource'); - } else { // $this->config['type'] === 'autologin' - $backendForm = new AutologinBackendForm(); - $backendForm->createElements($formData); + $backendForm->setResources(array($this->config['name'])); + $backendForm->create($formData); + $backendForm->getElement('resource')->setIgnore(true); + $this->addDescription($this->translate( + 'Before you are able to authenticate using the LDAP connection defined earlier you need to' + . ' provide some more information so that Icinga Web 2 is able to locate account details.' + )); + $this->addElement( + 'select', + 'type', + array( + 'ignore' => true, + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('Backend Type'), + 'description' => $this->translate( + 'The type of the resource being used for this authenticaton provider' + ), + 'multiOptions' => array( + 'ldap' => 'LDAP', + 'msldap' => 'ActiveDirectory' + ), + 'value' => $type + ) + ); + } else { // $this->config['type'] === 'external' + $backendForm = new ExternalBackendForm(); + $backendForm->create($formData); + $this->addDescription($this->translate( + 'You\'ve chosen to authenticate using a web server\'s mechanism so it may be necessary' + . ' to adjust usernames before any permissions, restrictions, etc. are being applied.' + )); } - $this->addElements($backendForm->getElements()); - $this->getElement('name')->setValue('icingaweb'); + $backendForm->getElement('name')->setValue('icingaweb2'); + $this->addSubForm($backendForm, 'backend_form'); + } + + /** + * Retrieve all form element values + * + * @param bool $suppressArrayNotation Ignored + * + * @return array + */ + public function getValues($suppressArrayNotation = false) + { + $values = parent::getValues(); + $values = array_merge($values, $values['backend_form']); + unset($values['backend_form']); + return $values; } /** @@ -124,12 +140,16 @@ class AuthBackendPage extends Form */ public function isValid($data) { - if (false === parent::isValid($data)) { + if (! parent::isValid($data)) { return false; } - if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) { - if ($this->config['type'] === 'ldap' && false === LdapBackendForm::isValidAuthenticationBackend($this)) { + if ($this->config['type'] === 'ldap' && (! isset($data['skip_validation']) || $data['skip_validation'] == 0)) { + $self = clone $this; + $self->getSubForm('backend_form')->getElement('resource')->setIgnore(false); + $inspection = UserBackendConfigForm::inspectUserBackend($self); + if ($inspection && $inspection->hasError()) { + $this->error($inspection->getError()); $this->addSkipValidationCheckbox(); return false; } @@ -138,6 +158,60 @@ class AuthBackendPage extends Form return true; } + /** + * Run the configured backend's inspection checks and show the result, if necessary + * + * This will only run any validation if the user pushed the 'backend_validation' button. + * + * @param array $formData + * + * @return bool + */ + public function isValidPartial(array $formData) + { + if (isset($formData['backend_validation']) && parent::isValid($formData)) { + $self = clone $this; + if (($resourceElement = $self->getSubForm('backend_form')->getElement('resource')) !== null) { + $resourceElement->setIgnore(false); + } + + $inspection = UserBackendConfigForm::inspectUserBackend($self); + if ($inspection !== null) { + $join = function ($e) use (& $join) { + return is_string($e) ? $e : join("\n", array_map($join, $e)); + }; + $this->addElement( + 'note', + 'inspection_output', + array( + 'order' => 0, + 'value' => '' . $this->translate('Validation Log') . "\n\n" + . join("\n", array_map($join, $inspection->toArray())), + 'decorators' => array( + 'ViewHelper', + array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')), + ) + ) + ); + + if ($inspection->hasError()) { + $this->warning(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $inspection->getError() + )); + return false; + } + } + + $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); + } + + return true; + } + /** * Add a checkbox to this form by which the user can skip the authentication validation */ @@ -147,11 +221,11 @@ class AuthBackendPage extends Form 'checkbox', 'skip_validation', array( - 'order' => 2, + 'order' => 0, 'ignore' => true, 'required' => true, - 'label' => mt('setup', 'Skip Validation'), - 'description' => mt('setup', 'Check this to not to validate authentication using this backend') + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate('Check this to not to validate authentication using this backend') ) ); } diff --git a/modules/setup/application/forms/AuthenticationPage.php b/modules/setup/application/forms/AuthenticationPage.php index 4b0f9bee7..370e4bb95 100644 --- a/modules/setup/application/forms/AuthenticationPage.php +++ b/modules/setup/application/forms/AuthenticationPage.php @@ -1,6 +1,5 @@ setRequiredCue(null); $this->setName('setup_authentication_type'); + $this->setTitle($this->translate('Authentication', 'setup.page.title')); + $this->addDescription($this->translate( + 'Please choose how you want to authenticate when accessing Icinga Web 2.' + . ' Configuring backend specific details follows in a later step.' + )); } /** @@ -25,45 +30,34 @@ class AuthenticationPage extends Form */ public function createElements(array $formData) { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'Authentication', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'setup', - 'Please choose how you want to authenticate when accessing Icinga Web 2.' - . ' Configuring backend specific details follows in a later step.' - ) - ) - ); + if (isset($formData['type']) && $formData['type'] === 'external' && !isset($_SERVER['REMOTE_USER'])) { + $this->info( + $this->translate( + 'You\'re currently not authenticated using any of the web server\'s authentication ' + . 'mechanisms. Make sure you\'ll configure such, otherwise you\'ll not be able to ' + . 'log into Icinga Web 2.' + ), + false + ); + } $backendTypes = array(); - if (Platform::extensionLoaded('mysql') || Platform::extensionLoaded('pgsql')) { - $backendTypes['db'] = t('Database'); + if (Platform::hasMysqlSupport() || Platform::hasPostgresqlSupport()) { + $backendTypes['db'] = $this->translate('Database'); } if (Platform::extensionLoaded('ldap')) { $backendTypes['ldap'] = 'LDAP'; } - $backendTypes['autologin'] = t('Autologin'); + $backendTypes['external'] = $this->translate('External'); $this->addElement( 'select', 'type', array( 'required' => true, - 'label' => mt('setup', 'Authentication Type'), - 'description' => mt('setup', 'The type of authentication to use when accessing Icinga Web 2'), + 'autosubmit' => true, + 'label' => $this->translate('Authentication Type'), + 'description' => $this->translate('The type of authentication to use when accessing Icinga Web 2'), 'multiOptions' => $backendTypes ) ); diff --git a/modules/setup/application/forms/DatabaseCreationPage.php b/modules/setup/application/forms/DatabaseCreationPage.php index ec4a77ee1..17cae3aca 100644 --- a/modules/setup/application/forms/DatabaseCreationPage.php +++ b/modules/setup/application/forms/DatabaseCreationPage.php @@ -1,6 +1,5 @@ setName('setup_database_creation'); + $this->setTitle($this->translate('Database Setup', 'setup.page.title')); + $this->addDescription($this->translate( + 'It seems that either the database you defined earlier does not yet exist and cannot be created' + . ' using the provided access credentials, the database does not have the required schema to be' + . ' operated by Icinga Web 2 or the provided access credentials do not have the sufficient ' + . 'permissions to access the database. Please provide appropriate access credentials to solve this.' + )); } /** @@ -47,7 +52,7 @@ class DatabaseCreationPage extends Form * * @param array $config * - * @return self + * @return $this */ public function setResourceConfig(array $config) { @@ -60,7 +65,7 @@ class DatabaseCreationPage extends Form * * @param array $privileges The privileges * - * @return self + * @return $this */ public function setDatabaseSetupPrivileges(array $privileges) { @@ -73,7 +78,7 @@ class DatabaseCreationPage extends Form * * @param array $privileges The privileges * - * @return self + * @return $this */ public function setDatabaseUsagePrivileges(array $privileges) { @@ -86,46 +91,25 @@ class DatabaseCreationPage extends Form */ public function createElements(array $formData) { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'Database Setup', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'setup', - 'It seems that either the database you defined earlier does not yet exist and cannot be created' - . ' using the provided access credentials or the database does not have the required schema to ' - . 'be operated by Icinga Web 2. Please provide appropriate access credentials to solve this.' - ) - ) - ); - $skipValidation = isset($formData['skip_validation']) && $formData['skip_validation']; $this->addElement( 'text', 'username', array( 'required' => false === $skipValidation, - 'label' => mt('setup', 'Username'), - 'description' => mt('setup', 'A user which is able to create databases and/or touch the database schema') + 'label' => $this->translate('Username'), + 'description' => $this->translate( + 'A user which is able to create databases and/or touch the database schema' + ) ) ); $this->addElement( 'password', 'password', array( - 'label' => mt('setup', 'Password'), - 'description' => mt('setup', 'The password for the database user defined above') + 'renderPassword' => true, + 'label' => $this->translate('Password'), + 'description' => $this->translate('The password for the database user defined above') ) ); @@ -172,7 +156,7 @@ class DatabaseCreationPage extends Form $db->connectToHost(); // Are we able to login on the server? } catch (PDOException $e) { // We are NOT able to login on the server.. - $this->addError($e->getMessage()); + $this->error($e->getMessage()); $this->addSkipValidationCheckbox(); return false; } @@ -181,8 +165,8 @@ class DatabaseCreationPage extends Form // In case we are connected the credentials filled into this // form need to be granted to create databases, users... if (false === $db->checkPrivileges($this->databaseSetupPrivileges)) { - $this->addError( - mt('setup', 'The provided credentials cannot be used to create the database and/or the user.') + $this->error( + $this->translate('The provided credentials cannot be used to create the database and/or the user.') ); $this->addSkipValidationCheckbox(); return false; @@ -190,9 +174,8 @@ class DatabaseCreationPage extends Form // ...and to grant all required usage privileges to others if (false === $db->isGrantable($this->databaseUsagePrivileges)) { - $this->addError(sprintf( - mt( - 'setup', + $this->error(sprintf( + $this->translate( 'The provided credentials cannot be used to grant all required privileges to the login "%s".' ), $this->config['username'] @@ -213,11 +196,10 @@ class DatabaseCreationPage extends Form 'checkbox', 'skip_validation', array( - 'order' => 2, + 'order' => 0, 'required' => true, - 'label' => mt('setup', 'Skip Validation'), - 'description' => mt( - 'setup', + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate( 'Check this to not to validate the ability to login and required privileges' ) ) diff --git a/modules/setup/application/forms/DbResourcePage.php b/modules/setup/application/forms/DbResourcePage.php index 4a6535324..6da2d03fc 100644 --- a/modules/setup/application/forms/DbResourcePage.php +++ b/modules/setup/application/forms/DbResourcePage.php @@ -1,6 +1,5 @@ setName('setup_db_resource'); + $this->setTitle($this->translate('Database Resource', 'setup.page.title')); + $this->setValidatePartial(true); } /** @@ -35,28 +35,6 @@ class DbResourcePage extends Form 'value' => 'db' ) ); - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'Database Resource', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'setup', - 'Now please configure your database resource. Note that the database itself does not need to' - . ' exist at this time as it is going to be created once the wizard is about to be finished.' - ) - ) - ); if (isset($formData['skip_validation']) && $formData['skip_validation']) { $this->addSkipValidationCheckbox(); @@ -74,14 +52,6 @@ class DbResourcePage extends Form $resourceForm = new DbResourceForm(); $this->addElements($resourceForm->createElements($formData)->getElements()); $this->getElement('name')->setValue('icingaweb_db'); - $this->addElement( - 'hidden', - 'prefix', - array( - 'required' => true, - 'value' => 'icingaweb_' - ) - ); } /** @@ -102,7 +72,7 @@ class DbResourcePage extends Form $db = new DbTool($this->getValues()); $db->checkConnectivity(); } catch (PDOException $e) { - $this->addError($e->getMessage()); + $this->error($e->getMessage()); $this->addSkipValidationCheckbox(); return false; } @@ -111,6 +81,38 @@ class DbResourcePage extends Form return true; } + /** + * Check whether it's possible to connect to the database server + * + * This will only run the check if the user pushed the 'backend_validation' button. + * + * @param array $formData + * + * @return bool + */ + public function isValidPartial(array $formData) + { + if (isset($formData['backend_validation']) && parent::isValid($formData)) { + try { + $db = new DbTool($this->getValues()); + $db->checkConnectivity(); + } catch (PDOException $e) { + $this->warning(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $e->getMessage() + )); + return false; + } + + $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); + } + + return true; + } + /** * Add a checkbox to the form by which the user can skip the connection validation */ @@ -121,8 +123,10 @@ class DbResourcePage extends Form 'skip_validation', array( 'required' => true, - 'label' => mt('setup', 'Skip Validation'), - 'description' => mt('setup', 'Check this to not to validate connectivity with the given database server') + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate( + 'Check this to not to validate connectivity with the given database server' + ) ) ); } diff --git a/modules/setup/application/forms/GeneralConfigPage.php b/modules/setup/application/forms/GeneralConfigPage.php index 309c1784d..922b1428f 100644 --- a/modules/setup/application/forms/GeneralConfigPage.php +++ b/modules/setup/application/forms/GeneralConfigPage.php @@ -1,11 +1,11 @@ setName('setup_general_config'); + $this->setTitle($this->translate('Application Configuration', 'setup.page.title')); + $this->addDescription($this->translate( + 'Now please adjust all application and logging related configuration options to fit your needs.' + )); } /** @@ -25,29 +29,13 @@ class GeneralConfigPage extends Form */ public function createElements(array $formData) { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'Application Configuration', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'setup', - 'Now please adjust all application and logging related configuration options to fit your needs.' - ) - ) - ); + $appConfigForm = new ApplicationConfigForm(); + $appConfigForm->createElements($formData); + $appConfigForm->removeElement('global_module_path'); + $appConfigForm->removeElement('global_config_resource'); + $this->addElements($appConfigForm->getElements()); - $loggingForm = new LoggingConfigForm(); - $this->addElements($loggingForm->createElements($formData)->getElements()); + $loggingConfigForm = new LoggingConfigForm(); + $this->addElements($loggingConfigForm->createElements($formData)->getElements()); } } diff --git a/modules/setup/application/forms/LdapDiscoveryConfirmPage.php b/modules/setup/application/forms/LdapDiscoveryConfirmPage.php index f76cbee07..acb2cd9af 100644 --- a/modules/setup/application/forms/LdapDiscoveryConfirmPage.php +++ b/modules/setup/application/forms/LdapDiscoveryConfirmPage.php @@ -1,6 +1,5 @@ setName('setup_ldap_discovery_confirm'); + $this->setTitle($this->translate('LDAP Discovery Results', 'setup.page.title')); } /** @@ -45,7 +45,7 @@ EOT; * * @param array $config * - * @return self + * @return $this */ public function setResourceConfig(array $config) { @@ -78,27 +78,10 @@ EOT; $html = str_replace('{user_attribute}', $backend['user_name_attribute'], $html); $html = str_replace('{user_class}', $backend['user_class'], $html); - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'LDAP Discovery Results', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => sprintf( - mt('setup', 'The following directory service has been found on domain "%s":'), - $this->config['domain'] - ) - ) - ); + $this->addDescription(sprintf( + $this->translate('The following directory service has been found on domain "%s".'), + $this->config['domain'] + )); $this->addElement( 'note', @@ -119,7 +102,7 @@ EOT; 'confirm', array( 'value' => '1', - 'label' => mt('setup', 'Use this configuration?') + 'label' => $this->translate('Use this configuration?') ) ); } diff --git a/modules/setup/application/forms/LdapDiscoveryPage.php b/modules/setup/application/forms/LdapDiscoveryPage.php index 8c61eb379..4a825d7f1 100644 --- a/modules/setup/application/forms/LdapDiscoveryPage.php +++ b/modules/setup/application/forms/LdapDiscoveryPage.php @@ -1,10 +1,12 @@ setName('setup_ldap_discovery'); + $this->setTitle($this->translate('LDAP Discovery', 'setup.page.title')); + $this->addDescription($this->translate( + 'You can use this page to discover LDAP or ActiveDirectory servers ' . + ' for authentication. If you don\' want to execute a discovery, just skip this step.' + )); } /** @@ -32,42 +39,15 @@ class LdapDiscoveryPage extends Form */ public function createElements(array $formData) { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'LDAP Discovery', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'setup', - 'You can use this page to discover LDAP or ActiveDirectory servers ' . - ' for authentication. If you don\' want to execute a discovery, just skip this step.' - ) - ) - ); - $discoveryForm = new LdapDiscoveryForm(); $this->addElements($discoveryForm->createElements($formData)->getElements()); - $this->getElement('domain')->setRequired( - isset($formData['skip_validation']) === false || ! $formData['skip_validation'] - ); $this->addElement( 'checkbox', 'skip_validation', array( - 'required' => true, - 'label' => mt('setup', 'Skip'), - 'description' => mt('setup', 'Do not discover LDAP servers and enter all settings manually.') + 'label' => $this->translate('Skip'), + 'description' => $this->translate('Do not discover LDAP servers and enter all settings manually.') ) ); } @@ -84,17 +64,27 @@ class LdapDiscoveryPage extends Form if (false === parent::isValid($data)) { return false; } - if ($data['skip_validation']) { + if (isset($data['skip_validation']) && $data['skip_validation']) { return true; } - if (isset($data['domain'])) { - $this->discovery = Discovery::discoverDomain($data['domain']); - if ($this->discovery->isSuccess()) { - return true; + if (isset($data['domain']) && $data['domain']) { + try { + $this->discovery = Discovery::discoverDomain($data['domain']); + if ($this->discovery->isSuccess()) { + return true; + } + } catch (Exception $e) { } + + $this->error( + sprintf($this->translate('Could not find any LDAP servers on the domain "%s".'), $data['domain']) + ); + } else { + $labeller = new ErrorLabeller(array('element' => $this->getElement('domain'))); + $this->getElement('domain')->addError($labeller->translate(Zend_Validate_NotEmpty::IS_EMPTY)); } - $this->addError(sprintf(t('Could not find any LDAP servers on the domain "%s".'), $data['domain'])); + return false; } diff --git a/modules/setup/application/forms/LdapResourcePage.php b/modules/setup/application/forms/LdapResourcePage.php index 49bed69c3..aacfcb207 100644 --- a/modules/setup/application/forms/LdapResourcePage.php +++ b/modules/setup/application/forms/LdapResourcePage.php @@ -1,10 +1,10 @@ setName('setup_ldap_resource'); + $this->setTitle($this->translate('LDAP Resource', 'setup.page.title')); + $this->addDescription($this->translate( + 'Now please configure your AD/LDAP resource. This will later ' + . 'be used to authenticate users logging in to Icinga Web 2.' + )); + $this->setValidatePartial(true); } /** @@ -33,28 +39,6 @@ class LdapResourcePage extends Form 'value' => 'ldap' ) ); - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'LDAP Resource', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt( - 'setup', - 'Now please configure your AD/LDAP resource. This will later ' - . 'be used to authenticate users logging in to Icinga Web 2.' - ) - ) - ); if (isset($formData['skip_validation']) && $formData['skip_validation']) { $this->addSkipValidationCheckbox(); @@ -83,12 +67,14 @@ class LdapResourcePage extends Form */ public function isValid($data) { - if (false === parent::isValid($data)) { + if (! parent::isValid($data)) { return false; } - if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) { - if (false === LdapResourceForm::isValidResource($this)) { + if (! isset($data['skip_validation']) || $data['skip_validation'] == 0) { + $inspection = ResourceConfigForm::inspectResource($this); + if ($inspection !== null && $inspection->hasError()) { + $this->error($inspection->getError()); $this->addSkipValidationCheckbox(); return false; } @@ -97,6 +83,55 @@ class LdapResourcePage extends Form return true; } + /** + * Run the configured backend's inspection checks and show the result, if necessary + * + * This will only run any validation if the user pushed the 'backend_validation' button. + * + * @param array $formData + * + * @return bool + */ + public function isValidPartial(array $formData) + { + if (isset($formData['backend_validation']) && parent::isValid($formData)) { + $inspection = ResourceConfigForm::inspectResource($this); + if ($inspection !== null) { + $join = function ($e) use (& $join) { + return is_string($e) ? $e : join("\n", array_map($join, $e)); + }; + $this->addElement( + 'note', + 'inspection_output', + array( + 'order' => 0, + 'value' => '' . $this->translate('Validation Log') . "\n\n" + . join("\n", array_map($join, $inspection->toArray())), + 'decorators' => array( + 'ViewHelper', + array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')), + ) + ) + ); + + if ($inspection->hasError()) { + $this->warning(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $inspection->getError() + )); + return false; + } + } + + $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); + } + + return true; + } + /** * Add a checkbox to the form by which the user can skip the connection validation */ @@ -107,9 +142,8 @@ class LdapResourcePage extends Form 'skip_validation', array( 'required' => true, - 'label' => mt('setup', 'Skip Validation'), - 'description' => mt( - 'setup', + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate( 'Check this to not to validate connectivity with the given directory service' ) ) diff --git a/modules/setup/application/forms/ModulePage.php b/modules/setup/application/forms/ModulePage.php index 5e8927dbc..83990193f 100644 --- a/modules/setup/application/forms/ModulePage.php +++ b/modules/setup/application/forms/ModulePage.php @@ -1,25 +1,15 @@ setName('setup_modules'); $this->setViewScript('form/setup-modules.phtml'); - $this->session = Session::getSession()->getNamespace(get_class($this)); $this->modulePaths = array(); if (($appModulePath = realpath(Icinga::app()->getApplicationDir() . '/../modules')) !== false) { @@ -37,84 +26,24 @@ class ModulePage extends Form } } - public function setPageData(array $pageData) + public function createElements(array $formData) { - $this->pageData = $pageData; - return $this; - } - - public function handleRequest(Request $request = null) - { - $isPost = strtolower($request->getMethod()) === 'post'; - if ($isPost && $this->wasSent($request->getPost())) { - if (($newModule = $request->getPost('module')) !== null) { - $this->setCurrentModule($newModule); - $this->getResponse()->redirectAndExit($this->getRedirectUrl()); - } else { - // The user submitted this form but with the parent wizard's navigation - // buttons so it's now up to the parent wizard to handle the request.. - } - } else { - $wizard = $this->getCurrentWizard(); - $wizardPage = $wizard->getCurrentPage(); - - $wizard->handleRequest($request); - if ($isPost && $wizard->isFinished() && $wizardPage->wasSent($request->getPost())) { - $wizards = $this->getWizards(); - - $newModule = null; - foreach ($wizards as $moduleName => $moduleWizard) { - if (false === $moduleWizard->isFinished()) { - $newModule = $moduleName; - } - } - - if ($newModule === null) { - // In case all module wizards were completed just pick the first one again - reset($wizards); - $newModule = key($wizards); - } - - $this->setCurrentModule($newModule); - } + foreach ($this->getModules() as $module) { + $this->addElement( + 'checkbox', + $module->getName(), + array( + 'required' => true, + 'description' => $module->getDescription(), + 'label' => ucfirst($module->getName()), + 'value' => $module->getName() === 'monitoring' ? 1 : 0, + 'decorators' => array('ViewHelper') + ) + ); } } - public function clearSession() - { - $this->session->clear(); - foreach ($this->getWizards() as $wizard) { - $wizard->clearSession(); - } - } - - public function setCurrentModule($moduleName) - { - if (false === array_key_exists($moduleName, $this->getWizards())) { - throw new InvalidArgumentException(sprintf('Module "%s" does not provide a setup wizard', $moduleName)); - } - - $this->session->currentModule = $moduleName; - } - - public function getCurrentModule() - { - $moduleName = $this->session->get('currentModule'); - if ($moduleName === null) { - $moduleName = key($this->getWizards()); - $this->setCurrentModule($moduleName); - } - - return $moduleName; - } - - public function getCurrentWizard() - { - $wizards = $this->getWizards(); - return $wizards[$this->getCurrentModule()]; - } - - public function getModules() + protected function getModules() { if ($this->modules !== null) { return $this->modules; @@ -125,37 +54,39 @@ class ModulePage extends Form $moduleManager = Icinga::app()->getModuleManager(); $moduleManager->detectInstalledModules($this->modulePaths); foreach ($moduleManager->listInstalledModules() as $moduleName) { - $this->modules[] = $moduleManager->loadModule($moduleName)->getModule($moduleName); + if ($moduleName !== 'setup') { + $this->modules[$moduleName] = $moduleManager->loadModule($moduleName)->getModule($moduleName); + } } return $this->modules; } - public function getWizards() + public function getCheckedModules() { - if ($this->wizards !== null) { - return $this->wizards; - } else { - $this->wizards = array(); - } + $modules = $this->getModules(); - foreach ($this->getModules() as $module) { - if ($module->providesSetupWizard()) { - $this->wizards[$module->getName()] = $module->getSetupWizard(); + $checked = array(); + foreach ($this->getElements() as $name => $element) { + if (array_key_exists($name, $modules) && $element->isChecked()) { + $checked[$name] = $modules[$name]; } } - $this->mergePageData($this->wizards); - return $this->wizards; + return $checked; } - protected function mergePageData(array $wizards) + public function getModuleWizards() { - foreach ($wizards as $wizard) { - $wizardPageData = & $wizard->getPageData(); - foreach ($this->pageData as $pageName => $pageData) { - $wizardPageData[$pageName] = $pageData; + $checked = $this->getCheckedModules(); + + $wizards = array(); + foreach ($checked as $name => $module) { + if ($module->providesSetupWizard()) { + $wizards[$name] = $module->getSetupWizard(); } } + + return $wizards; } } diff --git a/modules/setup/application/forms/PreferencesPage.php b/modules/setup/application/forms/PreferencesPage.php deleted file mode 100644 index 9a6f31448..000000000 --- a/modules/setup/application/forms/PreferencesPage.php +++ /dev/null @@ -1,82 +0,0 @@ -setName('setup_preferences_type'); - } - - /** - * Pre-select "db" as preference backend and add a hint to the select element - * - * @return self - */ - public function showDatabaseNote() - { - $this->getElement('type') - ->setValue('db') - ->setDescription( - mt( - 'setup', - 'Note that choosing "Database" causes Icinga Web 2 to use the same database as for authentication.' - ) - ); - return $this; - } - - /** - * @see Form::createElements() - */ - public function createElements(array $formData) - { - $this->addElement( - 'note', - 'title', - array( - 'value' => mt('setup', 'Preferences', 'setup.page.title'), - 'decorators' => array( - 'ViewHelper', - array('HtmlTag', array('tag' => 'h2')) - ) - ) - ); - $this->addElement( - 'note', - 'description', - array( - 'value' => mt('setup', 'Please choose how Icinga Web 2 should store user preferences.') - ) - ); - - $storageTypes = array(); - $storageTypes['ini'] = t('File System (INI Files)'); - if (Platform::extensionLoaded('mysql') || Platform::extensionLoaded('pgsql')) { - $storageTypes['db'] = t('Database'); - } - $storageTypes['null'] = t('Don\'t Store Preferences'); - - $this->addElement( - 'select', - 'type', - array( - 'required' => true, - 'label' => t('User Preference Storage Type'), - 'multiOptions' => $storageTypes - ) - ); - } -} diff --git a/modules/setup/application/forms/RequirementsPage.php b/modules/setup/application/forms/RequirementsPage.php index ec4405459..2237a04ed 100644 --- a/modules/setup/application/forms/RequirementsPage.php +++ b/modules/setup/application/forms/RequirementsPage.php @@ -1,11 +1,10 @@ requirements = $requirements; + $this->wizard = $wizard; return $this; } /** - * Return the requirements to list + * Return the wizard * - * @return Requirements + * @return SetupWizard */ - public function getRequirements() + public function getWizard() { - return $this->requirements; + return $this->wizard; } /** - * Validate the given form data and check whether the requirements are fulfilled + * Validate the given form data and check whether the wizard's requirements are fulfilled * * @param array $data The data to validate * @@ -64,6 +63,6 @@ class RequirementsPage extends Form return false; } - return $this->requirements->fulfilled(); + return $this->wizard->getRequirements()->fulfilled(); } } diff --git a/modules/setup/application/forms/SummaryPage.php b/modules/setup/application/forms/SummaryPage.php index 14ad93c56..c680eaf7c 100644 --- a/modules/setup/application/forms/SummaryPage.php +++ b/modules/setup/application/forms/SummaryPage.php @@ -1,9 +1,9 @@ setName('setup_summary'); + if ($this->getName() === $this->filterName(get_class($this))) { + throw new LogicException( + 'When utilizing ' . get_class($this) . ' it is required to set a unique name by using the form options' + ); + } + $this->setViewScript('form/setup-summary.phtml'); } @@ -59,7 +64,7 @@ class SummaryPage extends Form * * @param array $summary * - * @return self + * @return $this */ public function setSummary(array $summary) { diff --git a/modules/setup/application/forms/UserGroupBackendPage.php b/modules/setup/application/forms/UserGroupBackendPage.php new file mode 100644 index 000000000..272949d54 --- /dev/null +++ b/modules/setup/application/forms/UserGroupBackendPage.php @@ -0,0 +1,147 @@ +setName('setup_usergroup_backend'); + $this->setTitle($this->translate('User Group Backend', 'setup.page.title')); + $this->addDescription($this->translate( + 'To allow Icinga Web 2 to associate users and groups, you\'ll need to provide some further information' + . ' about the LDAP Connection that is already going to be used to locate account details.' + )); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return $this + */ + public function setResourceConfig(array $config) + { + $this->resourceConfig = $config; + return $this; + } + + /** + * Set the user backend configuration to use + * + * @param array $config + * + * @return $this + */ + public function setBackendConfig(array $config) + { + $this->backendConfig = $config; + return $this; + } + + /** + * Return the resource configuration as Config object + * + * @return Config + */ + protected function createResourceConfiguration() + { + $config = new Config(); + $config->setSection($this->resourceConfig['name'], $this->resourceConfig); + return $config; + } + + /** + * Return the user backend configuration as Config object + * + * @return Config + */ + protected function createBackendConfiguration() + { + $config = new Config(); + $backendConfig = $this->backendConfig; + $backendConfig['resource'] = $this->resourceConfig['name']; + $config->setSection($this->backendConfig['name'], $backendConfig); + return $config; + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + // LdapUserGroupBackendForm requires these factories to provide valid configurations + ResourceFactory::setConfig($this->createResourceConfiguration()); + UserBackend::setConfig($this->createBackendConfiguration()); + + $backendForm = new LdapUserGroupBackendForm(); + $formData['type'] = 'ldap'; + $backendForm->create($formData); + $backendForm->getElement('name')->setValue('icingaweb2'); + $this->addSubForm($backendForm, 'backend_form'); + + $backendForm->addElement( + 'hidden', + 'resource', + array( + 'required' => true, + 'value' => $this->resourceConfig['name'], + 'decorators' => array('ViewHelper') + ) + ); + $backendForm->addElement( + 'hidden', + 'user_backend', + array( + 'required' => true, + 'value' => $this->backendConfig['name'], + 'decorators' => array('ViewHelper') + ) + ); + } + + /** + * Retrieve all form element values + * + * @param bool $suppressArrayNotation Ignored + * + * @return array + */ + public function getValues($suppressArrayNotation = false) + { + $values = parent::getValues(); + $values = array_merge($values, $values['backend_form']); + unset($values['backend_form']); + return $values; + } +} diff --git a/modules/setup/application/forms/WelcomePage.php b/modules/setup/application/forms/WelcomePage.php index 5da607683..bc4c40b9f 100644 --- a/modules/setup/application/forms/WelcomePage.php +++ b/modules/setup/application/forms/WelcomePage.php @@ -1,6 +1,5 @@ setRequiredCue(null); $this->setName('setup_welcome'); $this->setViewScript('form/setup-welcome.phtml'); } @@ -32,9 +32,8 @@ class WelcomePage extends Form 'token', array( 'required' => true, - 'label' => mt('setup', 'Setup Token'), - 'description' => mt( - 'setup', + 'label' => $this->translate('Setup Token'), + 'description' => $this->translate( 'For security reasons we need to ensure that you are permitted to run this wizard.' . ' Please provide a token by following the instructions below.' ), diff --git a/modules/setup/application/views/scripts/form/setup-admin-account.phtml b/modules/setup/application/views/scripts/form/setup-admin-account.phtml deleted file mode 100644 index ac060526a..000000000 --- a/modules/setup/application/views/scripts/form/setup-admin-account.phtml +++ /dev/null @@ -1,74 +0,0 @@ -getElement('user_type'); -$showRadioBoxes = strpos(strtolower(get_class($radioElem)), 'radio') !== false; - -?> -
    - getElement('title'); ?> - getElement('description'); ?> -getElement('by_name')) !== null): ?> -
    -
    - -
    - -
    - -
    - -
    - -getElement('existing_user')) !== null): ?> -
    -
    - -
    - -
    - -
    - -
    - -getElement('new_user')) !== null): ?> -
    -
    - - getElement('new_user_password'); ?> - getElement('new_user_2ndpass'); ?> -
    - -
    - -
    - -
    - - - - - getElement($form->getTokenElementName()); ?> - getElement($form->getUidElementName()); ?> -
    - getElement(Wizard::BTN_NEXT); - $btn->setAttrib('class', 'double'); - $btn->setAttrib('tabindex', -1); - echo $btn; - ?> - getElement(Wizard::BTN_PREV); ?> - getElement(Wizard::BTN_NEXT); ?> -
    - \ No newline at end of file diff --git a/modules/setup/application/views/scripts/form/setup-modules.phtml b/modules/setup/application/views/scripts/form/setup-modules.phtml index a352803d7..8d8e9ecf9 100644 --- a/modules/setup/application/views/scripts/form/setup-modules.phtml +++ b/modules/setup/application/views/scripts/form/setup-modules.phtml @@ -3,41 +3,18 @@ use Icinga\Web\Wizard; ?> -
    -

    -

    -
    - getElement($form->getTokenElementName()); ?> - getElement($form->getUidElementName()); ?> -
      - - getModules() as $module): ?> - providesSetupWizard()): ?> -
    • - getName() === $form->getCurrentModule(); ?> - - getSetupWizard()->isFinished()): ?> - icon('ok', mt('setup', 'Completed', 'setup.modules.wizard.state')); ?> - - - -
    • - - -
    - - -

    - -

    +
    +

    translate('Modules', 'setup.page.title'); ?>

    +

    translate('The following modules were found in your Icinga Web 2 installation. To enable and configure a module, just tick it and click "Next".'); ?>

    +getElements() as $element): ?> + getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, $form->getTokenElementName(), $form->getUidElementName()))): ?> +
    +

    + + +
    -
    -
    - getCurrentWizard()->getForm()->render(); ?> -
    - + getElement($form->getTokenElementName()); ?> getElement($form->getUidElementName()); ?>
    diff --git a/modules/setup/application/views/scripts/form/setup-requirements.phtml b/modules/setup/application/views/scripts/form/setup-requirements.phtml index ca2556b95..fbd2c692a 100644 --- a/modules/setup/application/views/scripts/form/setup-requirements.phtml +++ b/modules/setup/application/views/scripts/form/setup-requirements.phtml @@ -1,44 +1,38 @@ getRequirements(); ?> -
    +
    statusSummary->hosts_not_processing_event_handlers): ?> -
    - - translate('%d hosts disabled'), $this->statusSummary->hosts_not_processing_event_handlers); ?> - + array('host_event_handler_enabled' => 0), + array( + 'class' => 'feature-highlight', + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is not processing any event handlers', + 'List %u hosts which are not processing any event handlers', + $this->statusSummary->hosts_not_processing_event_handlers + ), + $this->statusSummary->hosts_not_processing_event_handlers + ) + ) + ); ?> -
    - - translate('All hosts enabled'); ?> - + array('host_event_handler_enabled' => 1), + array('title' => $this->translate( + 'List all hosts, which are processing event handlers entirely' + )) + ); ?>
    +
    statusSummary->services_not_processing_event_handlers): ?> -
    - - translate('%d services disabled'), $this->statusSummary->services_not_processing_event_handlers); ?> - + array('service_event_handler_enabled' => 0), + array( + 'class' => 'feature-highlight', + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is not processing any event handlers', + 'List %u services which are not processing any event handlers', + $this->statusSummary->services_not_processing_event_handlers + ), + $this->statusSummary->services_not_processing_event_handlers + ) + ) + ); ?> -
    - - translate('All services enabled'); ?> - + array('service_event_handler_enabled' => 1), + array('title' => $this->translate( + 'List all services, which are processing event handlers entirely' + )) + ); ?>
    - - - - - - - +

    Icinga Web 2

    +getWizard()->getRequirements(true); ?> +getWizard()->getPage('setup_modules')->getModuleWizards() as $moduleName => $wizard): ?> +

    translate('Module'); ?>

    +getRequirements(); ?> - - - - - - -

    title; ?>

    description; ?>message; ?>
    -
    - -
    -
    getElement($form->getTokenElementName()); ?> getElement($form->getUidElementName()); ?> -
    +
    getElement(Wizard::BTN_PREV); ?> getElement(Wizard::BTN_NEXT); - if (false === $requirements->fulfilled()) { + if (! $form->getWizard()->getRequirements()->fulfilled()) { $btn->setAttrib('disabled', 1); } echo $btn; ?> +
    + translate('You may also need to restart the web-server for the changes to take effect!'); ?> + qlink( + $this->translate('Refresh'), + null, + null, + array( + 'class' => 'button-like', + 'title' => $title, + 'aria-label' => sprintf($this->translate('Refresh the page; %s'), $title) + ) + ); ?> +
    - + \ No newline at end of file diff --git a/modules/setup/application/views/scripts/form/setup-summary.phtml b/modules/setup/application/views/scripts/form/setup-summary.phtml index 714416833..e8053a897 100644 --- a/modules/setup/application/views/scripts/form/setup-summary.phtml +++ b/modules/setup/application/views/scripts/form/setup-summary.phtml @@ -4,9 +4,8 @@ use Icinga\Web\Wizard; ?>

    translate( + 'You\'ve configured %1$s successfully. You can review the changes supposed to be made before setting it up.' . ' Make sure that everything is correct (Feel free to navigate back to make any corrections!) so' . ' that you can start using %1$s right after it has successfully been set up.' ), @@ -21,7 +20,7 @@ use Icinga\Web\Wizard;

    -
    + getElement($form->getTokenElementName()); ?> getElement($form->getUidElementName()); ?>
    diff --git a/modules/setup/application/views/scripts/form/setup-welcome.phtml b/modules/setup/application/views/scripts/form/setup-welcome.phtml index 6c8228541..585353a79 100644 --- a/modules/setup/application/views/scripts/form/setup-welcome.phtml +++ b/modules/setup/application/views/scripts/form/setup-welcome.phtml @@ -5,22 +5,54 @@ use Icinga\Application\Config; use Icinga\Application\Platform; use Icinga\Web\Wizard; +$phpUser = Platform::getPhpUser(); $configDir = Icinga::app()->getConfigDir(); $setupTokenPath = rtrim($configDir, '/') . '/setup.token'; $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli'); +$groupadd = null; +$usermod = null; +if (! (false === ($distro = Platform::getLinuxDistro(1)) || $distro === 'linux')) { + foreach (array( + 'groupadd -r icingaweb2' => array( + 'redhat', 'rhel', 'centos', 'fedora', + 'suse', 'sles', 'sled', 'opensuse' + ), + 'addgroup --system icingaweb2' => array('debian', 'ubuntu') + ) as $groupadd_ => $distros) { + if (in_array($distro, $distros)) { + $groupadd = $groupadd_; + break; + } + } + + foreach (array( + 'usermod -a -G icingaweb2 apache' => array( + 'redhat', 'rhel', 'centos', 'fedora' + ), + 'usermod -A icingaweb2 wwwrun' => array( + 'suse', 'sles', 'sled', 'opensuse' + ), + 'usermod -a -G icingaweb2 www-data' => array( + 'debian', 'ubuntu' + ) + ) as $usermod_ => $distros) { + if (in_array($distro, $distros)) { + $usermod = $usermod_; + break; + } + } +} ?>
    -

    +

    translate('Welcome to the configuration of Icinga Web 2!') ?>

    -

    translate( 'You\'ve already completed the configuration of Icinga Web 2. Note that most of your configuration' . ' files will be overwritten in case you\'ll re-configure Icinga Web 2 using this wizard!' ); ?>

    -

    translate( 'This wizard will guide you through the configuration of Icinga Web 2. Once completed and successfully' . ' finished you are able to log in and to explore all the new and stunning features!' ); ?>

    @@ -34,30 +66,40 @@ $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
    -
    - Generating a New Setup Token -
    +

    Generating a New Setup Token

    translate( 'To run this wizard a user needs to authenticate using a token which is usually' . ' provided to him by an administrator who\'d followed the instructions below.' ); ?>

    -

    +

    translate('In any case, make sure that all of the following applies to your environment:'); ?>

    +
      +
    • translate('A system group called "icingaweb2" exists'); ?>
    • + +
    • translate('The user "%s" is a member of the system group "icingaweb2"'), $phpUser); ?>
    • + +
    • translate('Your webserver\'s user is a member of the system group "icingaweb2"'); ?>
    • + +
    +
    - setup config createDirectory ; + escape($groupadd . ';') ?> + escape($usermod . ';') ?> +
    + +

    translate('If you\'ve got the IcingaCLI installed you can do the following:'); ?>

    +
    + setup config directory --group icingaweb2; setup token create;
    -

    +

    translate('In case the IcingaCLI is missing you can create the token manually:'); ?>

    - su && mkdir -m 2770 ; - head -c 12 /dev/urandom | base64 | tee ; - chmod 0660 ; + su translate(''); ?> -c "mkdir -m 2770 ; chgrp icingaweb2 ; head -c 12 /dev/urandom | base64 | tee ; chmod 0660 ;";

    ' . mt('setup', 'Icinga Web 2 documentation') . '' // TODO: Add link to iw2 docs which points to the installation topic + $this->translate('Please see the %s for an extensive description on how to access and use this wizard.'), + '' . $this->translate('Icinga Web 2 documentation') . '' // TODO: Add link to iw2 docs which points to the installation topic ); ?>

    diff --git a/modules/setup/application/views/scripts/index/index.phtml b/modules/setup/application/views/scripts/index/index.phtml index 9af37be7c..ea0b61883 100644 --- a/modules/setup/application/views/scripts/index/index.phtml +++ b/modules/setup/application/views/scripts/index/index.phtml @@ -4,7 +4,7 @@ use Icinga\Web\Notification; $pages = $wizard->getPages(); $finished = isset($success); -$configPages = array_slice($pages, 2, count($pages) - 4, true); +$configPages = array_slice($pages, 3, count($pages) - 1, true); $currentPos = array_search($wizard->getCurrentPage(), $pages, true); list($configPagesLeft, $configPagesRight) = array_chunk($configPages, count($configPages) / 2, true); @@ -19,7 +19,7 @@ $maxProgress = @max(array_keys(array_filter( $notifications = Notification::getInstance(); if ($notifications->hasMessages()) { - foreach ($notifications->getMessages() as $m) { + foreach ($notifications->popMessages() as $m) { echo '
  • ' . $this->escape($m->message) . '
  • '; } } @@ -30,7 +30,7 @@ if ($notifications->hasMessages()) { img('img/logo_icinga_big.png'); ?>
    -

    +

    translate('Welcome', 'setup.progress'); ?>

    0 ? 'complete' : ( $maxProgress > 0 ? 'visited' : 'active' ); ?> @@ -41,33 +41,49 @@ if ($notifications->hasMessages()) {
    -

    +

    translate('Modules', 'setup.progress'); ?>

    1 ? ' complete' : ( $maxProgress > 1 ? ' visited' : ( $currentPos === 1 ? ' active' : '' ) ); ?> + + + + +
    +
    +
    +

    translate('Requirements', 'setup.progress'); ?>

    + 2 ? ' complete' : ( + $maxProgress > 2 ? ' visited' : ( + $currentPos === 2 ? ' active' : '' + ) + ); ?>
    -
    -

    +
    +

    translate('Configuration', 'setup.progress'); ?>

    $page): ?> 1 ? ' active' : '') + $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '') ); ?> -
    +
    @@ -78,7 +94,7 @@ if ($notifications->hasMessages()) {
    @@ -87,16 +103,19 @@ if ($notifications->hasMessages()) { $page): ?> 1 ? ' active' : '') + $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '') ); ?>
    -
    +
    @@ -105,27 +124,7 @@ if ($notifications->hasMessages()) {
    -

    - count($pages) - 2 ? ' complete' : ( - $maxProgress > count($pages) - 2 ? ' visited' : ($currentPos === count($pages) - 2 ? ' active' : '') - ); ?> - - - - -
    -
    -
    -

    - - - - - -
    -
    -
    -

    +

    translate('Finish', 'setup.progress'); ?>

    @@ -142,4 +141,4 @@ if ($notifications->hasMessages()) { render('index/parts/wizard.phtml'); ?> - \ No newline at end of file + diff --git a/modules/setup/application/views/scripts/index/parts/finish.phtml b/modules/setup/application/views/scripts/index/parts/finish.phtml index 94217017d..a56c07c3a 100644 --- a/modules/setup/application/views/scripts/index/parts/finish.phtml +++ b/modules/setup/application/views/scripts/index/parts/finish.phtml @@ -1,26 +1,33 @@
    -
    - - - - -
    - - - - - + +

    translate('Congratulations! Icinga Web 2 has been successfully set up.'); ?>

    + +

    translate('Sorry! Failed to set up Icinga Web 2 successfully.'); ?>

    + +
    -

    + qlink( + $this->translate('Login to Icinga Web 2'), + 'authentication/login', + null, + array( + 'class' => 'button-like login', + 'title' => $this->translate('Show the login page of Icinga Web 2') + ) + ); ?> -

    - -
    -
    - - - - + qlink( + $this->translate('Back'), + null, + null, + array( + 'class' => 'button-like', + 'title' => $this->translate('Show previous wizard-page') + ) + ); ?>
    +
    \ No newline at end of file diff --git a/modules/setup/library/Setup/Exception/SetupException.php b/modules/setup/library/Setup/Exception/SetupException.php index da5eed17c..49d0a6f9c 100644 --- a/modules/setup/library/Setup/Exception/SetupException.php +++ b/modules/setup/library/Setup/Exception/SetupException.php @@ -1,6 +1,5 @@ optional = false; + $this->descriptions = array(); + + foreach ($options as $key => $value) { + $setMethod = 'set' . ucfirst($key); + $addMethod = 'add' . ucfirst($key); + if (method_exists($this, $setMethod)) { + $this->$setMethod($value); + } elseif (method_exists($this, $addMethod)) { + $this->$addMethod($value); + } else { + throw LogicException('No setter found for option key: ' . $key); + } + } + } + + /** + * Set the state of this requirement + * + * @param bool $state + * + * @return Requirement + */ + public function setState($state) + { + $this->state = (bool) $state; + return $this; + } + + /** + * Return the state of this requirement + * + * Evaluates the requirement in case there is no state set yet. + * + * @return int + */ + public function getState() + { + if ($this->state === null) { + $this->state = $this->evaluate(); + } + + return $this->state; + } + + /** + * Set a descriptive text for this requirement's current state + * + * @param string $text + * + * @return Requirement + */ + public function setStateText($text) + { + $this->stateText = $text; + return $this; + } + + /** + * Return a descriptive text for this requirement's current state + * + * @return string + */ + public function getStateText() + { + return $this->stateText; + } + + /** + * Add a description for this requirement + * + * @param string $description + * + * @return Requirement + */ + public function addDescription($description) + { + $this->descriptions[] = $description; + return $this; + } + + /** + * Return the descriptions of this wizard + * + * @return array + */ + public function getDescriptions() + { + return $this->descriptions; + } + + /** + * Set the title for this requirement + * + * @param string $title + * + * @return Requirement + */ + public function setTitle($title) + { + $this->title = $title; + return $this; + } + + /** + * Return the title of this requirement + * + * In case there is no title set the alias is returned instead. + * + * @return string + */ + public function getTitle() + { + if ($this->title === null) { + return $this->getAlias(); + } + + return $this->title; + } + + /** + * Set the condition for this requirement + * + * @param mixed $condition + * + * @return Requirement + */ + public function setCondition($condition) + { + $this->condition = $condition; + return $this; + } + + /** + * Return the condition of this requirement + * + * @return mixed + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Set whether this requirement is optional + * + * @param bool $state + * + * @return Requirement + */ + public function setOptional($state = true) + { + $this->optional = (bool) $state; + return $this; + } + + /** + * Return whether this requirement is optional + * + * @return bool + */ + public function isOptional() + { + return $this->optional; + } + + /** + * Set the alias to display the condition with in a human readable way + * + * @param string $alias + * + * @return Requirement + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * Return the alias to display the condition with in a human readable way + * + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * Evaluate this requirement and return whether it is fulfilled + * + * @return bool + */ + abstract protected function evaluate(); + + /** + * Return whether the given requirement equals this one + * + * @param Requirement $requirement + * + * @return bool + */ + public function equals(Requirement $requirement) + { + if ($requirement instanceof static) { + return $this->getCondition() === $requirement->getCondition(); + } + + return false; + } +} diff --git a/modules/setup/library/Setup/Requirement/ClassRequirement.php b/modules/setup/library/Setup/Requirement/ClassRequirement.php new file mode 100644 index 000000000..e0f25cf4e --- /dev/null +++ b/modules/setup/library/Setup/Requirement/ClassRequirement.php @@ -0,0 +1,28 @@ +getCondition(); + if (Platform::classExists($classNameOrPath)) { + $this->setStateText(sprintf( + mt('setup', 'The %s is available.', 'setup.requirement.class'), + $this->getAlias() ?: $classNameOrPath . ' ' . mt('setup', 'class', 'setup.requirement.class') + )); + return true; + } else { + $this->setStateText(sprintf( + mt('setup', 'The %s is missing.', 'setup.requirement.class'), + $this->getAlias() ?: $classNameOrPath . ' ' . mt('setup', 'class', 'setup.requirement.class') + )); + return false; + } + } +} diff --git a/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php b/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php new file mode 100644 index 000000000..3404717db --- /dev/null +++ b/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php @@ -0,0 +1,42 @@ +getCondition(); + if (file_exists($path)) { + $readable = is_readable($path); + if ($readable && is_writable($path)) { + $this->setStateText(sprintf(mt('setup', 'The directory %s is read- and writable.'), $path)); + return true; + } else { + $this->setStateText(sprintf( + $readable + ? mt('setup', 'The directory %s is not writable.') + : mt('setup', 'The directory %s is not readable.'), + $path + )); + return false; + } + } else { + $this->setStateText(sprintf(mt('setup', 'The directory %s does not exist.'), $path)); + return false; + } + } +} diff --git a/modules/setup/library/Setup/Requirement/OSRequirement.php b/modules/setup/library/Setup/Requirement/OSRequirement.php new file mode 100644 index 000000000..ff185bb3c --- /dev/null +++ b/modules/setup/library/Setup/Requirement/OSRequirement.php @@ -0,0 +1,27 @@ +getCondition())); + } + + return $title; + } + + protected function evaluate() + { + $phpOS = Platform::getOperatingSystemName(); + $this->setStateText(sprintf(mt('setup', 'You are running PHP on a %s system.'), ucfirst($phpOS))); + return strtolower($phpOS) === strtolower($this->getCondition()); + } +} diff --git a/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php b/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php new file mode 100644 index 000000000..670c988e4 --- /dev/null +++ b/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php @@ -0,0 +1,22 @@ +getCondition(); + $configValue = Platform::getPhpConfig($configDirective); + $this->setStateText( + $configValue + ? sprintf(mt('setup', 'The PHP config `%s\' is set to "%s".'), $configDirective, $configValue) + : sprintf(mt('setup', 'The PHP config `%s\' is not defined.'), $configDirective) + ); + return is_bool($value) ? $configValue == $value : $configValue === $value; + } +} diff --git a/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php b/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php new file mode 100644 index 000000000..6581797ce --- /dev/null +++ b/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php @@ -0,0 +1,42 @@ +getAlias()) { + if ($title === null) { + $title = $this->getCondition(); + } + + return sprintf(mt('setup', 'PHP Module: %s'), $title); + } + + return $title; + } + + protected function evaluate() + { + $moduleName = $this->getCondition(); + if (Platform::extensionLoaded($moduleName)) { + $this->setStateText(sprintf( + mt('setup', 'The PHP module %s is available.'), + $this->getAlias() ?: $moduleName + )); + return true; + } else { + $this->setStateText(sprintf( + mt('setup', 'The PHP module %s is missing.'), + $this->getAlias() ?: $moduleName + )); + return false; + } + } +} diff --git a/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php b/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php new file mode 100644 index 000000000..d6ca5f189 --- /dev/null +++ b/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php @@ -0,0 +1,28 @@ +setStateText(sprintf(mt('setup', 'You are running PHP version %s.'), $phpVersion)); + list($operator, $requiredVersion) = $this->getCondition(); + return version_compare($phpVersion, $requiredVersion, $operator); + } +} diff --git a/modules/setup/library/Setup/RequirementSet.php b/modules/setup/library/Setup/RequirementSet.php new file mode 100644 index 000000000..8bb018d7b --- /dev/null +++ b/modules/setup/library/Setup/RequirementSet.php @@ -0,0 +1,333 @@ +optional = $optional; + $this->requirements = array(); + $this->setMode($mode ?: static::MODE_AND); + } + + /** + * Set the state of this set + * + * @param bool $state + * + * @return RequirementSet + */ + public function setState($state) + { + $this->state = (bool) $state; + return $this; + } + + /** + * Return the state of this set + * + * Alias for RequirementSet::fulfilled(true). + * + * @return bool + */ + public function getState() + { + return $this->fulfilled(true); + } + + /** + * Set whether this set of requirements should be optional + * + * @param bool $state + * + * @return RequirementSet + */ + public function setOptional($state = true) + { + $this->optional = (bool) $state; + return $this; + } + + /** + * Return whether this set of requirements is optional + * + * @return bool + */ + public function isOptional() + { + return $this->optional; + } + + /** + * Set the mode by which to evaluate the requirements + * + * @param int $mode + * + * @return RequirementSet + * + * @throws LogicException In case the given mode is invalid + */ + public function setMode($mode) + { + if ($mode !== static::MODE_AND && $mode !== static::MODE_OR) { + throw new LogicException(sprintf('Invalid mode %u given.'), $mode); + } + + $this->mode = $mode; + return $this; + } + + /** + * Return the mode by which the requirements are evaluated + * + * @return int + */ + public function getMode() + { + return $this->mode; + } + + /** + * Register a requirement + * + * @param Requirement $requirement The requirement to add + * + * @return RequirementSet + */ + public function add(Requirement $requirement) + { + $merged = false; + foreach ($this->requirements as $knownRequirement) { + if ($knownRequirement instanceof Requirement && $requirement->equals($knownRequirement)) { + $knownRequirement->setOptional($requirement->isOptional()); + foreach ($requirement->getDescriptions() as $description) { + $knownRequirement->addDescription($description); + } + + $merged = true; + break; + } + } + + if (! $merged) { + $this->requirements[] = $requirement; + } + + return $this; + } + + /** + * Return all registered requirements + * + * @return array + */ + public function getAll() + { + return $this->requirements; + } + + /** + * Register the given set of requirements + * + * @param RequirementSet $set The set to register + * + * @return RequirementSet + */ + public function merge(RequirementSet $set) + { + if ($this->getMode() === $set->getMode() && $this->isOptional() === $set->isOptional()) { + foreach ($set->getAll() as $requirement) { + if ($requirement instanceof static) { + $this->merge($requirement); + } else { + $this->add($requirement); + } + } + } else { + $this->requirements[] = $set; + } + + return $this; + } + + /** + * Return whether all requirements can successfully be evaluated based on the current mode + * + * In case this is a optional set of requirements (and $force is false), true is returned immediately. + * + * @param bool $force Whether to ignore the optionality of a set or single requirement + * + * @return bool + */ + public function fulfilled($force = false) + { + $state = $this->isOptional(); + if (! $force && $state) { + return true; + } + + if (! $force && $this->state !== null) { + return $this->state; + } elseif ($force && $this->forcedState !== null) { + return $this->forcedState; + } + + $self = $this->requirements; + foreach ($self as $requirement) { + if ($requirement->getState()) { + $state = true; + if ($this->getMode() === static::MODE_OR) { + break; + } + } elseif ($force || !$requirement->isOptional()) { + $state = false; + if ($this->getMode() === static::MODE_AND) { + break; + } + } + } + + if ($force) { + return $this->forcedState = $state; + } + + return $this->state = $state; + } + + /** + * Return whether the current element represents a nested set of requirements + * + * @return bool + */ + public function hasChildren() + { + $current = $this->current(); + return $current instanceof static; + } + + /** + * Return a iterator for the current nested set of requirements + * + * @return RecursiveIterator + */ + public function getChildren() + { + return $this->current(); + } + + /** + * Rewind the iterator to its first element + */ + public function rewind() + { + reset($this->requirements); + } + + /** + * Return whether the current iterator position is valid + * + * @return bool + */ + public function valid() + { + return $this->key() !== null; + } + + /** + * Return the current element in the iteration + * + * @return Requirement|RequirementSet + */ + public function current() + { + return current($this->requirements); + } + + /** + * Return the position of the current element in the iteration + * + * @return int + */ + public function key() + { + return key($this->requirements); + } + + /** + * Advance the iterator to the next element + */ + public function next() + { + next($this->requirements); + } + + /** + * Return this set of requirements rendered as HTML + * + * @return string + */ + public function __toString() + { + $renderer = new RequirementsRenderer($this); + return (string) $renderer; + } +} diff --git a/modules/setup/library/Setup/Requirements.php b/modules/setup/library/Setup/Requirements.php deleted file mode 100644 index 4b3d411cb..000000000 --- a/modules/setup/library/Setup/Requirements.php +++ /dev/null @@ -1,159 +0,0 @@ -requirements[] = $requirement; - return $this; - } - - /** - * Return all registered requirements - * - * @return array - */ - public function getAll() - { - return $this->requirements; - } - - /** - * Return an iterator of all registered requirements - * - * @return ArrayIterator - */ - public function getIterator() - { - return new ArrayIterator($this->getAll()); - } - - /** - * Register an optional requirement - * - * @param string $title - * @param string $description - * @param bool $state - * @param string $message - * - * @return self - */ - public function addOptional($title, $description, $state, $message) - { - $this->add((object) array( - 'title' => $title, - 'message' => $message, - 'description' => $description, - 'state' => (bool) $state ? static::STATE_OK : static::STATE_OPTIONAL - )); - return $this; - } - - /** - * Register a mandatory requirement - * - * @param string $title - * @param string $description - * @param bool $state - * @param string $message - * - * @return self - */ - public function addMandatory($title, $description, $state, $message) - { - $this->add((object) array( - 'title' => $title, - 'message' => $message, - 'description' => $description, - 'state' => (bool) $state ? static::STATE_OK : static::STATE_MANDATORY - )); - return $this; - } - - /** - * Register the given requirements - * - * @param Requirements $requirements The requirements to register - * - * @return self - */ - public function merge(Requirements $requirements) - { - foreach ($requirements->getAll() as $requirement) { - $this->add($requirement); - } - - return $this; - } - - /** - * Make all registered requirements being optional - * - * @return self - */ - public function allOptional() - { - foreach ($this->getAll() as $requirement) { - if ($requirement->state === static::STATE_MANDATORY) { - $requirement->state = static::STATE_OPTIONAL; - } - } - - return $this; - } - - /** - * Return whether all mandatory requirements are fulfilled - * - * @return bool - */ - public function fulfilled() - { - foreach ($this->getAll() as $requirement) { - if ($requirement->state === static::STATE_MANDATORY) { - return false; - } - } - - return true; - } -} diff --git a/modules/setup/library/Setup/RequirementsRenderer.php b/modules/setup/library/Setup/RequirementsRenderer.php new file mode 100644 index 000000000..8ad3ef5f6 --- /dev/null +++ b/modules/setup/library/Setup/RequirementsRenderer.php @@ -0,0 +1,64 @@ +tags[] = '
      '; + } + + public function endIteration() + { + $this->tags[] = '
    '; + } + + public function beginChildren() + { + $this->tags[] = '
  • '; + $currentSet = $this->getSubIterator(); + $state = $currentSet->getState() ? 'fulfilled' : ($currentSet->isOptional() ? 'not-available' : 'missing'); + $this->tags[] = '
      '; + } + + public function endChildren() + { + $this->tags[] = '
    '; + $this->tags[] = '
  • '; + } + + public function render() + { + foreach ($this as $requirement) { + $this->tags[] = '
  • '; + $this->tags[] = '

    ' . $requirement->getTitle() . '

    '; + $this->tags[] = '
    '; + $descriptions = $requirement->getDescriptions(); + if (count($descriptions) > 1) { + $this->tags[] = '
      '; + foreach ($descriptions as $d) { + $this->tags[] = '
    • ' . $d . '
    • '; + } + $this->tags[] = '
    '; + } elseif (! empty($descriptions)) { + $this->tags[] = $descriptions[0]; + } + $this->tags[] = '
    '; + $this->tags[] = '
    ' . $requirement->getStateText() . '
    '; + $this->tags[] = '
  • '; + } + + return implode("\n", $this->tags); + } + + public function __toString() + { + return $this->render(); + } +} diff --git a/modules/setup/library/Setup/Setup.php b/modules/setup/library/Setup/Setup.php index ec5117c9a..fa508ed35 100644 --- a/modules/setup/library/Setup/Setup.php +++ b/modules/setup/library/Setup/Setup.php @@ -1,6 +1,5 @@ steps as $step) { - $reports[] = $step->getReport(); + $report = $step->getReport(); + if (! empty($report)) { + $reports[] = $report; + } } return $reports; diff --git a/modules/setup/library/Setup/SetupWizard.php b/modules/setup/library/Setup/SetupWizard.php index 754207276..1ce948dc1 100644 --- a/modules/setup/library/Setup/SetupWizard.php +++ b/modules/setup/library/Setup/SetupWizard.php @@ -1,6 +1,5 @@ createAccount(); } - $success &= $this->defineInitialAdmin(); + $success &= $this->createRolesIni(); return $success; } @@ -50,11 +49,9 @@ class AuthenticationStep extends Step } try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('authentication.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('authentication.ini')) + ->saveIni(); } catch (Exception $e) { $this->authIniError = $e; return false; @@ -64,20 +61,28 @@ class AuthenticationStep extends Step return true; } - protected function defineInitialAdmin() + protected function createRolesIni() { - $config = array(); - $config['admins'] = array( - 'users' => $this->data['adminAccountData']['username'], - 'permission' => '*' - ); + if (isset($this->data['adminAccountData']['username'])) { + $config = array( + 'users' => $this->data['adminAccountData']['username'], + 'permissions' => '*' + ); + + if ($this->data['backendConfig']['backend'] === 'db') { + $config['groups'] = mt('setup', 'Administrators', 'setup.role.name'); + } + } else { // isset($this->data['adminAccountData']['groupname']) + $config = array( + 'groups' => $this->data['adminAccountData']['groupname'], + 'permissions' => '*' + ); + } try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('permissions.ini') - )); - $writer->write(); + Config::fromArray(array(mt('setup', 'Administrators', 'setup.role.name') => $config)) + ->setConfigFile(Config::resolvePath('roles.ini')) + ->saveIni(); } catch (Exception $e) { $this->permIniError = $e; return false; @@ -94,18 +99,19 @@ class AuthenticationStep extends Step ResourceFactory::createResource(new ConfigObject($this->data['adminAccountData']['resourceConfig'])) ); - if (array_search($this->data['adminAccountData']['username'], $backend->listUsers()) === false) { - $backend->addUser( - $this->data['adminAccountData']['username'], - $this->data['adminAccountData']['password'] - ); + if ($backend->select()->where('user_name', $this->data['adminAccountData']['username'])->count() === 0) { + $backend->insert('user', array( + 'user_name' => $this->data['adminAccountData']['username'], + 'password' => $this->data['adminAccountData']['password'], + 'is_active' => true + )); + $this->dbError = false; } } catch (Exception $e) { $this->dbError = $e; return false; } - $this->dbError = false; return true; } @@ -119,7 +125,9 @@ class AuthenticationStep extends Step $backendDesc = '

    ' . sprintf( mt('setup', 'Users will authenticate using %s.', 'setup.summary.auth'), $authType === 'db' ? mt('setup', 'a database', 'setup.summary.auth.type') : ( - $authType === 'ldap' ? 'LDAP' : mt('setup', 'webserver authentication', 'setup.summary.auth.type') + $authType === 'ldap' || $authType === 'msldap' ? 'LDAP' : ( + mt('setup', 'webserver authentication', 'setup.summary.auth.type') + ) ) ) . '

    '; @@ -130,16 +138,22 @@ class AuthenticationStep extends Step . '
    ' . '' . '' - . ($authType === 'ldap' ? ( + . ($authType === 'ldap' || $authType === 'msldap' ? ( '' - . '' - . '' + . '' + . '' . '' . '' - . '' - . '' + . '' + . '' . '' - ) : ($authType === 'autologin' ? ( + . '' + . '' + . '' + . '' + ) : ($authType === 'external' ? ( '' . '' . '' @@ -148,13 +162,20 @@ class AuthenticationStep extends Step . '' . '
    ' . t('Backend Name') . '' . $this->data['backendConfig']['name'] . '
    ' . t('User Object Class') . '' . $this->data['backendConfig']['user_class'] . '' . mt('setup', 'User Object Class') . '' . ($authType === 'msldap' ? 'user' : $this->data['backendConfig']['user_class']) . '
    ' . t('User Name Attribute') . '' . $this->data['backendConfig']['user_name_attribute'] . '' . mt('setup', 'Custom Filter') . '' . (trim($this->data['backendConfig']['filter']) ?: t('None', 'auth.ldap.filter')) . '
    ' . mt('setup', 'User Name Attribute') . '' . ($authType === 'msldap' + ? 'sAMAccountName' + : $this->data['backendConfig']['user_name_attribute']) . '
    ' . t('Filter Pattern') . '' . $this->data['backendConfig']['strip_username_regexp'] . '
    '; - $adminHtml = '

    ' . (isset($this->data['adminAccountData']['resourceConfig']) ? sprintf( - mt('setup', 'Administrative rights will initially be granted to a new account called "%s".'), - $this->data['adminAccountData']['username'] - ) : sprintf( - mt('setup', 'Administrative rights will initially be granted to an existing account called "%s".'), - $this->data['adminAccountData']['username'] - )) . '

    '; + if (isset($this->data['adminAccountData']['username'])) { + $adminHtml = '

    ' . (isset($this->data['adminAccountData']['resourceConfig']) ? sprintf( + mt('setup', 'Administrative rights will initially be granted to a new account called "%s".'), + $this->data['adminAccountData']['username'] + ) : sprintf( + mt('setup', 'Administrative rights will initially be granted to an existing account called "%s".'), + $this->data['adminAccountData']['username'] + )) . '

    '; + } else { // isset($this->data['adminAccountData']['groupname']) + $adminHtml = '

    ' . sprintf( + mt('setup', 'Administrative rights will initially be granted to members of the user group "%s".'), + $this->data['adminAccountData']['groupname'] + ) . '

    '; + } return $pageTitle . '
    ' . $backendDesc . $backendTitle . $backendHtml . '
    ' . '
    ' . $adminTitle . $adminHtml . '
    '; @@ -162,32 +183,54 @@ class AuthenticationStep extends Step public function getReport() { - $report = ''; + $report = array(); + if ($this->authIniError === false) { - $message = mt('setup', 'Authentication configuration has been successfully written to: %s'); - $report .= '

    ' . sprintf($message, Config::resolvePath('authentication.ini')) . '

    '; + $report[] = sprintf( + mt('setup', 'Authentication configuration has been successfully written to: %s'), + Config::resolvePath('authentication.ini') + ); } elseif ($this->authIniError !== null) { - $message = mt('setup', 'Authentication configuration could not be written to: %s; An error occured:'); - $report .= '

    ' . sprintf($message, Config::resolvePath('authentication.ini')) . '

    ' - . '

    ' . $this->authIniError->getMessage() . '

    '; + $report[] = sprintf( + mt('setup', 'Authentication configuration could not be written to: %s. An error occured:'), + Config::resolvePath('authentication.ini') + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->authIniError)); } if ($this->dbError === false) { - $message = mt('setup', 'Account "%s" has been successfully created.'); - $report .= '

    ' . sprintf($message, $this->data['adminAccountData']['username']) . '

    '; + $report[] = sprintf( + mt('setup', 'Account "%s" has been successfully created.'), + $this->data['adminAccountData']['username'] + ); } elseif ($this->dbError !== null) { - $message = mt('setup', 'Unable to create account "%s". An error occured:'); - $report .= '

    ' . sprintf($message, $this->data['adminAccountData']['username']) . '

    ' - . '

    ' . $this->dbError->getMessage() . '

    '; + $report[] = sprintf( + mt('setup', 'Unable to create account "%s". An error occured:'), + $this->data['adminAccountData']['username'] + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->dbError)); } if ($this->permIniError === false) { - $message = mt('setup', 'Account "%s" has been successfully defined as initial administrator.'); - $report .= '

    ' . sprintf($message, $this->data['adminAccountData']['username']) . '

    '; + $report[] = isset($this->data['adminAccountData']['username']) ? sprintf( + mt('setup', 'Account "%s" has been successfully defined as initial administrator.'), + $this->data['adminAccountData']['username'] + ) : sprintf( + mt('setup', 'The members of the user group "%s" were successfully defined as initial administrators.'), + $this->data['adminAccountData']['groupname'] + ); } elseif ($this->permIniError !== null) { - $message = mt('setup', 'Unable to define account "%s" as initial administrator. An error occured:'); - $report .= '

    ' . sprintf($message, $this->data['adminAccountData']['username']) . '

    ' - . '

    ' . $this->permIniError->getMessage() . '

    '; + $report[] = isset($this->data['adminAccountData']['username']) ? sprintf( + mt('setup', 'Unable to define account "%s" as initial administrator. An error occured:'), + $this->data['adminAccountData']['username'] + ) : sprintf( + mt( + 'setup', + 'Unable to define the members of the user group "%s" as initial administrators. An error occured:' + ), + $this->data['adminAccountData']['groupname'] + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->permIniError)); } return $report; diff --git a/modules/setup/library/Setup/Steps/DatabaseStep.php b/modules/setup/library/Setup/Steps/DatabaseStep.php index c6ec2ef2b..faf324ab6 100644 --- a/modules/setup/library/Setup/Steps/DatabaseStep.php +++ b/modules/setup/library/Setup/Steps/DatabaseStep.php @@ -1,12 +1,11 @@ reconnect($this->data['resourceConfig']['dbname']); } - if (array_search(key($this->data['tables']), $db->listTables()) !== false) { + if (array_search(reset($this->data['tables']), $db->listTables(), true) !== false) { $this->log(mt('setup', 'Database schema already exists...')); } else { $this->log(mt('setup', 'Creating database schema...')); - $db->import(Icinga::app()->getApplicationDir() . '/../etc/schema/mysql.schema.sql'); + $db->import($this->data['schemaPath'] . '/mysql.schema.sql'); } if ($db->hasLogin($this->data['resourceConfig']['username'])) { @@ -118,11 +117,11 @@ class DatabaseStep extends Step $db->reconnect($this->data['resourceConfig']['dbname']); } - if (array_search(key($this->data['tables']), $db->listTables()) !== false) { + if (array_search(reset($this->data['tables']), $db->listTables(), true) !== false) { $this->log(mt('setup', 'Database schema already exists...')); } else { $this->log(mt('setup', 'Creating database schema...')); - $db->import(Icinga::app()->getApplicationDir() . '/../etc/schema/pgsql.schema.sql'); + $db->import($this->data['schemaPath'] . '/pgsql.schema.sql'); } if ($db->hasLogin($this->data['resourceConfig']['username'])) { @@ -165,7 +164,7 @@ class DatabaseStep extends Step try { $db->connectToDb(); - if (array_search(key($this->data['tables']), $db->listTables()) === false) { + if (array_search(reset($this->data['tables']), $db->listTables(), true) === false) { if ($resourceConfig['username'] !== $this->data['resourceConfig']['username']) { $message = sprintf( mt( @@ -249,12 +248,14 @@ class DatabaseStep extends Step public function getReport() { if ($this->error === false) { - return '

    ' . join('

    ', $this->messages) . '

    ' - . '

    ' . mt('setup', 'The database has been fully set up!') . '

    '; + $report = $this->messages; + $report[] = mt('setup', 'The database has been fully set up!'); + return $report; } elseif ($this->error !== null) { - $message = mt('setup', 'Failed to fully setup the database. An error occured:'); - return '

    ' . join('

    ', $this->messages) . '

    ' - . '

    ' . $message . '

    ' . $this->error->getMessage() . '

    '; + $report = $this->messages; + $report[] = mt('setup', 'Failed to fully setup the database. An error occured:'); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error)); + return $report; } } diff --git a/modules/setup/library/Setup/Steps/GeneralConfigStep.php b/modules/setup/library/Setup/Steps/GeneralConfigStep.php index ee9e2c581..7924d87d0 100644 --- a/modules/setup/library/Setup/Steps/GeneralConfigStep.php +++ b/modules/setup/library/Setup/Steps/GeneralConfigStep.php @@ -1,13 +1,12 @@ data['generalConfig'] as $sectionAndPropertyName => $value) { - list($section, $property) = explode('_', $sectionAndPropertyName); + list($section, $property) = explode('_', $sectionAndPropertyName, 2); $config[$section][$property] = $value; } - $config['preferences']['type'] = $this->data['preferencesType']; - if (isset($this->data['preferencesResource'])) { - $config['preferences']['resource'] = $this->data['preferencesResource']; + if ($config['global']['config_backend'] === 'db') { + $config['global']['config_resource'] = $this->data['resourceName']; } try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('config.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('config.ini')) + ->saveIni(); } catch (Exception $e) { $this->error = $e; return false; @@ -58,14 +54,10 @@ class GeneralConfigStep extends Step $generalHtml = '' . '
      ' . '
    • ' . sprintf( - $this->data['preferencesType'] === 'ini' ? sprintf( + $this->data['generalConfig']['global_config_backend'] === 'ini' ? sprintf( t('Preferences will be stored per user account in INI files at: %s'), Config::resolvePath('preferences') - ) : ( - $this->data['preferencesType'] === 'db' ? t('Preferences will be stored using a database.') : ( - t('Preferences will not be persisted across browser sessions.') - ) - ) + ) : t('Preferences will be stored using a database.') ) . '
    • ' . '
    '; @@ -111,12 +103,18 @@ class GeneralConfigStep extends Step public function getReport() { if ($this->error === false) { - $message = mt('setup', 'General configuration has been successfully written to: %s'); - return '

    ' . sprintf($message, Config::resolvePath('config.ini')) . '

    '; + return array(sprintf( + mt('setup', 'General configuration has been successfully written to: %s'), + Config::resolvePath('config.ini') + )); } elseif ($this->error !== null) { - $message = mt('setup', 'General configuration could not be written to: %s; An error occured:'); - return '

    ' . sprintf($message, Config::resolvePath('config.ini')) . '

    ' - . '

    ' . $this->error->getMessage() . '

    '; + return array( + sprintf( + mt('setup', 'General configuration could not be written to: %s. An error occured:'), + Config::resolvePath('config.ini') + ), + sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error)) + ); } } } diff --git a/modules/setup/library/Setup/Steps/ResourceStep.php b/modules/setup/library/Setup/Steps/ResourceStep.php index c04cdbc79..de46a229d 100644 --- a/modules/setup/library/Setup/Steps/ResourceStep.php +++ b/modules/setup/library/Setup/Steps/ResourceStep.php @@ -1,12 +1,11 @@ Config::fromArray($resourceConfig), - 'filename' => Config::resolvePath('resources.ini'), - 'filemode' => 0660 - )); - $writer->write(); + Config::fromArray($resourceConfig) + ->setConfigFile(Config::resolvePath('resources.ini')) + ->saveIni(); } catch (Exception $e) { $this->error = $e; return false; @@ -138,12 +134,18 @@ class ResourceStep extends Step public function getReport() { if ($this->error === false) { - $message = mt('setup', 'Resource configuration has been successfully written to: %s'); - return '

    ' . sprintf($message, Config::resolvePath('resources.ini')) . '

    '; + return array(sprintf( + mt('setup', 'Resource configuration has been successfully written to: %s'), + Config::resolvePath('resources.ini') + )); } elseif ($this->error !== null) { - $message = mt('setup', 'Resource configuration could not be written to: %s; An error occured:'); - return '

    ' . sprintf($message, Config::resolvePath('resources.ini')) . '

    ' - . '

    ' . $this->error->getMessage() . '

    '; + return array( + sprintf( + mt('setup', 'Resource configuration could not be written to: %s. An error occured:'), + Config::resolvePath('resources.ini') + ), + sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error)) + ); } } } diff --git a/modules/setup/library/Setup/Steps/UserGroupStep.php b/modules/setup/library/Setup/Steps/UserGroupStep.php new file mode 100644 index 000000000..ab58c1880 --- /dev/null +++ b/modules/setup/library/Setup/Steps/UserGroupStep.php @@ -0,0 +1,209 @@ +data = $data; + } + + public function apply() + { + $success = $this->createGroupsIni(); + if (isset($this->data['resourceConfig'])) { + $success &= $this->createUserGroup(); + if ($success) { + $success &= $this->createMembership(); + } + } + + return $success; + } + + protected function createGroupsIni() + { + $config = array(); + if (isset($this->data['groupConfig'])) { + $backendConfig = $this->data['groupConfig']; + $backendName = $backendConfig['name']; + unset($backendConfig['name']); + $config[$backendName] = $backendConfig; + } else { + $backendConfig = array( + 'backend' => $this->data['backendConfig']['backend'], // "db" or "msldap" + 'resource' => $this->data['resourceName'] + ); + + if ($backendConfig['backend'] === 'msldap') { + $backendConfig['user_backend'] = $this->data['backendConfig']['name']; + } + + $config[$this->data['backendConfig']['name']] = $backendConfig; + } + + try { + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('groups.ini')) + ->saveIni(); + } catch (Exception $e) { + $this->groupIniError = $e; + return false; + } + + $this->groupIniError = false; + return true; + } + + protected function createUserGroup() + { + try { + $backend = new DbUserGroupBackend( + ResourceFactory::createResource(new ConfigObject($this->data['resourceConfig'])) + ); + + $groupName = mt('setup', 'Administrators', 'setup.role.name'); + if ($backend->select()->where('group_name', $groupName)->count() === 0) { + $backend->insert('group', array( + 'group_name' => $groupName + )); + $this->groupError = false; + } + } catch (Exception $e) { + $this->groupError = $e; + return false; + } + + return true; + } + + protected function createMembership() + { + try { + $backend = new DbUserGroupBackend( + ResourceFactory::createResource(new ConfigObject($this->data['resourceConfig'])) + ); + + $groupName = mt('setup', 'Administrators', 'setup.role.name'); + $userName = $this->data['username']; + if ($backend + ->select() + ->from('group_membership') + ->where('group_name', $groupName) + ->where('user_name', $userName) + ->count() === 0 + ) { + $backend->insert('group_membership', array( + 'group_name' => $groupName, + 'user_name' => $userName + )); + $this->memberError = false; + } + } catch (Exception $e) { + $this->memberError = $e; + return false; + } + + return true; + } + + public function getSummary() + { + if (! isset($this->data['groupConfig'])) { + return; // It's not necessary to show the user something he didn't configure.. + } + + $pageTitle = '

    ' . mt('setup', 'User Groups', 'setup.page.title') . '

    '; + $backendTitle = '

    ' . mt('setup', 'User Group Backend', 'setup.page.title') . '

    '; + + $backendHtml = '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '
    ' . t('Backend Name') . '' . $this->data['groupConfig']['name'] . '
    ' . mt('setup', 'Group Object Class') . '' . $this->data['groupConfig']['group_class'] . '
    ' . mt('setup', 'Custom Filter') . '' . (trim($this->data['groupConfig']['group_filter']) ?: t('None', 'auth.ldap.filter')) . '
    ' . mt('setup', 'Group Name Attribute') . '' . $this->data['groupConfig']['group_name_attribute'] . '
    '; + + return $pageTitle . '
    ' . $backendTitle . $backendHtml . '
    '; + } + + public function getReport() + { + $report = array(); + + if ($this->groupIniError === false) { + $report[] = sprintf( + mt('setup', 'User Group Backend configuration has been successfully written to: %s'), + Config::resolvePath('groups.ini') + ); + } elseif ($this->groupIniError !== null) { + $report[] = sprintf( + mt('setup', 'User Group Backend configuration could not be written to: %s. An error occured:'), + Config::resolvePath('groups.ini') + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->groupIniError)); + } + + if ($this->groupError === false) { + $report[] = sprintf( + mt('setup', 'User Group "%s" has been successfully created.'), + mt('setup', 'Administrators', 'setup.role.name') + ); + } elseif ($this->groupError !== null) { + $report[] = sprintf( + mt('setup', 'Unable to create user group "%s". An error occured:'), + mt('setup', 'Administrators', 'setup.role.name') + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->groupError)); + } + + if ($this->memberError === false) { + $report[] = sprintf( + mt('setup', 'Account "%s" has been successfully added as member to user group "%s".'), + $this->data['username'], + mt('setup', 'Administrators', 'setup.role.name') + ); + } elseif ($this->memberError !== null) { + $report[] = sprintf( + mt('setup', 'Unable to add account "%s" as member to user group "%s". An error occured:'), + $this->data['username'], + mt('setup', 'Administrators', 'setup.role.name') + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->memberError)); + } + + return $report; + } +} diff --git a/modules/setup/library/Setup/Utils/DbTool.php b/modules/setup/library/Setup/Utils/DbTool.php index 37d0673b1..bf3465458 100644 --- a/modules/setup/library/Setup/Utils/DbTool.php +++ b/modules/setup/library/Setup/Utils/DbTool.php @@ -1,6 +1,5 @@ 29, 'LOCK TABLES' => 5, 'PROCESS' => 1, - 'REFERENCES' => 0, + 'REFERENCES' => 12, 'RELOAD' => 1, 'REPLICATION CLIENT' => 1, 'REPLICATION SLAVE' => 1, @@ -128,7 +127,7 @@ class DbTool /** * Connect to the server * - * @return self + * @return $this */ public function connectToHost() { @@ -151,7 +150,7 @@ class DbTool /** * Connect to the database * - * @return self + * @return $this */ public function connectToDb() { @@ -168,15 +167,15 @@ class DbTool */ protected function assertHostAccess() { - if (false === isset($this->config['db'])) { + if (! isset($this->config['db'])) { throw new ConfigurationError('Can\'t connect to database server of unknown type'); - } elseif (false === isset($this->config['host'])) { + } elseif (! isset($this->config['host'])) { throw new ConfigurationError('Can\'t connect to database server without a hostname or address'); - } elseif (false === isset($this->config['port'])) { + } elseif (! isset($this->config['port'])) { throw new ConfigurationError('Can\'t connect to database server without a port'); - } elseif (false === isset($this->config['username'])) { + } elseif (! isset($this->config['username'])) { throw new ConfigurationError('Can\'t connect to database server without a username'); - } elseif (false === isset($this->config['password'])) { + } elseif (! isset($this->config['password'])) { throw new ConfigurationError('Can\'t connect to database server without a password'); } } @@ -188,7 +187,7 @@ class DbTool */ protected function assertDatabaseAccess() { - if (false === isset($this->config['dbname'])) { + if (! isset($this->config['dbname'])) { throw new ConfigurationError('Can\'t connect to database without a valid database name'); } } @@ -352,6 +351,24 @@ class DbTool } } + /** + * Return the given table name with all wildcards being escaped + * + * @param string $tableName + * + * @return string + * + * @throws LogicException In case there is no behaviour implemented for the current PDO driver + */ + public function escapeTableWildcards($tableName) + { + if ($this->config['db'] === 'mysql') { + return str_replace(array('_', '%'), array('\_', '\%'), $tableName); + } + + throw new LogicException('Unable to escape table wildcards.'); + } + /** * Return the given value escaped as string * @@ -482,46 +499,52 @@ class DbTool { if ($this->config['db'] === 'mysql') { list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn()); - $queryString = sprintf( - 'GRANT %%s ON %s.%%s TO %s@%s', - $this->quoteIdentifier($this->config['dbname']), + $quotedDbName = $this->quoteIdentifier($this->config['dbname']); + + $grant = 'GRANT %s'; + $on = ' ON %s.%s'; + $to = sprintf( + ' TO %s@%s', $this->quoteIdentifier($username), - $this->quoteIdentifier($host) + str_replace('%', '%%', $this->quoteIdentifier($host)) ); $dbPrivileges = array(); $tablePrivileges = array(); foreach (array_intersect($privileges, array_keys($this->mysqlGrantContexts)) as $privilege) { - if (false === empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) { + if (! empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) { $tablePrivileges[] = $privilege; } elseif ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) { $dbPrivileges[] = $privilege; } } - if (false === empty($tablePrivileges)) { + if (! empty($tablePrivileges)) { + $tableGrant = sprintf($grant, join(',', $tablePrivileges)); foreach ($context as $table) { - $this->exec( - sprintf($queryString, join(',', $tablePrivileges), $this->quoteIdentifier($table)) - ); + $this->exec($tableGrant . sprintf($on, $quotedDbName, $this->quoteIdentifier($table)) . $to); } } - if (false === empty($dbPrivileges)) { - $this->exec(sprintf($queryString, join(',', $dbPrivileges), '*')); + if (! empty($dbPrivileges)) { + $this->exec( + sprintf($grant, join(',', $dbPrivileges)) + . sprintf($on, $this->escapeTableWildcards($quotedDbName), '*') + . $to + ); } } elseif ($this->config['db'] === 'pgsql') { $dbPrivileges = array(); $tablePrivileges = array(); foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) { - if (false === empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) { + if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) { $tablePrivileges[] = $privilege; } elseif ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) { $dbPrivileges[] = $privilege; } } - if (false === empty($dbPrivileges)) { + if (! empty($dbPrivileges)) { $this->exec(sprintf( 'GRANT %s ON DATABASE %s TO %s', join(',', $dbPrivileges), @@ -530,7 +553,7 @@ class DbTool )); } - if (false === empty($tablePrivileges)) { + if (! empty($tablePrivileges)) { foreach ($context as $table) { $this->exec(sprintf( 'GRANT %s ON TABLE %s TO %s', @@ -630,46 +653,67 @@ EOD; $mysqlPrivileges = array_intersect($privileges, array_keys($this->mysqlGrantContexts)); list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn()); $grantee = "'" . ($username === null ? $this->config['username'] : $username) . "'@'" . $host . "'"; - $privilegeCondition = sprintf( - 'privilege_type IN (%s)', - join(',', array_map(array($this, 'quote'), $mysqlPrivileges)) - ); if (isset($this->config['dbname'])) { $dbPrivileges = array(); $tablePrivileges = array(); foreach ($mysqlPrivileges as $privilege) { - if (false === empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) { + if (! empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) { $tablePrivileges[] = $privilege; - } elseif ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) { + } + if ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) { $dbPrivileges[] = $privilege; } } $dbPrivilegesGranted = true; - if (false === empty($dbPrivileges)) { - $query = $this->query( - 'SELECT COUNT(*) as matches' + $tablePrivilegesGranted = true; + + if (! empty($dbPrivileges)) { + $queryString = 'SELECT COUNT(*) as matches' . ' FROM information_schema.schema_privileges' . ' WHERE grantee = :grantee' . ' AND table_schema = :dbname' - . ' AND ' . $privilegeCondition - . ($requireGrants ? " AND is_grantable = 'YES'" : ''), - array(':grantee' => $grantee, ':dbname' => $this->config['dbname']) + . ' AND privilege_type IN (%s)' + . ($requireGrants ? " AND is_grantable = 'YES'" : ''); + + $dbAndTableQuery = $this->query( + sprintf($queryString, join(',', array_map(array($this, 'quote'), $dbPrivileges))), + array(':grantee' => $grantee, ':dbname' => $this->escapeTableWildcards($this->config['dbname'])) ); - $dbPrivilegesGranted = (int) $query->fetchObject()->matches === count($dbPrivileges); + $grantedDbAndTablePrivileges = (int) $dbAndTableQuery->fetchObject()->matches; + if ($grantedDbAndTablePrivileges === count($dbPrivileges)) { + $tableExclusivePrivileges = array_diff($tablePrivileges, $dbPrivileges); + if (! empty($tableExclusivePrivileges)) { + $tablePrivileges = $tableExclusivePrivileges; + $tablePrivilegesGranted = false; + } + } else { + $tablePrivilegesGranted = false; + $dbExclusivePrivileges = array_diff($dbPrivileges, $tablePrivileges); + if (! empty($dbExclusivePrivileges)) { + $dbExclusiveQuery = $this->query( + sprintf($queryString, join(',', array_map(array($this, 'quote'), $dbExclusivePrivileges))), + array( + ':grantee' => $grantee, + ':dbname' => $this->escapeTableWildcards($this->config['dbname']) + ) + ); + $dbPrivilegesGranted = (int) $dbExclusiveQuery->fetchObject()->matches === count( + $dbExclusivePrivileges + ); + } + } } - $tablePrivilegesGranted = true; - if (false === empty($tablePrivileges)) { - $tableCondition = 'table_name IN (' . join(',', array_map(array($this, 'quote'), $context)) . ')'; + if (! $tablePrivilegesGranted && !empty($tablePrivileges)) { $query = $this->query( 'SELECT COUNT(*) as matches' . ' FROM information_schema.table_privileges' . ' WHERE grantee = :grantee' . ' AND table_schema = :dbname' - . ' AND ' . $tableCondition - . ' AND ' . $privilegeCondition + . ' AND table_name IN (' . join(',', array_map(array($this, 'quote'), $context)) . ')' + . ' AND privilege_type IN (' . join(',', array_map(array($this, 'quote'), $tablePrivileges)) . ')' . ($requireGrants ? " AND is_grantable = 'YES'" : ''), array(':grantee' => $grantee, ':dbname' => $this->config['dbname']) ); @@ -684,7 +728,8 @@ EOD; $query = $this->query( 'SELECT COUNT(*) as matches FROM information_schema.user_privileges WHERE grantee = :grantee' - . ' AND ' . $privilegeCondition . ($requireGrants ? " AND is_grantable = 'YES'" : ''), + . ' AND privilege_type IN (' . join(',', array_map(array($this, 'quote'), $mysqlPrivileges)) . ')' + . ($requireGrants ? " AND is_grantable = 'YES'" : ''), array(':grantee' => $grantee) ); return (int) $query->fetchObject()->matches === count($mysqlPrivileges); @@ -715,36 +760,54 @@ EOD; $dbPrivileges = array(); $tablePrivileges = array(); foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) { - if (false === empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) { + if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) { $tablePrivileges[] = $privilege; - } elseif ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) { + } + if ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) { $dbPrivileges[] = $privilege; } } - if (false === empty($dbPrivileges)) { - $query = $this->query( - 'SELECT has_database_privilege(:user, :dbname, :privileges) AS db_privileges_granted', - array( - ':user' => $username !== null ? $username : $this->config['username'], - ':dbname' => $this->config['dbname'], - ':privileges' => join(',', $dbPrivileges) . ($requireGrants ? ' WITH GRANT OPTION' : '') - ) - ); - $privilegesGranted &= $query->fetchObject()->db_privileges_granted; - } - - if (false === empty($tablePrivileges)) { - foreach (array_intersect($context, $this->listTables()) as $table) { + if (! empty($dbPrivileges)) { + $dbExclusivesGranted = true; + foreach ($dbPrivileges as $dbPrivilege) { $query = $this->query( - 'SELECT has_table_privilege(:user, :table, :privileges) AS table_privileges_granted', + 'SELECT has_database_privilege(:user, :dbname, :privilege) AS db_privilege_granted', array( ':user' => $username !== null ? $username : $this->config['username'], - ':table' => $table, - ':privileges' => join(',', $tablePrivileges) . ($requireGrants ? ' WITH GRANT OPTION' : '') + ':dbname' => $this->config['dbname'], + ':privilege' => $dbPrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '') ) ); - $privilegesGranted &= $query->fetchObject()->table_privileges_granted; + if (! $query->fetchObject()->db_privilege_granted) { + $privilegesGranted = false; + if (! in_array($dbPrivilege, $tablePrivileges)) { + $dbExclusivesGranted = false; + } + } + } + + if ($privilegesGranted) { + // Do not check privileges twice if they are already granted at database level + $tablePrivileges = array_diff($tablePrivileges, $dbPrivileges); + } elseif ($dbExclusivesGranted) { + $privilegesGranted = true; + } + } + + if ($privilegesGranted && !empty($tablePrivileges)) { + foreach (array_intersect($context, $this->listTables()) as $table) { + foreach ($tablePrivileges as $tablePrivilege) { + $query = $this->query( + 'SELECT has_table_privilege(:user, :table, :privilege) AS table_privilege_granted', + array( + ':user' => $username !== null ? $username : $this->config['username'], + ':table' => $table, + ':privilege' => $tablePrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '') + ) + ); + $privilegesGranted &= $query->fetchObject()->table_privilege_granted; + } } } } else { @@ -752,17 +815,17 @@ EOD; // connected to the database defined in the resource configuration it is safe to just ignore them // as the chances are very high that the database is created later causing the current user being // the owner with ALL privileges. (Which in turn can be granted to others.) + + if (array_search('CREATE', $privileges, true) !== false) { + $query = $this->query( + 'select rolcreatedb from pg_roles where rolname = :user', + array(':user' => $username !== null ? $username : $this->config['username']) + ); + $privilegesGranted &= $query->fetchColumn() !== false; + } } - if (array_search('CREATE', $privileges) !== false) { - $query = $this->query( - 'select rolcreatedb from pg_roles where rolname = :user', - array(':user' => $username !== null ? $username : $this->config['username']) - ); - $privilegesGranted &= $query->fetchColumn() !== false; - } - - if (array_search('CREATEROLE', $privileges) !== false) { + if (array_search('CREATEROLE', $privileges, true) !== false) { $query = $this->query( 'select rolcreaterole from pg_roles where rolname = :user', array(':user' => $username !== null ? $username : $this->config['username']) @@ -770,7 +833,7 @@ EOD; $privilegesGranted &= $query->fetchColumn() !== false; } - if (array_search('SUPER', $privileges) !== false) { + if (array_search('SUPER', $privileges, true) !== false) { $query = $this->query( 'select rolsuper from pg_roles where rolname = :user', array(':user' => $username !== null ? $username : $this->config['username']) @@ -778,6 +841,6 @@ EOD; $privilegesGranted &= $query->fetchColumn() !== false; } - return $privilegesGranted; + return (bool) $privilegesGranted; } } diff --git a/modules/setup/library/Setup/Utils/EnableModuleStep.php b/modules/setup/library/Setup/Utils/EnableModuleStep.php index 3ca3eebd1..88f48ff80 100644 --- a/modules/setup/library/Setup/Utils/EnableModuleStep.php +++ b/modules/setup/library/Setup/Utils/EnableModuleStep.php @@ -1,24 +1,24 @@ moduleName = $moduleName; + $this->moduleNames = $moduleNames; $this->modulePaths = array(); if (($appModulePath = realpath(Icinga::app()->getApplicationDir() . '/../modules')) !== false) { @@ -28,17 +28,20 @@ class EnableModuleStep extends Step public function apply() { - try { - $moduleManager = Icinga::app()->getModuleManager(); - $moduleManager->detectInstalledModules($this->modulePaths); - $moduleManager->enableModule($this->moduleName); - } catch (Exception $e) { - $this->error = $e; - return false; + $moduleManager = Icinga::app()->getModuleManager(); + $moduleManager->detectInstalledModules($this->modulePaths); + + $success = true; + foreach ($this->moduleNames as $moduleName) { + try { + $moduleManager->enableModule($moduleName); + } catch (Exception $e) { + $this->errors[$moduleName] = $e; + $success = false; + } } - $this->error = false; - return true; + return $success; } public function getSummary() @@ -48,12 +51,19 @@ class EnableModuleStep extends Step public function getReport() { - if ($this->error === false) { - return '

    ' . sprintf(mt('setup', 'Module "%s" has been successfully enabled.'), $this->moduleName) . '

    '; - } elseif ($this->error !== null) { - $message = mt('setup', 'Module "%s" could not be enabled. An error occured:'); - return '

    ' . sprintf($message, $this->moduleName) . '

    ' - . '

    ' . $this->error->getMessage() . '

    '; + $okMessage = mt('setup', 'Module "%s" has been successfully enabled.'); + $failMessage = mt('setup', 'Module "%s" could not be enabled. An error occured:'); + + $report = array(); + foreach ($this->moduleNames as $moduleName) { + if (isset($this->errors[$moduleName])) { + $report[] = sprintf($failMessage, $moduleName); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->errors[$moduleName])); + } else { + $report[] = sprintf($okMessage, $moduleName); + } } + + return $report; } } diff --git a/modules/setup/library/Setup/Utils/MakeDirStep.php b/modules/setup/library/Setup/Utils/MakeDirStep.php index d15c7db6a..5e486d561 100644 --- a/modules/setup/library/Setup/Utils/MakeDirStep.php +++ b/modules/setup/library/Setup/Utils/MakeDirStep.php @@ -1,6 +1,5 @@ errors[$path] = null; - $old = umask(0); chmod($path, $this->dirmode); - umask($old); } } } @@ -56,14 +53,14 @@ class MakeDirStep extends Step $okMessage = mt('setup', 'Directory "%s" in "%s" has been successfully created.'); $failMessage = mt('setup', 'Unable to create directory "%s" in "%s". An error occured:'); - $report = ''; + $report = array(); foreach ($this->paths as $path) { if (array_key_exists($path, $this->errors)) { if (is_array($this->errors[$path])) { - $report .= '

    ' . sprintf($failMessage, basename($path), dirname($path)) . '

    ' - . '

    ' . $this->errors[$path]['message'] . '

    '; + $report[] = sprintf($failMessage, basename($path), dirname($path)); + $report[] = sprintf(mt('setup', 'ERROR: %s'), $this->errors[$path]['message']); } else { - $report .= '

    ' . sprintf($okMessage, basename($path), dirname($path)) . '

    '; + $report[] = sprintf($okMessage, basename($path), dirname($path)); } } } diff --git a/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php b/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php index 7dc2ea2f9..199b4215d 100644 --- a/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php +++ b/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php @@ -1,6 +1,5 @@ sprintf( - mt('setup', 'Cannot validate token, file "%s" must only be accessible by the webserver\'s user.'), - $tokenPath - ), 'TOKEN_INVALID' => mt('setup', 'Invalid token supplied.') ); } @@ -56,12 +51,6 @@ class TokenValidator extends Zend_Validate_Abstract */ public function isValid($value, $context = null) { - $tokenStats = @stat($this->tokenPath); - if (($tokenStats['mode'] & 4) === 4) { - $this->_error('TOKEN_FILE_PUBLIC'); - return false; - } - try { $file = new File($this->tokenPath); $expectedToken = trim($file->fgets()); diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index 72cd4f286..e3831e673 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -1,6 +1,5 @@ addPage(new WelcomePage()); + $this->addPage(new ModulePage()); $this->addPage(new RequirementsPage()); $this->addPage(new AuthenticationPage()); - $this->addPage(new PreferencesPage()); - $this->addPage(new DbResourcePage()); + $this->addPage(new DbResourcePage(array('name' => 'setup_auth_db_resource'))); + $this->addPage(new DatabaseCreationPage(array('name' => 'setup_auth_db_creation'))); $this->addPage(new LdapDiscoveryPage()); - $this->addPage(new LdapDiscoveryConfirmPage()); + //$this->addPage(new LdapDiscoveryConfirmPage()); $this->addPage(new LdapResourcePage()); $this->addPage(new AuthBackendPage()); + $this->addPage(new UserGroupBackendPage()); $this->addPage(new AdminAccountPage()); $this->addPage(new GeneralConfigPage()); - $this->addPage(new DatabaseCreationPage()); - $this->addPage(new ModulePage()); - $this->addPage(new SummaryPage()); + $this->addPage(new DbResourcePage(array('name' => 'setup_config_db_resource'))); + $this->addPage(new DatabaseCreationPage(array('name' => 'setup_config_db_creation'))); + $this->addPage(new SummaryPage(array('name' => 'setup_summary'))); + + if (($modulePageData = $this->getPageData('setup_modules')) !== null) { + $modulePage = $this->getPage('setup_modules')->populate($modulePageData); + foreach ($modulePage->getModuleWizards() as $moduleWizard) { + $this->addPage($moduleWizard); + } + } } /** - * @see Wizard::setupPage() + * Setup the given page that is either going to be displayed or validated + * + * @param Form $page The page to setup + * @param Request $request The current request */ public function setupPage(Form $page, Request $request) { if ($page->getName() === 'setup_requirements') { - $page->setRequirements($this->getRequirements()); - } elseif ($page->getName() === 'setup_preferences_type') { - $authData = $this->getPageData('setup_authentication_type'); - if ($authData['type'] === 'db') { - $page->create()->showDatabaseNote(); - } + $page->setWizard($this); } elseif ($page->getName() === 'setup_authentication_backend') { $authData = $this->getPageData('setup_authentication_type'); if ($authData['type'] === 'db') { - $page->setResourceConfig($this->getPageData('setup_db_resource')); + $page->setResourceConfig($this->getPageData('setup_auth_db_resource')); } elseif ($authData['type'] === 'ldap') { $page->setResourceConfig($this->getPageData('setup_ldap_resource')); - $suggestions = $this->getPageData('setup_ldap_discovery_confirm'); - if (isset($suggestions['backend'])) { - $page->populate($suggestions['backend']); + if (! $this->hasPageData('setup_authentication_backend')) { + $suggestions = $this->getPageData('setup_ldap_discovery'); + if (isset($suggestions['backend'])) { + $page->populate($suggestions['backend']); + } + } + + if ($this->getDirection() === static::FORWARD) { + $backendConfig = $this->getPageData('setup_authentication_backend'); + if ($backendConfig !== null && $request->getPost('name') !== $backendConfig['name']) { + $pageData = & $this->getPageData(); + unset($pageData['setup_usergroup_backend']); + } } } - } elseif ($page->getName() === 'setup_ldap_discovery_confirm') { - $page->setResourceConfig($this->getPageData('setup_ldap_discovery')); + + if ($this->getDirection() === static::FORWARD) { + $backendConfig = $this->getPageData('setup_authentication_backend'); + if ($backendConfig !== null && $request->getPost('backend') !== $backendConfig['backend']) { + $pageData = & $this->getPageData(); + unset($pageData['setup_usergroup_backend']); + } + } + /*} elseif ($page->getName() === 'setup_ldap_discovery_confirm') { + $page->setResourceConfig($this->getPageData('setup_ldap_discovery'));*/ + } elseif ($page->getName() === 'setup_auth_db_resource') { + $page->addDescription(mt( + 'setup', + 'Now please configure the database resource where to store users and user groups.' + )); + $page->addDescription(mt( + 'setup', + 'Note that the database itself does not need to exist at this time as' + . ' it is going to be created once the wizard is about to be finished.' + )); + } elseif ($page->getName() === 'setup_usergroup_backend') { + $page->setResourceConfig($this->getPageData('setup_ldap_resource')); + $page->setBackendConfig($this->getPageData('setup_authentication_backend')); } elseif ($page->getName() === 'setup_admin_account') { $page->setBackendConfig($this->getPageData('setup_authentication_backend')); + $page->setGroupConfig($this->getPageData('setup_usergroup_backend')); $authData = $this->getPageData('setup_authentication_type'); if ($authData['type'] === 'db') { - $page->setResourceConfig($this->getPageData('setup_db_resource')); + $page->setResourceConfig($this->getPageData('setup_auth_db_resource')); } elseif ($authData['type'] === 'ldap') { $page->setResourceConfig($this->getPageData('setup_ldap_resource')); } - } elseif ($page->getName() === 'setup_database_creation') { - $page->setDatabaseSetupPrivileges($this->databaseSetupPrivileges); + } elseif ($page->getName() === 'setup_auth_db_creation' || $page->getName() === 'setup_config_db_creation') { + $page->setDatabaseSetupPrivileges( + array_unique(array_merge($this->databaseCreationPrivileges, $this->databaseSetupPrivileges)) + ); $page->setDatabaseUsagePrivileges($this->databaseUsagePrivileges); - $page->setResourceConfig($this->getPageData('setup_db_resource')); + $page->setResourceConfig( + $this->getPageData('setup_auth_db_resource') ?: $this->getPageData('setup_config_db_resource') + ); } elseif ($page->getName() === 'setup_summary') { $page->setSubjectTitle('Icinga Web 2'); $page->setSummary($this->getSetup()->getSummary()); - } elseif ($page->getName() === 'setup_db_resource') { + } elseif ($page->getName() === 'setup_config_db_resource') { + $page->addDescription(mt( + 'setup', + 'Now please configure the database resource where to store user preferences.' + )); + $page->addDescription(mt( + 'setup', + 'Note that the database itself does not need to exist at this time as' + . ' it is going to be created once the wizard is about to be finished.' + )); + $ldapData = $this->getPageData('setup_ldap_resource'); if ($ldapData !== null && $request->getPost('name') === $ldapData['name']) { - $page->addError( + $page->error( mt('setup', 'The given resource name must be unique and is already in use by the LDAP resource') ); } } elseif ($page->getName() === 'setup_ldap_resource') { - $dbData = $this->getPageData('setup_db_resource'); - if ($dbData !== null && $request->getPost('name') === $dbData['name']) { - $page->addError( - mt('setup', 'The given resource name must be unique and is already in use by the database resource') - ); - } - - $suggestion = $this->getPageData('setup_ldap_discovery_confirm'); + $suggestion = $this->getPageData('setup_ldap_discovery'); if (isset($suggestion['resource'])) { $page->populate($suggestion['resource']); } + + if ($this->getDirection() === static::FORWARD) { + $resourceConfig = $this->getPageData('setup_ldap_resource'); + if ($resourceConfig !== null && $request->getPost('name') !== $resourceConfig['name']) { + $pageData = & $this->getPageData(); + unset($pageData['setup_usergroup_backend']); + } + } + } elseif ($page->getName() === 'setup_general_config') { + $authData = $this->getPageData('setup_authentication_type'); + if ($authData['type'] === 'db') { + $page + ->create($this->getRequestData($page, $request)) + ->getElement('global_config_backend') + ->setValue('db'); + $page->info( + mt( + 'setup', + 'Note that choosing "Database" as preference storage causes' + . ' Icinga Web 2 to use the same database as for authentication.' + ), + false + ); + } } elseif ($page->getName() === 'setup_authentication_type' && $this->getDirection() === static::FORWARD) { $authData = $this->getPageData($page->getName()); if ($authData !== null && $request->getPost('type') !== $authData['type']) { @@ -165,41 +250,66 @@ class WebWizard extends Wizard implements SetupWizard $pageData = & $this->getPageData(); unset($pageData['setup_admin_account']); unset($pageData['setup_authentication_backend']); + + if ($authData['type'] === 'db') { + unset($pageData['setup_auth_db_resource']); + unset($pageData['setup_auth_db_creation']); + } elseif ($request->getPost('type') === 'db') { + unset($pageData['setup_config_db_resource']); + unset($pageData['setup_config_db_creation']); + } } - } elseif ($page->getName() === 'setup_modules') { - $page->setPageData($this->getPageData()); - $page->handleRequest($request); } } /** - * @see Wizard::getNewPage() + * Return the new page to set as current page + * + * {@inheritdoc} Runs additional checks related to some registered pages. + * + * @param string $requestedPage The name of the requested page + * @param Form $originPage The origin page + * + * @return Form The new page + * + * @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet */ protected function getNewPage($requestedPage, Form $originPage) { $skip = false; $newPage = parent::getNewPage($requestedPage, $originPage); - if ($newPage->getName() === 'setup_db_resource') { - $prefData = $this->getPageData('setup_preferences_type'); + if ($newPage->getName() === 'setup_auth_db_resource') { $authData = $this->getPageData('setup_authentication_type'); - $skip = $prefData['type'] !== 'db' && $authData['type'] !== 'db'; + $skip = $authData['type'] !== 'db'; } elseif ($newPage->getname() === 'setup_ldap_discovery') { $authData = $this->getPageData('setup_authentication_type'); $skip = $authData['type'] !== 'ldap'; - } elseif ($newPage->getName() === 'setup_ldap_discovery_confirm') { - $skip = false === $this->hasPageData('setup_ldap_discovery'); + /*} elseif ($newPage->getName() === 'setup_ldap_discovery_confirm') { + $skip = false === $this->hasPageData('setup_ldap_discovery');*/ } elseif ($newPage->getName() === 'setup_ldap_resource') { $authData = $this->getPageData('setup_authentication_type'); $skip = $authData['type'] !== 'ldap'; - } elseif ($newPage->getName() === 'setup_database_creation') { - if (($config = $this->getPageData('setup_db_resource')) !== null && ! $config['skip_validation']) { + } elseif ($newPage->getName() === 'setup_usergroup_backend') { + $backendConfig = $this->getPageData('setup_authentication_backend'); + $skip = $backendConfig['backend'] !== 'ldap'; + } elseif ($newPage->getName() === 'setup_config_db_resource') { + $authData = $this->getPageData('setup_authentication_type'); + $configData = $this->getPageData('setup_general_config'); + $skip = $authData['type'] === 'db' || $configData['global_config_backend'] !== 'db'; + } elseif (in_array($newPage->getName(), array('setup_auth_db_creation', 'setup_config_db_creation'))) { + if ( + ($newPage->getName() === 'setup_auth_db_creation' || $this->hasPageData('setup_config_db_resource')) + && (($config = $this->getPageData('setup_auth_db_resource')) !== null + || ($config = $this->getPageData('setup_config_db_resource')) !== null) + && !$config['skip_validation'] + ) { $db = new DbTool($config); try { $db->connectToDb(); // Are we able to login on the database? - if (array_search(key($this->databaseTables), $db->listTables()) === false) { - // In case the database schema does not yet exist the user - // needs the privileges to create and setup the database + if (array_search(reset($this->databaseTables), $db->listTables(), true) === false) { + // In case the database schema does not yet exist the + // user needs the privileges to setup the database $skip = $db->checkPrivileges($this->databaseSetupPrivileges, $this->databaseTables); } else { // In case the database schema exists the user needs the required privileges @@ -211,7 +321,12 @@ class WebWizard extends Wizard implements SetupWizard $db->connectToHost(); // Are we able to login on the server? // It is not possible to reliably determine whether a database exists or not if a user can't // log in to the database, so we just require the user to be able to create the database - $skip = $db->checkPrivileges($this->databaseSetupPrivileges, $this->databaseTables); + $skip = $db->checkPrivileges( + array_unique( + array_merge($this->databaseCreationPrivileges, $this->databaseSetupPrivileges) + ), + $this->databaseTables + ); } catch (PDOException $_) { // We are NOT able to login on the server.. } @@ -221,27 +336,13 @@ class WebWizard extends Wizard implements SetupWizard } } - if ($skip) { - if ($this->hasPageData($newPage->getName())) { - $pageData = & $this->getPageData(); - unset($pageData[$newPage->getName()]); - } - - $pages = $this->getPages(); - if ($this->getDirection() === static::FORWARD) { - $nextPage = $pages[array_search($newPage, $pages, true) + 1]; - $newPage = $this->getNewPage($nextPage->getName(), $newPage); - } else { // $this->getDirection() === static::BACKWARD - $previousPage = $pages[array_search($newPage, $pages, true) - 1]; - $newPage = $this->getNewPage($previousPage->getName(), $newPage); - } - } - - return $newPage; + return $skip ? $this->skipPage($newPage) : $newPage; } /** - * @see Wizard::addButtons() + * Add buttons to the given page based on its position in the page-chain + * + * @param Form $page The page to add the buttons to */ protected function addButtons(Form $page) { @@ -254,15 +355,35 @@ class WebWizard extends Wizard implements SetupWizard } elseif ($index === count($pages) - 1) { $page->getElement(static::BTN_NEXT)->setLabel(mt('setup', 'Setup Icinga Web 2', 'setup.summary.btn.finish')); } + + $authData = $this->getPageData('setup_authentication_type'); + $veto = $page->getName() === 'setup_authentication_backend' && $authData['type'] === 'db'; + if (! $veto && in_array($page->getName(), array( + 'setup_authentication_backend', + 'setup_auth_db_resource', + 'setup_config_db_resource', + 'setup_ldap_resource', + 'setup_monitoring_ido' + ))) { + $page->addElement( + 'submit', + 'backend_validation', + array( + 'ignore' => true, + 'label' => t('Validate Configuration'), + 'decorators' => array('ViewHelper') + ) + ); + $page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation')); + } } /** - * @see Wizard::clearSession() + * Clear the session being used by this wizard and drop the setup token */ public function clearSession() { parent::clearSession(); - $this->getPage('setup_modules')->clearSession(); $tokenPath = Config::resolvePath('setup.token'); if (file_exists($tokenPath)) { @@ -271,71 +392,131 @@ class WebWizard extends Wizard implements SetupWizard } /** - * @see SetupWizard::getSetup() + * Return the setup for this wizard + * + * @return Setup */ public function getSetup() { $pageData = $this->getPageData(); $setup = new Setup(); - if (isset($pageData['setup_db_resource']) - && ! $pageData['setup_db_resource']['skip_validation'] - && (false === isset($pageData['setup_database_creation']) - || ! $pageData['setup_database_creation']['skip_validation'] + if ( + isset($pageData['setup_auth_db_resource']) + && !$pageData['setup_auth_db_resource']['skip_validation'] + && (! isset($pageData['setup_auth_db_creation']) + || !$pageData['setup_auth_db_creation']['skip_validation'] ) ) { $setup->addStep( new DatabaseStep(array( 'tables' => $this->databaseTables, 'privileges' => $this->databaseUsagePrivileges, - 'resourceConfig' => $pageData['setup_db_resource'], - 'adminName' => isset($pageData['setup_database_creation']['username']) - ? $pageData['setup_database_creation']['username'] + 'resourceConfig' => $pageData['setup_auth_db_resource'], + 'adminName' => isset($pageData['setup_auth_db_creation']['username']) + ? $pageData['setup_auth_db_creation']['username'] : null, - 'adminPassword' => isset($pageData['setup_database_creation']['password']) - ? $pageData['setup_database_creation']['password'] - : null + 'adminPassword' => isset($pageData['setup_auth_db_creation']['password']) + ? $pageData['setup_auth_db_creation']['password'] + : null, + 'schemaPath' => Config::module('setup') + ->get('schema', 'path', Icinga::app()->getBaseDir('etc' . DIRECTORY_SEPARATOR . 'schema')) + )) + ); + } elseif ( + isset($pageData['setup_config_db_resource']) + && !$pageData['setup_config_db_resource']['skip_validation'] + && (! isset($pageData['setup_config_db_creation']) + || !$pageData['setup_config_db_creation']['skip_validation'] + ) + ) { + $setup->addStep( + new DatabaseStep(array( + 'tables' => $this->databaseTables, + 'privileges' => $this->databaseUsagePrivileges, + 'resourceConfig' => $pageData['setup_config_db_resource'], + 'adminName' => isset($pageData['setup_config_db_creation']['username']) + ? $pageData['setup_config_db_creation']['username'] + : null, + 'adminPassword' => isset($pageData['setup_config_db_creation']['password']) + ? $pageData['setup_config_db_creation']['password'] + : null, + 'schemaPath' => Config::module('setup') + ->get('schema', 'path', Icinga::app()->getBaseDir('etc' . DIRECTORY_SEPARATOR . 'schema')) )) ); } $setup->addStep( new GeneralConfigStep(array( - 'generalConfig' => $pageData['setup_general_config'], - 'preferencesType' => $pageData['setup_preferences_type']['type'], - 'preferencesResource' => isset($pageData['setup_db_resource']['name']) - ? $pageData['setup_db_resource']['name'] - : null + 'generalConfig' => $pageData['setup_general_config'], + 'resourceName' => isset($pageData['setup_auth_db_resource']['name']) + ? $pageData['setup_auth_db_resource']['name'] + : (isset($pageData['setup_config_db_resource']['name']) + ? $pageData['setup_config_db_resource']['name'] + : null + ) )) ); $adminAccountType = $pageData['setup_admin_account']['user_type']; - $adminAccountData = array('username' => $pageData['setup_admin_account'][$adminAccountType]); - if ($adminAccountType === 'new_user' && ! $pageData['setup_db_resource']['skip_validation'] - && (false === isset($pageData['setup_database_creation']) - || ! $pageData['setup_database_creation']['skip_validation'] - ) - ) { - $adminAccountData['resourceConfig'] = $pageData['setup_db_resource']; - $adminAccountData['password'] = $pageData['setup_admin_account']['new_user_password']; + if ($adminAccountType === 'user_group') { + $adminAccountData = array('groupname' => $pageData['setup_admin_account'][$adminAccountType]); + } else { + $adminAccountData = array('username' => $pageData['setup_admin_account'][$adminAccountType]); + if ($adminAccountType === 'new_user' && !$pageData['setup_auth_db_resource']['skip_validation'] + && (! isset($pageData['setup_auth_db_creation']) + || !$pageData['setup_auth_db_creation']['skip_validation'] + ) + ) { + $adminAccountData['resourceConfig'] = $pageData['setup_auth_db_resource']; + $adminAccountData['password'] = $pageData['setup_admin_account']['new_user_password']; + } } $authType = $pageData['setup_authentication_type']['type']; $setup->addStep( new AuthenticationStep(array( 'adminAccountData' => $adminAccountData, 'backendConfig' => $pageData['setup_authentication_backend'], - 'resourceName' => $authType === 'db' ? $pageData['setup_db_resource']['name'] : ( + 'resourceName' => $authType === 'db' ? $pageData['setup_auth_db_resource']['name'] : ( $authType === 'ldap' ? $pageData['setup_ldap_resource']['name'] : null ) )) ); - if (isset($pageData['setup_db_resource']) || isset($pageData['setup_ldap_resource'])) { + if ($authType !== 'external') { + $setup->addStep( + new UserGroupStep(array( + 'backendConfig' => $pageData['setup_authentication_backend'], + 'groupConfig' => isset($pageData['setup_usergroup_backend']) + ? $pageData['setup_usergroup_backend'] + : null, + 'resourceName' => $authType === 'db' + ? $pageData['setup_auth_db_resource']['name'] + : $pageData['setup_ldap_resource']['name'], + 'resourceConfig' => $authType === 'db' + ? $pageData['setup_auth_db_resource'] + : null, + 'username' => $authType === 'db' + ? $pageData['setup_admin_account'][$adminAccountType] + : null + )) + ); + } + + if ( + isset($pageData['setup_auth_db_resource']) + || isset($pageData['setup_config_db_resource']) + || isset($pageData['setup_ldap_resource']) + ) { $setup->addStep( new ResourceStep(array( - 'dbResourceConfig' => isset($pageData['setup_db_resource']) - ? array_diff_key($pageData['setup_db_resource'], array('skip_validation' => null)) - : null, + 'dbResourceConfig' => isset($pageData['setup_auth_db_resource']) + ? array_diff_key($pageData['setup_auth_db_resource'], array('skip_validation' => null)) + : (isset($pageData['setup_config_db_resource']) + ? array_diff_key($pageData['setup_config_db_resource'], array('skip_validation' => null)) + : null + ), 'ldapResourceConfig' => isset($pageData['setup_ldap_resource']) ? array_diff_key($pageData['setup_ldap_resource'], array('skip_validation' => null)) : null @@ -343,216 +524,175 @@ class WebWizard extends Wizard implements SetupWizard ); } - $configDir = $this->getConfigDir(); - $setup->addStep( - new MakeDirStep( - array( - $configDir . '/modules', - $configDir . '/preferences', - $configDir . '/enabledModules' - ), - 0775 - ) - ); - - foreach ($this->getPage('setup_modules')->setPageData($this->getPageData())->getWizards() as $wizard) { - if ($wizard->isFinished()) { + foreach ($this->getWizards() as $wizard) { + if ($wizard->isComplete()) { $setup->addSteps($wizard->getSetup()->getSteps()); } } + $setup->addStep(new EnableModuleStep(array_keys($this->getPage('setup_modules')->getCheckedModules()))); + return $setup; } /** - * @see SetupWizard::getRequirements() + * Return the requirements of this wizard + * + * @return RequirementSet */ - public function getRequirements() + public function getRequirements($skipModules = false) { - $requirements = new Requirements(); + $set = new RequirementSet(); - $phpVersion = Platform::getPhpVersion(); - $requirements->addMandatory( - mt('setup', 'PHP Version'), - mt( + $set->add(new PhpVersionRequirement(array( + 'condition' => array('>=', '5.3.2'), + 'description' => mt( 'setup', 'Running Icinga Web 2 requires PHP version 5.3.2. Advanced features' . ' like the built-in web server require PHP version 5.4.' - ), - version_compare($phpVersion, '5.3.2', '>='), - sprintf(mt('setup', 'You are running PHP version %s.'), $phpVersion) - ); + ) + ))); - $defaultTimezone = Platform::getPhpConfig('date.timezone'); - $requirements->addMandatory( - mt('setup', 'Default Timezone'), - sprintf( + $set->add(new PhpConfigRequirement(array( + 'condition' => array('date.timezone', true), + 'title' => mt('setup', 'Default Timezone'), + 'description' => sprintf( mt('setup', 'It is required that a default timezone has been set using date.timezone in %s.'), php_ini_loaded_file() ?: 'php.ini' ), - $defaultTimezone, - $defaultTimezone ? sprintf(mt('setup', 'Your default timezone is: %s'), $defaultTimezone) : ( - mt('setup', 'You did not define a default timezone.') - ) - ); + ))); - $requirements->addOptional( - mt('setup', 'Linux Platform'), - mt( + $set->add(new OSRequirement(array( + 'optional' => true, + 'condition' => 'linux', + 'description' => mt( 'setup', 'Icinga Web 2 is developed for and tested on Linux. While we cannot' . ' guarantee they will, other platforms may also perform as well.' - ), - Platform::isLinux(), - sprintf(mt('setup', 'You are running PHP on a %s system.'), Platform::getOperatingSystemName()) - ); - - $requirements->addMandatory( - mt('setup', 'PHP Module: OpenSSL'), - mt('setup', 'The PHP module for OpenSSL is required to generate cryptographically safe password salts.'), - Platform::extensionLoaded('openssl'), - Platform::extensionLoaded('openssl') ? mt('setup', 'The PHP module for OpenSSL is available.') : ( - mt('setup', 'The PHP module for OpenSSL is missing.') ) - ); + ))); - $requirements->addOptional( - mt('setup', 'PHP Module: JSON'), - mt('setup', 'The JSON module for PHP is required for various export functionalities as well as APIs.'), - Platform::extensionLoaded('json'), - Platform::extensionLoaded('json') ? mt('setup', 'The PHP module JSON is available.') : ( - mt('setup', 'The PHP module JSON is missing.') + $set->add(new PhpModuleRequirement(array( + 'condition' => 'OpenSSL', + 'description' => mt( + 'setup', + 'The PHP module for OpenSSL is required to generate cryptographically safe password salts.' ) - ); + ))); - $requirements->addOptional( - mt('setup', 'PHP Module: LDAP'), - mt('setup', 'If you\'d like to authenticate users using LDAP the corresponding PHP module is required'), - Platform::extensionLoaded('ldap'), - Platform::extensionLoaded('ldap') ? mt('setup', 'The PHP module LDAP is available') : ( - mt('setup', 'The PHP module LDAP is missing') + $set->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'JSON', + 'description' => mt( + 'setup', + 'The JSON module for PHP is required for various export functionalities as well as APIs.' ) - ); + ))); - $requirements->addOptional( - mt('setup', 'PHP Module: INTL'), - mt( + $set->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'LDAP', + 'description' => mt( + 'setup', + 'If you\'d like to authenticate users using LDAP the corresponding PHP module is required.' + ) + ))); + + $set->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'INTL', + 'description' => mt( 'setup', 'If you want your users to benefit from language, timezone and date/time' . ' format negotiation, the INTL module for PHP is required.' - ), - Platform::extensionLoaded('intl'), - Platform::extensionLoaded('intl') ? mt('setup', 'The PHP module INTL is available') : ( - mt('setup', 'The PHP module INTL is missing') ) - ); + ))); // TODO(6172): Remove this requirement once we do not ship dompdf with Icinga Web 2 anymore - $requirements->addOptional( - mt('setup', 'PHP Module: DOM'), - mt('setup', 'To be able to export views and reports to PDF, the DOM module for PHP is required.'), - Platform::extensionLoaded('dom'), - Platform::extensionLoaded('dom') ? mt('setup', 'The PHP module DOM is available') : ( - mt('setup', 'The PHP module DOM is missing') - ) - ); - - $requirements->addOptional( - mt('setup', 'PHP Module: GD'), - mt( + $set->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'DOM', + 'description' => mt( 'setup', - 'In case you want icons and graphs being exported to PDF' - . ' as well, you\'ll need the GD extension for PHP.' - ), - Platform::extensionLoaded('gd'), - Platform::extensionLoaded('gd') ? mt('setup', 'The PHP module GD is available') : ( - mt('setup', 'The PHP module GD is missing') + 'To be able to export views and reports to PDF, the DOM module for PHP is required.' ) - ); + ))); - $requirements->addOptional( - mt('setup', 'PHP Module: PDO-MySQL'), - mt( + $set->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'GD', + 'description' => mt( 'setup', - 'Is Icinga Web 2 supposed to access a MySQL database the PDO-MySQL module for PHP is required.' - ), - Platform::extensionLoaded('mysql'), - Platform::extensionLoaded('mysql') ? mt('setup', 'The PHP module PDO-MySQL is available.') : ( - mt('setup', 'The PHP module PDO-MySQL is missing.') + 'In case you want views being exported to PDF, you\'ll need the GD extension for PHP.' ) - ); + ))); - $requirements->addOptional( - mt('setup', 'PHP Module: PDO-PostgreSQL'), - mt( + $set->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'Imagick', + 'description' => mt( 'setup', - 'Is Icinga Web 2 supposed to access a PostgreSQL database' - . ' the PDO-PostgreSQL module for PHP is required.' - ), - Platform::extensionLoaded('pgsql'), - Platform::extensionLoaded('pgsql') ? mt('setup', 'The PHP module PDO-PostgreSQL is available.') : ( - mt('setup', 'The PHP module PDO-PostgreSQL is missing.') + 'In case you want graphs being exported to PDF as well, you\'ll need the ImageMagick extension for PHP.' ) - ); + ))); - $mysqlAdapterFound = Platform::zendClassExists('Zend_Db_Adapter_Pdo_Mysql'); - $requirements->addOptional( - mt('setup', 'Zend Database Adapter For MySQL'), - mt('setup', 'The Zend database adapter for MySQL is required to access a MySQL database.'), - $mysqlAdapterFound, - $mysqlAdapterFound ? mt('setup', 'The Zend database adapter for MySQL is available.') : ( - mt('setup', 'The Zend database adapter for MySQL is missing.') - ) - ); - - $pgsqlAdapterFound = Platform::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql'); - $requirements->addOptional( - mt('setup', 'Zend Database Adapter For PostgreSQL'), - mt('setup', 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.'), - $pgsqlAdapterFound, - $pgsqlAdapterFound ? mt('setup', 'The Zend database adapter for PostgreSQL is available.') : ( - mt('setup', 'The Zend database adapter for PostgreSQL is missing.') - ) - ); - - $configDir = $this->getConfigDir(); - $requirements->addMandatory( - mt('setup', 'Writable Config Directory'), - mt( + $mysqlSet = new RequirementSet(true); + $mysqlSet->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'mysql', + 'alias' => 'PDO-MySQL', + 'description' => mt( 'setup', - 'The Icinga Web 2 configuration directory defaults to "/etc/icingaweb", if' . + 'To store users or preferences in a MySQL database the PDO-MySQL module for PHP is required.' + ) + ))); + $mysqlSet->add(new ClassRequirement(array( + 'optional' => true, + 'condition' => 'Zend_Db_Adapter_Pdo_Mysql', + 'alias' => mt('setup', 'Zend database adapter for MySQL'), + 'description' => mt( + 'setup', + 'The Zend database adapter for MySQL is required to access a MySQL database.' + ) + ))); + $set->merge($mysqlSet); + + $pgsqlSet = new RequirementSet(true); + $pgsqlSet->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'pgsql', + 'alias' => 'PDO-PostgreSQL', + 'description' => mt( + 'setup', + 'To store users or preferences in a PostgreSQL database the PDO-PostgreSQL module for PHP is required.' + ) + ))); + $pgsqlSet->add(new ClassRequirement(array( + 'optional' => true, + 'condition' => 'Zend_Db_Adapter_Pdo_Pgsql', + 'alias' => mt('setup', 'Zend database adapter for PostgreSQL'), + 'description' => mt( + 'setup', + 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.' + ) + ))); + $set->merge($pgsqlSet); + + $set->add(new ConfigDirectoryRequirement(array( + 'condition' => Icinga::app()->getConfigDir(), + 'description' => mt( + 'setup', + 'The Icinga Web 2 configuration directory defaults to "/etc/icingaweb2", if' . ' not explicitly set in the environment variable "ICINGAWEB_CONFIGDIR".' - ), - is_writable($configDir), - sprintf( - is_writable($configDir) ? mt('setup', 'The current configuration directory is writable: %s') : ( - mt('setup', 'The current configuration directory is not writable: %s') - ), - $configDir ) - ); + ))); - foreach ($this->getPage('setup_modules')->setPageData($this->getPageData())->getWizards() as $wizard) { - $requirements->merge($wizard->getRequirements()->allOptional()); + if (! $skipModules) { + foreach ($this->getWizards() as $wizard) { + $set->merge($wizard->getRequirements()); + } } - return $requirements; - } - - /** - * Return the configuration directory of Icinga Web 2 - * - * @return string - */ - protected function getConfigDir() - { - if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { - $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; - } else { - $configDir = '/etc/icingaweb'; - } - - $canonical = realpath($configDir); - return $canonical ? $canonical : $configDir; + return $set; } } diff --git a/modules/setup/library/Setup/Webserver.php b/modules/setup/library/Setup/Webserver.php index 2449f9b9e..3647e162e 100644 --- a/modules/setup/library/Setup/Webserver.php +++ b/modules/setup/library/Setup/Webserver.php @@ -1,6 +1,5 @@ assertFalse($emptySet->fulfilled(), 'A empty mandatory set of type and is fulfilled'); + + $singleTrueSet = new RequirementSet(); + $singleTrueSet->add(new TrueRequirement()); + $this->assertTrue( + $singleTrueSet->fulfilled(), + 'A mandatory set of type and with a single TrueRequirement is not fulfilled' + ); + + $singleFalseSet = new RequirementSet(); + $singleFalseSet->add(new FalseRequirement()); + $this->assertFalse( + $singleFalseSet->fulfilled(), + 'A mandatory set of type and with a single FalseRequirement is fulfilled' + ); + + $mixedSet = new RequirementSet(); + $mixedSet->add(new TrueRequirement()); + $mixedSet->add(new FalseRequirement()); + $this->assertFalse( + $mixedSet->fulfilled(), + 'A mandatory set of type and with one True- and one FalseRequirement is fulfilled' + ); + } + + public function testFlatOptionalRequirementsOfTypeAnd() + { + $emptySet = new RequirementSet(true); + $this->assertTrue($emptySet->fulfilled(), 'A empty optional set of type and is not fulfilled'); + + $singleTrueSet = new RequirementSet(true); + $singleTrueSet->add(new TrueRequirement()); + $this->assertTrue( + $singleTrueSet->fulfilled(), + 'A optional set of type and with a single TrueRequirement is not fulfilled' + ); + + $singleFalseSet = new RequirementSet(true); + $singleFalseSet->add(new FalseRequirement()); + $this->assertTrue( + $singleFalseSet->fulfilled(), + 'A optional set of type and with a single FalseRequirement is not fulfilled' + ); + + $mixedSet = new RequirementSet(true); + $mixedSet->add(new TrueRequirement()); + $mixedSet->add(new FalseRequirement()); + $this->assertTrue( + $mixedSet->fulfilled(), + 'A optional set of type and with one True- and one FalseRequirement is not fulfilled' + ); + } + + public function testFlatMixedRequirementsOfTypeAnd() + { + $mandatoryOptionalTrueSet = new RequirementSet(); + $mandatoryOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $mandatoryOptionalTrueSet->add(new FalseRequirement()); + $this->assertFalse( + $mandatoryOptionalTrueSet->fulfilled(), + 'A mandatory set of type and with one optional True- and one mandatory FalseRequirement is fulfilled' + ); + + $mandatoryOptionalFalseSet = new RequirementSet(); + $mandatoryOptionalFalseSet->add(new TrueRequirement()); + $mandatoryOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $this->assertTrue( + $mandatoryOptionalFalseSet->fulfilled(), + 'A mandatory set of type and with one mandatory True- and one optional FalseRequirement is not fulfilled' + ); + + $optionalOptionalTrueSet = new RequirementSet(true); + $optionalOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $optionalOptionalTrueSet->add(new FalseRequirement()); + $this->assertTrue( + $optionalOptionalTrueSet->fulfilled(), + 'A optional set of type and with one optional True- and one mandatory FalseRequirement is not fulfilled' + ); + + $optionalOptionalFalseSet = new RequirementSet(true); + $optionalOptionalFalseSet->add(new TrueRequirement()); + $optionalOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $this->assertTrue( + $optionalOptionalFalseSet->fulfilled(), + 'A optional set of type and with one mandatory True- and one optional FalseRequirement is not fulfilled' + ); + } + + public function testFlatMandatoryRequirementsOfTypeOr() + { + $emptySet = new RequirementSet(false, RequirementSet::MODE_OR); + $this->assertFalse($emptySet->fulfilled(), 'A empty mandatory set of type or is fulfilled'); + + $singleTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $singleTrueSet->add(new TrueRequirement()); + $this->assertTrue( + $singleTrueSet->fulfilled(), + 'A mandatory set of type or with a single TrueRequirement is not fulfilled' + ); + + $singleFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $singleFalseSet->add(new FalseRequirement()); + $this->assertFalse( + $singleFalseSet->fulfilled(), + 'A mandatory set of type or with a single FalseRequirement is fulfilled' + ); + + $mixedSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mixedSet->add(new TrueRequirement()); + $mixedSet->add(new FalseRequirement()); + $this->assertTrue( + $mixedSet->fulfilled(), + 'A mandatory set of type or with one True- and one FalseRequirement is not fulfilled' + ); + } + + public function testFlatOptionalRequirementsOfTypeOr() + { + $emptySet = new RequirementSet(true, RequirementSet::MODE_OR); + $this->assertTrue($emptySet->fulfilled(), 'A empty optional set of type or is not fulfilled'); + + $singleTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $singleTrueSet->add(new TrueRequirement()); + $this->assertTrue( + $singleTrueSet->fulfilled(), + 'A optional set of type or with a single TrueRequirement is not fulfilled' + ); + + $singleFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $singleFalseSet->add(new FalseRequirement()); + $this->assertTrue( + $singleFalseSet->fulfilled(), + 'A optional set of type or with a single FalseRequirement is not fulfilled' + ); + + $mixedSet = new RequirementSet(true, RequirementSet::MODE_OR); + $mixedSet->add(new TrueRequirement()); + $mixedSet->add(new FalseRequirement()); + $this->assertTrue( + $mixedSet->fulfilled(), + 'A optional set of type or with one True- and one FalseRequirement is not fulfilled' + ); + } + + public function testFlatMixedRequirementsOfTypeOr() + { + $mandatoryOptionalTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $mandatoryOptionalTrueSet->add(new FalseRequirement()); + $this->assertTrue( + $mandatoryOptionalTrueSet->fulfilled(), + 'A mandatory set of type or with one optional True- and one mandatory FalseRequirement is not fulfilled' + ); + + $mandatoryOptionalFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryOptionalFalseSet->add(new TrueRequirement()); + $mandatoryOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $this->assertTrue( + $mandatoryOptionalFalseSet->fulfilled(), + 'A mandatory set of type or with one mandatory True- and one optional FalseRequirement is not fulfilled' + ); + + $optionalOptionalTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $optionalOptionalTrueSet->add(new FalseRequirement()); + $this->assertTrue( + $optionalOptionalTrueSet->fulfilled(), + 'A optional set of type or with one optional True- and one mandatory FalseRequirement is not fulfilled' + ); + + $optionalOptionalFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalFalseSet->add(new TrueRequirement()); + $optionalOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $this->assertTrue( + $optionalOptionalFalseSet->fulfilled(), + 'A optional set of type or with one mandatory True- and one optional FalseRequirement is not fulfilled' + ); + } + + public function testNestedMandatoryRequirementsOfTypeAnd() + { + $trueSet = new RequirementSet(); + $trueSet->add(new TrueRequirement()); + $falseSet = new RequirementSet(); + $falseSet->add(new FalseRequirement()); + + $nestedTrueSet = new RequirementSet(); + $nestedTrueSet->merge($trueSet); + $this->assertTrue( + $nestedTrueSet->fulfilled(), + 'A nested mandatory set of type and with one mandatory TrueRequirement is not fulfilled' + ); + + $nestedFalseSet = new RequirementSet(); + $nestedFalseSet->merge($falseSet); + $this->assertFalse( + $nestedFalseSet->fulfilled(), + 'A nested mandatory set of type and with one mandatory FalseRequirement is fulfilled' + ); + + $nestedMixedSet = new RequirementSet(); + $nestedMixedSet->merge($trueSet); + $nestedMixedSet->merge($falseSet); + $this->assertFalse( + $nestedMixedSet->fulfilled(), + 'Two nested mandatory sets of type and with one mandatory True- and' + . ' one mandatory FalseRequirement respectively are fulfilled' + ); + } + + public function testNestedOptionalRequirementsOfTypeAnd() + { + $trueSet = new RequirementSet(true); + $trueSet->add(new TrueRequirement()); + $falseSet = new RequirementSet(true); + $falseSet->add(new FalseRequirement()); + + $nestedTrueSet = new RequirementSet(true); + $nestedTrueSet->merge($trueSet); + $this->assertTrue( + $nestedTrueSet->fulfilled(), + 'A nested optional set of type and with one mandatory TrueRequirement is not fulfilled' + ); + + $nestedFalseSet = new RequirementSet(true); + $nestedFalseSet->merge($falseSet); + $this->assertTrue( + $nestedFalseSet->fulfilled(), + 'A nested optional set of type and with one mandatory FalseRequirement is not fulfilled' + ); + + $nestedMixedSet = new RequirementSet(true); + $nestedMixedSet->merge($trueSet); + $nestedMixedSet->merge($falseSet); + $this->assertTrue( + $nestedMixedSet->fulfilled(), + 'Two nested optional sets of type and with one mandatory True- and' + . ' one mandatory FalseRequirement respectively are not fulfilled' + ); + } + + public function testNestedMixedRequirementsOfTypeAnd() + { + $mandatoryMandatoryTrueSet = new RequirementSet(); + $mandatoryMandatoryTrueSet->add(new TrueRequirement()); + $mandatoryOptionalTrueSet = new RequirementSet(); + $mandatoryOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $mandatoryMandatoryFalseSet = new RequirementSet(); + $mandatoryMandatoryFalseSet->add(new FalseRequirement()); + $mandatoryOptionalFalseSet = new RequirementSet(); + $mandatoryOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $optionalMandatoryTrueSet = new RequirementSet(true); + $optionalMandatoryTrueSet->add(new TrueRequirement()); + $optionalOptionalTrueSet = new RequirementSet(true); + $optionalOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $optionalMandatoryFalseSet = new RequirementSet(true); + $optionalMandatoryFalseSet->add(new FalseRequirement()); + $optionalOptionalFalseSet = new RequirementSet(true); + $optionalOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + + $mandatoryMandatoryOptionalTrueSet = new RequirementSet(); + $mandatoryMandatoryOptionalTrueSet->merge($mandatoryOptionalTrueSet); + $mandatoryMandatoryOptionalTrueSet->merge($mandatoryMandatoryFalseSet); + $this->assertFalse( + $mandatoryMandatoryOptionalTrueSet->fulfilled(), + 'A mandatory set of type and with two nested mandatory sets of type and where one has a optional' + . ' TrueRequirement and the other one has a mandatory FalseRequirement is fulfilled' + ); + + $mandatoryMandatoryOptionalFalseSet = new RequirementSet(); + $mandatoryMandatoryOptionalFalseSet->merge($mandatoryOptionalFalseSet); + $mandatoryMandatoryOptionalFalseSet->merge($mandatoryMandatoryTrueSet); + $this->assertTrue( + $mandatoryMandatoryOptionalFalseSet->fulfilled(), + 'A mandatory set of type and with two nested mandatory sets of type and where one has a mandatory' + . ' TrueRequirement and the other one has a optional FalseRequirement is not fulfilled' + ); + + $optionalOptionalOptionalTrueSet = new RequirementSet(true); + $optionalOptionalOptionalTrueSet->merge($optionalOptionalTrueSet); + $optionalOptionalOptionalTrueSet->merge($optionalMandatoryFalseSet); + $this->assertTrue( + $optionalOptionalOptionalTrueSet->fulfilled(), + 'A optional set of type and with two nested optional sets of type and where one has a optional' + . ' TrueRequirement and the other one has a mandatory FalseRequirement is not fulfilled' + ); + + $optionalOptionalOptionalFalseSet = new RequirementSet(true); + $optionalOptionalOptionalFalseSet->merge($optionalOptionalFalseSet); + $optionalOptionalOptionalFalseSet->merge($optionalMandatoryTrueSet); + $this->assertTrue( + $optionalOptionalOptionalFalseSet->fulfilled(), + 'A optional set of type and with two nested optional sets of type and where one has a mandatory' + . ' TrueRequirement and the other one has a optional FalseRequirement is not fulfilled' + ); + } + + public function testNestedMandatoryRequirementsOfTypeOr() + { + $trueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $trueSet->add(new TrueRequirement()); + $falseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $falseSet->add(new FalseRequirement()); + + $nestedTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $nestedTrueSet->merge($trueSet); + $this->assertTrue( + $nestedTrueSet->fulfilled(), + 'A nested mandatory set of type or with one mandatory TrueRequirement is not fulfilled' + ); + + $nestedFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $nestedFalseSet->merge($falseSet); + $this->assertFalse( + $nestedFalseSet->fulfilled(), + 'A nested mandatory set of type or with one mandatory FalseRequirement is fulfilled' + ); + + $nestedMixedSet = new RequirementSet(false, RequirementSet::MODE_OR); + $nestedMixedSet->merge($trueSet); + $nestedMixedSet->merge($falseSet); + $this->assertTrue( + $nestedMixedSet->fulfilled(), + 'Two nested mandatory sets of type or with one mandatory True- and' + . ' one mandatory FalseRequirement respectively are not fulfilled' + ); + } + + public function testNestedOptionalRequirementsOfTypeOr() + { + $trueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $trueSet->add(new TrueRequirement()); + $falseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $falseSet->add(new FalseRequirement()); + + $nestedTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $nestedTrueSet->merge($trueSet); + $this->assertTrue( + $nestedTrueSet->fulfilled(), + 'A nested optional set of type or with one mandatory TrueRequirement is not fulfilled' + ); + + $nestedFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $nestedFalseSet->merge($falseSet); + $this->assertTrue( + $nestedFalseSet->fulfilled(), + 'A nested optional set of type or with one mandatory FalseRequirement is not fulfilled' + ); + + $nestedMixedSet = new RequirementSet(true, RequirementSet::MODE_OR); + $nestedMixedSet->merge($trueSet); + $nestedMixedSet->merge($falseSet); + $this->assertTrue( + $nestedMixedSet->fulfilled(), + 'Two nested optional sets of type or with one mandatory True- and' + . ' one mandatory FalseRequirement respectively are not fulfilled' + ); + } + + public function testNestedMixedRequirementsOfTypeOr() + { + $mandatoryMandatoryTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryMandatoryTrueSet->add(new TrueRequirement()); + $mandatoryOptionalTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $mandatoryMandatoryFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryMandatoryFalseSet->add(new FalseRequirement()); + $mandatoryOptionalFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + $optionalMandatoryTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalMandatoryTrueSet->add(new TrueRequirement()); + $optionalOptionalTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalTrueSet->add(new TrueRequirement(array('optional' => true))); + $optionalMandatoryFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalMandatoryFalseSet->add(new FalseRequirement()); + $optionalOptionalFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalFalseSet->add(new FalseRequirement(array('optional' => true))); + + $mandatoryMandatoryOptionalTrueSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryMandatoryOptionalTrueSet->merge($mandatoryOptionalTrueSet); + $mandatoryMandatoryOptionalTrueSet->merge($mandatoryMandatoryFalseSet); + $this->assertTrue($mandatoryMandatoryOptionalTrueSet->fulfilled()); + + $mandatoryMandatoryOptionalFalseSet = new RequirementSet(false, RequirementSet::MODE_OR); + $mandatoryMandatoryOptionalFalseSet->merge($mandatoryOptionalFalseSet); + $mandatoryMandatoryOptionalFalseSet->merge($mandatoryMandatoryTrueSet); + $this->assertTrue($mandatoryMandatoryOptionalFalseSet->fulfilled()); + + $optionalOptionalOptionalTrueSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalOptionalTrueSet->merge($optionalOptionalTrueSet); + $optionalOptionalOptionalTrueSet->merge($optionalMandatoryFalseSet); + $this->assertTrue($optionalOptionalOptionalTrueSet->fulfilled()); + + $optionalOptionalOptionalFalseSet = new RequirementSet(true, RequirementSet::MODE_OR); + $optionalOptionalOptionalFalseSet->merge($optionalOptionalFalseSet); + $optionalOptionalOptionalFalseSet->merge($optionalMandatoryTrueSet); + $this->assertTrue($optionalOptionalOptionalFalseSet->fulfilled()); + } + + public function testNestedMandatoryRequirementsOfDifferentTypes() + { + $true = new TrueRequirement(); + $false = new FalseRequirement(); + + $level1And = new RequirementSet(); + $level2FirstOr = new RequirementSet(false, RequirementSet::MODE_OR); + $level2SecondOr = new RequirementSet(false, RequirementSet::MODE_OR); + $level1And->merge($level2FirstOr)->merge($level2SecondOr); + $level3FirstAnd = new RequirementSet(); + $level3SecondAnd = new RequirementSet(); + $level2FirstOr->merge($level3FirstAnd)->merge($level3SecondAnd); + $level2SecondOr->merge($level3FirstAnd)->merge($level3SecondAnd); + $level3FirstAnd->add($true)->add($true); + $level3SecondAnd->add($false)->add($true); + $this->assertTrue($level1And->fulfilled()); + + $level1Or = new RequirementSet(false, RequirementSet::MODE_OR); + $level2FirstAnd = new RequirementSet(); + $level2SecondAnd = new RequirementSet(); + $level1Or->merge($level2FirstAnd)->merge($level2SecondAnd); + $level3FirstOr = new RequirementSet(false, RequirementSet::MODE_OR); + $level3SecondOr = new RequirementSet(false, RequirementSet::MODE_OR); + $level2FirstAnd->merge($level3FirstOr)->merge($level3SecondOr); + $level2SecondAnd->merge($level3FirstOr)->merge($level3SecondOr); + $level3FirstOr->add($false); + $level3SecondOr->add($true); + $this->assertFalse($level1Or->fulfilled()); + } + + public function testNestedOptionalRequirementsOfDifferentTypes() + { + $true = new TrueRequirement(); + $false = new FalseRequirement(); + + $level1And = new RequirementSet(); + $level2FirstAnd = new RequirementSet(true); + $level2SecondAnd = new RequirementSet(true); + $level1And->merge($level2FirstAnd)->merge($level2SecondAnd); + $level3FirstOr = new RequirementSet(true, RequirementSet::MODE_OR); + $level3SecondOr = new RequirementSet(true, RequirementSet::MODE_OR); + $level2FirstAnd->merge($level3FirstOr)->merge($level3SecondOr); + $level2SecondAnd->merge($level3FirstOr)->merge($level3SecondOr); + $level3FirstOr->add($false); + $level3SecondOr->add($false); + $this->assertFalse($level1And->fulfilled()); + $this->assertTrue($level2FirstAnd->fulfilled()); + $this->assertTrue($level2SecondAnd->fulfilled()); + + $level1Or = new RequirementSet(false, RequirementSet::MODE_OR); + $level2FirstOr = new RequirementSet(true, RequirementSet::MODE_OR); + $level2SecondOr = new RequirementSet(true, RequirementSet::MODE_OR); + $level1Or->merge($level2FirstOr)->merge($level2SecondOr); + $level3FirstAnd = new RequirementSet(true); + $level3SecondAnd = new RequirementSet(true); + $level2FirstOr->merge($level3FirstAnd)->merge($level3SecondAnd); + $level2SecondOr->merge($level3FirstAnd)->merge($level3SecondAnd); + $level3FirstAnd->add($true)->add($true); + $level3SecondAnd->add($false)->add($true); + $this->assertTrue($level1Or->fulfilled()); + } + + public function testNestedMixedRequirementsOfDifferentTypes() + { + $this->markTestIncomplete(); + } +} diff --git a/modules/test/application/clicommands/PhpCommand.php b/modules/test/application/clicommands/PhpCommand.php index dbcb58f05..59a79e0a1 100644 --- a/modules/test/application/clicommands/PhpCommand.php +++ b/modules/test/application/clicommands/PhpCommand.php @@ -1,6 +1,5 @@ validateLocaleCode($this->params->shift()); - $helper = new GettextTranslationHelper($this->app, $locale); + $helper = $this->getTranslationHelper($locale); $helper->compileIcingaTranslation(); } @@ -62,7 +61,7 @@ class CompileCommand extends TranslationCommand $module = $this->validateModuleName($this->params->shift()); $locale = $this->validateLocaleCode($this->params->shift()); - $helper = new GettextTranslationHelper($this->app, $locale); + $helper = $this->getTranslationHelper($locale); $helper->compileModuleTranslation($module); } } diff --git a/modules/translation/application/clicommands/RefreshCommand.php b/modules/translation/application/clicommands/RefreshCommand.php index 78b58f55c..7320b9651 100644 --- a/modules/translation/application/clicommands/RefreshCommand.php +++ b/modules/translation/application/clicommands/RefreshCommand.php @@ -1,6 +1,5 @@ validateLocaleCode($this->params->shift()); - $helper = new GettextTranslationHelper($this->app, $locale); + $helper = $this->getTranslationHelper($locale); $helper->updateIcingaTranslations(); } @@ -62,7 +61,7 @@ class RefreshCommand extends TranslationCommand $module = $this->validateModuleName($this->params->shift()); $locale = $this->validateLocaleCode($this->params->shift()); - $helper = new GettextTranslationHelper($this->app, $locale); + $helper = $this->getTranslationHelper($locale); $helper->updateModuleTranslations($module); } } diff --git a/modules/translation/doc/translation.md b/modules/translation/doc/translation.md index 55dc415ad..308eae6a8 100644 --- a/modules/translation/doc/translation.md +++ b/modules/translation/doc/translation.md @@ -109,7 +109,7 @@ When you are done, just save your new settings. To work with Icinga Web 2 .po files, you can open for e.g. the german icinga.po file which is located under `application/locale/de_DE/LC_MESSAGES/icinga.po`, as shown below, you will get then a full list of all available -translation strings for the core application. Each module names it's translation files `%module_name%.po`. For a +translation strings for the core application. Each module names its translation files `%module_name%.po`. For a module called __yourmodule__ the .po translation file will be named `yourmodule.po`. @@ -196,4 +196,4 @@ The last step is to compile the __yourmodule.po__ to the __yourmodule.mo__: icingacli translation compile module development ll_CC At this moment, everywhere in the module where the `Dummy` should be translated, it would returns the translated -string `Attrappe`. \ No newline at end of file +string `Attrappe`. diff --git a/modules/translation/library/Translation/Cli/TranslationCommand.php b/modules/translation/library/Translation/Cli/TranslationCommand.php index bae20cc5e..48f7d6ed2 100644 --- a/modules/translation/library/Translation/Cli/TranslationCommand.php +++ b/modules/translation/library/Translation/Cli/TranslationCommand.php @@ -1,18 +1,32 @@ app, $locale); + $helper->setConfig($this->Config()); + return $helper; + } + /** * Check whether the given locale code is valid * @@ -47,7 +61,7 @@ class TranslationCommand extends Command { $enabledModules = $this->app->getModuleManager()->listEnabledModules(); - if (!in_array($name, $enabledModules)) { + if (! in_array($name, $enabledModules)) { throw new IcingaException( 'Module with name \'%s\' not found or is not enabled', $name diff --git a/modules/translation/library/Translation/Util/GettextTranslationHelper.php b/modules/translation/library/Translation/Util/GettextTranslationHelper.php index 0d5a950e1..c3a6d206e 100644 --- a/modules/translation/library/Translation/Util/GettextTranslationHelper.php +++ b/modules/translation/library/Translation/Util/GettextTranslationHelper.php @@ -1,14 +1,14 @@ moduleMgr = $bootstrap->getModuleManager()->loadCoreModules()->loadEnabledModules(); + $this->moduleMgr = $bootstrap->getModuleManager()->loadEnabledModules(); $this->appDir = $bootstrap->getApplicationDir(); + $this->libDir = $bootstrap->getLibraryDir('Icinga'); $this->locale = $locale; } + /** + * Get the config + * + * @return Config + */ + public function getConfig() + { + return $this->config; + } + + /** + * Set the config + * + * @param Config $config + * + * @return $this + */ + public function setConfig(Config $config) + { + $this->config = $config; + return $this; + } + /** * Update the translation table for the main application */ @@ -212,7 +250,12 @@ class GettextTranslationHelper private function updateTranslationTable() { if (is_file($this->tablePath)) { - shell_exec(sprintf('/usr/bin/msgmerge --update %s %s 2>&1', $this->tablePath, $this->templatePath)); + shell_exec(sprintf( + '%s --update %s %s 2>&1', + $this->getConfig()->get('translation', 'msgmerge', '/usr/bin/env msgmerge'), + $this->tablePath, + $this->templatePath + )); } else { if ((!is_dir(dirname($this->tablePath)) && !@mkdir(dirname($this->tablePath), 0755, true)) || !rename($this->templatePath, $this->tablePath)) { @@ -234,7 +277,7 @@ class GettextTranslationHelper implode( ' ', array( - '/usr/bin/xgettext', + $this->getConfig()->get('translation', 'xgettext', '/usr/bin/env xgettext'), '--language=PHP', '--keyword=translate', '--keyword=translate:1,2c', @@ -361,7 +404,7 @@ class GettextTranslationHelper $this->getSourceFileNames($this->moduleDir, $catalog); } else { $this->getSourceFileNames($this->appDir, $catalog); - $this->getSourceFileNames(realpath($this->appDir . '/../library/Icinga'), $catalog); + $this->getSourceFileNames($this->libDir, $catalog); } } catch (Exception $error) { throw $error; @@ -415,7 +458,7 @@ class GettextTranslationHelper implode( ' ', array( - '/usr/bin/msgfmt', + $this->getConfig()->get('translation', 'msgfmt', '/usr/bin/env msgfmt'), '-o ' . $targetPath, $this->tablePath ) diff --git a/modules/translation/module.info b/modules/translation/module.info index 9934ec931..098ad7981 100644 --- a/modules/translation/module.info +++ b/modules/translation/module.info @@ -1,7 +1,7 @@ Module: translation -Version: 2.0.0~alpha4 +Version: 2.0.0-rc1 Description: Translation module This module allows developers and translators to translate Icinga Web 2 and - it's modules for multiple languages. You do not need this module to run an + its modules for multiple languages. You do not need this module to run an internationalized web frontend. This is only for people who want to contribute - translations or translate just their own moduls. + translations or translate just their own modules. diff --git a/packages/debian/control b/packages/debian/control index 560b2ef65..73d0b57a3 100644 --- a/packages/debian/control +++ b/packages/debian/control @@ -24,7 +24,7 @@ Package: icingaweb-module-doc Architecture: any Depends: icingaweb-common Description: Icingaweb documentation module - This module renders documentation for Icingaweb and it's modules + This module renders documentation for Icingaweb and its modules Package: icingaweb-module-monitoring Architecture: any @@ -42,7 +42,7 @@ Package: icingaweb-module-test Architecture: any Depends: icingacli Description: Icingaweb test module - Use this module to run unit tests against Icingaweb or any of it's modules + Use this module to run unit tests against Icingaweb or any of its modules Package: icingaweb-module-translation Architecture: any @@ -54,7 +54,7 @@ Package: icingacli Architecture: any Depends: icingaweb-common, php5-cli (>= 5.3.2) Description: Icinga CLI tool - The Icinga CLI allows one to access it's Icinga monitoring + The Icinga CLI allows one to access its Icinga monitoring system from a terminal. . The CLI is based on the Icinga PHP libraries diff --git a/packages/files/apache/icingaweb.conf b/packages/files/apache/icingaweb2.conf similarity index 91% rename from packages/files/apache/icingaweb.conf rename to packages/files/apache/icingaweb2.conf index 2c52b73e3..6bf0b7a66 100644 --- a/packages/files/apache/icingaweb.conf +++ b/packages/files/apache/icingaweb2.conf @@ -1,4 +1,4 @@ -Alias /icingaweb "/usr/share/icingaweb2/public" +Alias /icingaweb2 "/usr/share/icingaweb2/public" Options SymLinksIfOwnerMatch @@ -23,7 +23,7 @@ Alias /icingaweb "/usr/share/icingaweb2/public" RewriteEngine on - RewriteBase /icingaweb/ + RewriteBase /icingaweb2/ RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d diff --git a/packages/files/bin/icingacli b/packages/files/bin/icingacli index 10b4857aa..4b51e3384 100755 --- a/packages/files/bin/icingacli +++ b/packages/files/bin/icingacli @@ -1,6 +1,7 @@ #!/usr/bin/php dispatch(); +Icinga\Application\Cli::start('/usr/share/icingaweb2')->dispatch(); diff --git a/packages/files/config/modules/doc/config.ini b/packages/files/config/modules/doc/config.ini new file mode 100644 index 000000000..80df20784 --- /dev/null +++ b/packages/files/config/modules/doc/config.ini @@ -0,0 +1,3 @@ +[documentation] +icingaweb2 = /usr/share/doc/icingaweb2/markdown +modules = /usr/share/doc/icingaweb2/modules/{module}/markdown diff --git a/packages/files/config/modules/setup/config.ini b/packages/files/config/modules/setup/config.ini new file mode 100644 index 000000000..5158aae99 --- /dev/null +++ b/packages/files/config/modules/setup/config.ini @@ -0,0 +1,2 @@ +[schema] +path = /usr/share/doc/icingaweb2/schema diff --git a/packages/files/config/modules/translation/config.ini b/packages/files/config/modules/translation/config.ini new file mode 100644 index 000000000..5bdf37b0c --- /dev/null +++ b/packages/files/config/modules/translation/config.ini @@ -0,0 +1,4 @@ +[translation] +msgmerge = /usr/bin/msgmerge +xgettext = /usr/bin/xgettext +msgfmt = /usr/bin/msgfmt diff --git a/packages/files/public/index.php b/packages/files/public/index.php index ebd223c03..9a8772a97 100644 --- a/packages/files/public/index.php +++ b/packages/files/public/index.php @@ -1,3 +1,4 @@ (except h4) */ -h1 { - font-size: 2em; - color: @colorTextDefault; - border-bottom: 2px solid @colorPetrol; -} + a { + text-decoration: none; + color: inherit; -h2 { - font-size: 1.5em; - color: @colorPetrol; -} - -h3 { - font-size: 1.17em; - color: @colorTextDefault; - border-bottom: 1px solid @colorPetrol; -} - -h4 { - font-size: 1em; - color: @colorPetrol; + &:hover { + text-decoration: underline; + } + } } h5 { - font-size: .83em; - border-bottom: 1px solid @colorPetrol; + font-size: 0.83em; } h6 { - font-size: .75em; - color: @colorPetrol; + font-size: 0.75em; } -h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { - text-decoration: none; - color: inherit; +h1 { + border-bottom: 3px solid #666; + font-variant: small-caps; + font-weight: bold; } -h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover { - text-decoration: underline; +h2 { + border-bottom: 1px solid #888; + font-variant: small-caps; + font-weight: normal; +} + +h3 { + font-weight: normal; + border-bottom: 1px solid #aaa; } button { - font-family: inherit; + font-family: inherit; } #fontsize-calc { @@ -104,5 +132,3 @@ button { top: -2em; } /*** END of Base rules */ - - diff --git a/public/css/icinga/footer-elements.less b/public/css/icinga/footer-elements.less new file mode 100644 index 000000000..f3b248b69 --- /dev/null +++ b/public/css/icinga/footer-elements.less @@ -0,0 +1,50 @@ +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +div#footer { + position: fixed; + left: 0px; + right: 0px; + bottom: 0px; + z-index: 9999; +} + +/** Notifications **/ + +#notifications { + margin: 0; + padding: 0; +} + +#notifications > li { + list-style-type: none; + display: block; + border-top: 1px solid #999; + color: white; + line-height: 2.5em; + padding-left: 3em; + background-repeat: no-repeat; + background-position: 1em center; +} + +#notifications > li:hover { + cursor: pointer; +} + +#notifications > li.info { + background-color: @colorFormNotificationInfo; +} + +#notifications > li.warning { + background-color: @colorWarningHandled; +} +#notifications > li.error { + background-color: @colorCritical; + background-image: url(../img/icons/error_white.png); +} + +#notifications > li.success { + background-color: #fe6; + background-image: url(../img/icons/success.png); + color: #333; +} +/** END of Notifications **/ diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less index 813ae28f7..82a5f5184 100644 --- a/public/css/icinga/forms.less +++ b/public/css/icinga/forms.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ div.config-form-buttons { margin-top: 5px; @@ -26,7 +25,7 @@ label { font-weight: bold; } -input, select { +input, select, textarea { box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -45,10 +44,19 @@ input[type=submit] { font-weight: bold; text-align: center; color: #fff; - border: 2px solid #ddd; + border: 1px solid; border-color: @colorPetrol; background: @colorPetrol; outline: 0; + + &[disabled] { + background-color: #666; + border-color: black; + } + + &:hover[disabled], &:active[disabled], &:focus[disabled] { + background-color: #666; + } } input[type=submit]:hover, a.button:hover, input[type=submit]:focus { @@ -93,17 +101,47 @@ select::-moz-focus-inner { outline: 0; } -input:disabled, select:disabled { - background-color: #fff; - border-color: white; -} - form.inline { margin: 0; padding: 0; display: inline; } +button, .button-like { + font-size: 0.9em; + font-weight: bold; + outline: 0; + color: #fff; + padding: 0.2em; + border: 1px solid; + border-color: @colorPetrol; + background: @colorPetrol; + + &[disabled] { + background-color: #666; + border-color: black; + } + + &:hover, &:focus, &:active { + background-color: #333; + border-color: #333; + cursor: pointer; + + &[disabled] { + background-color: #666; + } + } +} + +.button-like { + display: inline-block; +} + +a.button-like { + cursor: default; + text-decoration: none; +} + form.link-like input[type="submit"], form.link-like button[type="submit"], input.link-like, button.link-like { color: @colorLinkDefault; font-weight: normal; @@ -112,13 +150,29 @@ form.link-like input[type="submit"], form.link-like button[type="submit"], input padding: 0; font-size: 1em; cursor: pointer; + + &.icon-only { + color: inherit; + font-size: 1.5em; + + &:hover, &:focus, &:active { + color: #666; + } + } } form.link-like input[type="submit"]:hover, form.link-like input[type="submit"]:focus, +form.link-like input[type="submit"]:active, form.link-like button[type="submit"]:hover, form.link-like button[type="submit"]:focus, -input.link-like:hover, button.link-like:focus { +form.link-like button[type="submit"]:active, +input.link-like:hover, +input.link-like:focus, +input.link-like:active, +button.link-like:hover, +button.link-like:focus, +button.link-like:active { text-decoration: underline; background: none; color: @colorLinkDefault; @@ -127,10 +181,10 @@ input.link-like:hover, button.link-like:focus { .non-list-like-list { list-style-type: none; margin: 0; - padding: 0; + padding: 0.5em 0.5em 0; li { - margin: 0.5em; + padding-bottom: 0.5em; } } @@ -141,29 +195,59 @@ form div.element ul.errors { li { color: @colorCritical; font-weight: bold; - line-height: 1.5em; } } form ul.form-errors { .non-list-like-list; - display: inline-block; margin-bottom: 1em; background-color: @colorCritical; ul.errors { .non-list-like-list; + padding: 0; + + li:last-child { + padding-bottom: 0; + } + } + + li { + color: white; + font-weight: bold; + } +} + +form ul.form-notifications { + .non-list-like-list; + margin-bottom: 1em; + padding: 0; + + ul { + .non-list-like-list; + + &.info { + background-color: @colorFormNotificationInfo; + } + + &.warning { + background-color: @colorFormNotificationWarning; + } + + &.error { + background-color: @colorFormNotificationError; + } } li { color: white; font-weight: bold; - line-height: 1.5em; } } form div.element { - margin: 0.5em 0; + margin-top: 0.5em; + margin-bottom: 0.5em; } form label { @@ -173,6 +257,14 @@ form label { width: 10em; } +form div.element > * { + vertical-align: top; +} + +form dt { + vertical-align: top; +} + select, input[type=text], textarea { width: 20em; display: inline-block; @@ -182,6 +274,13 @@ textarea { height: 4em; } +textarea.resource { + &.ssh-identity { + width: 50%; + height: 25em; + } +} + form .description { font-size: 0.8em; margin: 0.3em 0 0 0.6em; @@ -191,33 +290,67 @@ form .description { display: block; } -form label.has-feedback:after { - content: '\e85b'; - font-family: "ifont"; - font-style: normal; - font-weight: normal; - speak: none; - - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - /* opacity: .8; */ - - /* For safety - reset parent styles, that can break glyph codes*/ - font-variant: normal; - text-transform: none; - - /* fix buttons height, for twitter bootstrap */ - line-height: 1em; - - /* Animation center compensation - margins should be symmetric */ - /* remove if not needed */ - margin-left: .2em; - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ +select.grant-permissions { + height: 20em; + width: auto; } + +label ~ input, label ~ select { + margin-left: 1.35em; +} + +label + i ~ input, label + i ~ select { + margin-left: 0; +} + +button.noscript-apply { + margin-left: 0.5em; +} + +html.no-js i.autosubmit-warning { + .sr-only; +} + +form ul.descriptions { + .info-box; + padding: 0.5em 0.5em 0 1.8em; + + li { + padding-bottom: 0.5em; + + &:only-child { + margin-left: -1.3em; + list-style-type: none; + } + } +} + +form ul.hints { + .non-list-like-list; + padding: 0.5em 0.5em 0 0.5em; + + li { + font-size: 0.8em; + padding-bottom: 0.5em; + } +} + +.control-group { + & > * { + float: left; + margin-right: 0.5em; + } + + div.element { + margin-top: 0; + margin-bottom: 0; + } + + &:after { + content: "."; + visibility: hidden; + display: block; + height: 0; + clear: both; + } +} \ No newline at end of file diff --git a/public/css/icinga/header-elements.less b/public/css/icinga/header-elements.less index e1f3a7bce..4ecfb0823 100644 --- a/public/css/icinga/header-elements.less +++ b/public/css/icinga/header-elements.less @@ -1,12 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} - -#header div.user { - font-size: 1.1em; - margin-top: 0.8em; - margin-right: 2em; - float: right; -} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ #logo { height: 3.6em; diff --git a/public/css/icinga/layout-colors.less b/public/css/icinga/layout-colors.less index c57cd1a26..64642b078 100644 --- a/public/css/icinga/layout-colors.less +++ b/public/css/icinga/layout-colors.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /* Layout colors */ @@ -11,7 +10,7 @@ } #header { - background-color: #049baf; + background-color: @colorPetrol; color: #ddd; color: #d0d0d0; border-bottom: solid 1px; @@ -40,26 +39,3 @@ } } -@colorLinkDefault: #049baf; -@colorTextDefault: #666; -@colorTextDarkDefault: #555; -@colorPetrol: #049baf; -@colorOk: #44bb77; -@colorWarning: #ffaa44; -@colorWarningHandled: #ffcc66; -@colorCritical: #ff5566; -@colorCriticalHandled: #ff99aa; -/* -@colorUnknown: #dd66ff; -@colorUnknownHandled: #ee99ff; -@colorUnreachable: #dd66ff; -@colorUnreachableHandled: #ee99ff; -*/ -@colorUnknown: #aa44ff; -@colorUnknownHandled: #cc77ff; -@colorUnreachable: #aa44ff; -@colorUnreachableHandled: #cc77ff; - -@colorPending: #77aaff; -@colorInvalid: #999; - diff --git a/public/css/icinga/layout-structure.less b/public/css/icinga/layout-structure.less index 201f5c88a..47adbca7c 100644 --- a/public/css/icinga/layout-structure.less +++ b/public/css/icinga/layout-structure.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** Default layout **/ html { @@ -48,6 +47,10 @@ html { } } +#fileupload-frame-target { + display: none; +} + #responsive-debug { font-size: 0.9em; font-family: Courier new, monospace; @@ -82,13 +85,29 @@ html { /** Fullscreen layout **/ #layout.fullscreen-layout { - #header, #sidebar, .controls > .tabs { + #header, #sidebar { display: none; } + .container .controls { + padding: 0; + } + + .controls > ul.tabs { + margin-top: 0; + height: 1.5em; + background-color: @colorPetrol; + font-size: 0.75em; + padding: 0.2em 0 0; + } + + .controls > ul.tabs > li > a { + line-height: 1.5em; + } + #main { left: 0; - top: 0; + top: 0 !important; } } @@ -97,6 +116,7 @@ html { box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; + font-size: 0.875em; width: 100%; height: 100%; overflow: auto; @@ -120,12 +140,17 @@ html { .container .controls { top: 0; background-color: white; - padding: 0; + padding: 1em 1em 0; z-index: 100; } +.container .controls.separated { + box-shadow: 0 3px 4px -4px rgba(0, 0, 0, 0.2); + padding-bottom: 0.5em; +} + .container .fake-controls { - padding: 0; + padding: 1em 1em 0; } .container .controls .pagination { @@ -137,7 +162,6 @@ html { } .dashboard > div.container { - font-size: 0.9em; vertical-align: top; width: 48.5%; display: inline-block; @@ -146,14 +170,6 @@ html { margin-left: 1%; } -.content h3 { - font-size: 0.9em; -} - -.container .controls > * { - margin-left: 1em; -} - .container .controls .pagination { margin-left: 1.2em; } @@ -164,6 +180,11 @@ html { .dashboard .content { padding: 0; + overflow: auto; +} + +.dashboard .controls { + padding: 0; } /* Not growing larger than 3840px at 1em=16px right now */ @@ -257,6 +278,40 @@ html { #main { left: 0; } + + #login { + .logo .image img { + width: 70%; + } + .form { + width: 100%; + margin: auto; + } + .form label { + width: 100%; + margin: 0; + text-align: center; + display: inline-block; + } + .footer { + margin-left: 0; + } + h1 { + margin-left: 0px; + text-align: center; + } + form { + width: 100%; + margin: 0; + } + form input { + margin: auto; + display: block; + } + form input[type=submit] { + margin-top: 1.5em; + } + } } @@ -297,8 +352,7 @@ html { position: absolute; } -/* TODO: replace this with .error */ -.fileNotReadable { +.message-error { padding: 0.5em; background-color: @colorCritical; font-weight: bold; @@ -312,6 +366,7 @@ html { .hbox-item { display: inline-block; vertical-align: top; + margin-top: 0.5em; margin-bottom: 0.25em; margin-left: 1em; margin-right: 1em; @@ -322,3 +377,41 @@ html { vertical-align: top; width: 2em; } + +/* + * Class to hide content from users but available for screen reader + * @todo(mh): Replace .audible class + */ +.sr-only { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +.clearfix:after { + content: "."; + visibility: hidden; + display: block; + height: 0; + clear: both; +} + +.multi-commands { + padding-top: 0em; + font-size: 0.9em; +} + +// Hide non-javascript elements if javascript is enabled +html.js *.no-js { + .sr-only; +} + +// Hide javascript elements if javascript is disabled +html.no-js *.js { + .sr-only; +} \ No newline at end of file diff --git a/public/css/icinga/login.less b/public/css/icinga/login.less index cfae3c478..a24265e88 100644 --- a/public/css/icinga/login.less +++ b/public/css/icinga/login.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ #login { width: 100%; @@ -51,6 +50,7 @@ margin-left: 2.3em; border: none; color: @colorTextDefault; + font-variant: unset; } .form div.element { @@ -80,9 +80,10 @@ form input { width: 18em; padding: 0.5em; - background: #ddd; - color: #333; + background: #ddd; + color: #333; border: 1px solid #ddd; + margin-left: 0; } form input:focus { @@ -109,22 +110,37 @@ .footer { margin-top: 7em; font-size: 0.9em; - text-align: center; + text-align: center; margin-left: 5em; } - div.config-note { + p.config-note { width: 50%; padding: 1em; - margin: 5em auto 0; + margin: 0 auto 2.5em; text-align: center; color: white; background-color: @colorCritical; + + a { + color: white; + font-weight: bold; + } } - div.config-note a { - color: white; - font-weight: bold; + p.info-box { + width: 50%; + height: 2.2em; + margin: 2em auto 2.5em; + + i.icon-info { + float: left; + height: 100%; + } + + em { + text-decoration: underline; + } } } diff --git a/public/css/icinga/main-content.less b/public/css/icinga/main-content.less index 6972e95dc..d0f182913 100644 --- a/public/css/icinga/main-content.less +++ b/public/css/icinga/main-content.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ code { background-color: #eee; @@ -13,6 +12,15 @@ p code { padding: 0.3em; } +pre.log-output { + padding: 0.5em; + margin-top: 0; + margin-bottom: 2em; + max-height: 12em; + overflow: auto; + border: 1px solid #ddd; +} + a { color: #39a; } @@ -24,39 +32,6 @@ img.icon { border: none; } -/** Notifications **/ - -#notifications { - margin: 0; - padding: 0; -} - -#notifications > li { - list-style-type: none; - display: block; - border-bottom: 1px solid #999; - color: white; - line-height: 2.5em; - padding-left: 3em; - background-repeat: no-repeat; - background-position: 1em center; -} - -#notifications > li.warning { - background-color: @colorWarningHandled; -} -#notifications > li.error { - background-color: @colorCritical; - background-image: url(../img/icons/error_white.png); -} - -#notifications > li.success { - background-color: #fe6; - background-image: url(../img/icons/success.png); - color: #333; -} -/** END of Notifications **/ - /* TODO: Remove once there is no more module container */ .container > div > pre { margin: 1em; @@ -70,7 +45,6 @@ table.avp { color: @colorTextDarkDefault; table-layout: auto; width: 100%; - font-size: 0.9em; } table.avp a { @@ -81,7 +55,7 @@ table.avp a { table.avp > tbody > tr > th { text-align: left; margin: 0; - width: 10em; + width: 14em; vertical-align: top; line-height: 1.8em; } @@ -96,7 +70,7 @@ table.avp > tbody > tr > td { } table.avp.newsection, table.avp tr.newsection { - border-top: 1px solid #d9d9d9; + margin-top: 1em; } .content table.avp dd, .content table.avp dd { @@ -104,10 +78,6 @@ table.avp.newsection, table.avp tr.newsection { padding: 0; } -table.avp { - font-size: 0.8em; -} - table.avp a { color: @colorLinkDefault; text-decoration: none; @@ -117,71 +87,12 @@ table.avp a:hover { text-decoration: underline; } -/* Definitively monitoring-only: */ -table.objectstate { - margin: 1em; - border-collapse: separate; - border-spacing: 1px; -} - -table.objectstate td { - font-size: 1.2em; - line-height: 1.5em; - padding-left: 1em; -} - -table.objectstate tr.state td.state { - font-size: 1em; - width: 9em; - text-align: center; - padding-left: 0; - border-radius: 0; -} - -table.objectstate tr.state.handled td.state { -} - -table.perfdata { - min-width: 24em; - font-size: 0.9em; -} - -table.perfdata th { - padding: 0; - text-align: left; - padding-right: 0.5em; -} - -table.perfdata td { - white-space: nowrap; -} - -table.objectlist { - min-width: 28em; - th { - text-align: left; - } -} - table.benchmark { margin: 1em 1% 1em 1%; font-family: monospace; - font-size: 1em; width: 96%; } -.dashboard h1 { - font-size: 1em; -} - -.dashboard h2 { - font-size: 1em; -} - -.dashboard h3 { - font-size: 1em; -} - .dashboard table.benchmark { font-size: 0.9em; } @@ -194,11 +105,236 @@ table.benchmark { color: inherit; } -/* controls have no padding as of tabs */ -.controls > h1 { - margin-right: 1em; -} - [class^="icon-"]:before, [class*=" icon-"]:before { text-decoration: none; + margin-left: 0; +} + +.info-box { + padding: 0.5em; + border: 1px solid lightgrey; + background-color: #f2f4fd; +} + +/* Action table */ +table.action { + border-collapse: separate; + border-spacing: 1px; + width: 100%; + table-layout: fixed !important; + margin: 0; + color: @colorTextDefault; +} + +table.action th { + text-align: left; + overflow: hidden; +} + +table.action.wide td { + line-height: 2.5em; +} + +table.action td { + padding: 0.3em 0.5em 0.3em 0.5em; + line-height: 1.5em; + overflow: hidden; +} + +.dashboard table.action td { + line-height: 1.2em; + padding: 0.2 0.4em 0.2em 0.5em; +} + +table.action td a { + color: inherit; + text-decoration: none; +} + +table.action td a:hover { + text-decoration: underline; +} + +/* END of Action table */ + +/* Table behaviour */ + +tr[href] { + cursor: pointer; +} + +tr[href].active { + background-color: #eee; + color: black; +} + +table.alternating { + tr[href].active:nth-child(even), tr[href].active:nth-child(odd) { + background-color: #E5E5E5; + color: black; + } + tr[href]:nth-child(even) { + background-color: #FDFDFD; + } + tr[href]:nth-child(odd) { + background-color: #f5f5f5; + } + tr[href]:hover { + color: black; + background-color: #DDDDDD; + } +} +/* End of table behaviour */ + + +/* HOVER colors */ + +tr[href]:hover { + color: black; + background-color: #DDDDDD; +} +/* END of HOVER colors */ + +div.controls table.user-header { + th { + width: 9em; + line-height: 1.5em; + font-size: 0.9em; + } + + td { + line-height: 1.5em; + font-size: 0.9em; + } +} + +/* TODO: get rid of most if not all styles below this line, + by ensuring to not to make everything look ugly... */ +div.content.users { + table.user-list { + th.user-remove { + width: 8em; + padding-right: 0.5em; + text-align: right; + } + + td.user-remove { + text-align: right; + } + } + + p { + margin-top: 0; + } + + a.user-add { + display: block; + margin-top: 1em; + } +} + +div.content.memberships { + table.membership-list { + th.membership-cancel { + width: 8em; + padding-right: 0.5em; + text-align: right; + } + + td.membership-cancel { + text-align: right; + + form button.link-like { + color: inherit; + } + } + } + + p { + margin-top: 0; + } + + a.membership-create { + display: block; + margin-top: 1em; + } +} + +div.content.groups { + table.group-list { + th.group-remove { + width: 8em; + padding-right: 0.5em; + text-align: right; + } + + td.group-remove { + text-align: right; + } + } + + p { + margin-top: 0; + } + + a.group-add { + display: block; + margin-top: 1em; + } +} + +div.content.members { + table.member-list { + th.member-remove { + width: 8em; + padding-right: 0.5em; + text-align: right; + } + + td.member-remove { + text-align: right; + + form button.link-like { + color: inherit; + } + } + } + + p { + margin-top: 0; + } + + a.member-add { + display: block; + margin-top: 1em; + } +} + +form.backend-selection { + float: right; + + div.element { + margin: 0; + + label { + width: auto; + margin-right: 0.5em; + } + + select { + width: 11.5em; + margin-left: 0; + } + } +} + +table.usergroupbackend-list { + th.backend-remove { + width: 8em; + text-align: right; + } + + td.backend-remove { + text-align: right; + } } diff --git a/public/css/icinga/menu.less b/public/css/icinga/menu.less index c8ab698a5..b3adec2ef 100644 --- a/public/css/icinga/menu.less +++ b/public/css/icinga/menu.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ #menu { max-height: 100%; @@ -28,64 +27,55 @@ margin-left: 0.0em; } -#menu > ul > li > ul { +#menu > nav > ul > li > ul { display: none; padding-left: 6px; } -.no-js #menu > ul > li > ul { +.no-js #menu > nav > ul > li > ul { display: block; } -#menu > ul > li.active > ul { +#menu > nav > ul > li.active > ul { display: block; } -#menu > ul > li.active { +#menu > nav > ul > li.active { background-color: white; padding-left: 0.0em; margin-left: 0; margin-right: 0; } -#menu > ul > li > a { +#menu > nav > ul > li > a { font-size: 0.9em; text-shadow: none; } -#menu > ul > li.active > a { +#menu > nav > ul > li.active > a { color: @colorTextDefault; text-shadow: none; } -#menu > ul > li.active li.active a { +#menu > nav > ul > li.active li.active a { color: @colorPetrol; } -#menu > ul > li.active li a:focus { - color: @colorTextDefault; -} - -#menu > ul > li > a:focus { - color: @colorTextDefault; - text-shadow: none; -} - -#menu > ul > li.active li a:hover { +#menu > nav > ul > li.active li a:hover { text-decoration: underline; } -#menu > ul > li { +#menu > nav > ul > li { border-bottom: 1px solid #d9d9d9; } -#menu > ul > li li { +#menu > nav > ul > li li { font-size: 0.8em; padding-left: 1.6em; } /* Collapsed menu item color */ -#menu > ul > li > a { +#menu > nav > ul > li > a { color: @colorTextDefault; } @@ -120,11 +110,11 @@ padding-bottom: 1em; } -#menu > ul > li { +#menu > nav > ul > li { margin-left: 5px; } -#menu > ul > li.active { +#menu > nav > ul > li.active { -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; @@ -264,3 +254,42 @@ html.ie8 #menu input.search { input:focus { outline: none; } +/* Make focus outline properly visible */ +a:focus { + outline: dotted black 1px; +} + +/* Displaying the outline in the navigation is not possible because of the messed up link borders, + color those links instead. */ +#menu a:focus, +#menu > nav > ul > li > a:focus, +#menu .active ul li a:focus { + color: #21b5ad; + outline: none; +} + +.skip-links { + position: relative; + ul { + list-style-type: none; + margin: 0; + padding: 0; + li { + display: block; + a, button[type="submit"] { + background-color: #fff; + left: -999em; + padding: 0.8em; + position: absolute; + width: 100%; + &:focus { + left: 0; + outline: 1px dotted black; + } + } + button[type="submit"] { + text-align: left; + } + } + } +} diff --git a/public/css/icinga/monitoring-colors.less b/public/css/icinga/monitoring-colors.less deleted file mode 100644 index 52154c24e..000000000 --- a/public/css/icinga/monitoring-colors.less +++ /dev/null @@ -1,933 +0,0 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} - -/* Special tables and states */ - -table.colors { - font-size: 0.8em; - width: 98%; - margin: 0 1%; -} - -table.colors td { - text-align: center; - vertical-align: middle; - width: 10%; - height: 1.6em; - font-weight: normal; - border: 0.079em solid white; -} - -/* Action table */ -table.action { - font-size: 0.9em; - border-collapse: separate; - border-spacing: 1px; - width: 100%; - table-layout: fixed; - margin: 0; - color: @colorTextDefault; -} - -table.action th { - text-align: left; - overflow: hidden; -} - -table.action.wide td { - line-height: 2.5em; -} - -table.action td { - padding: 0.3em 0.5em 0.3em 0.5em; - line-height: 1.5em; - overflow: hidden; -} - -.dashboard table.action td { - line-height: 1.2em; - padding: 0.2 0.4em 0.2em 0.5em; -} - -table.action td .pluginoutput { - font-size: 0.8em; - line-height: 1.2em; - padding: 0; - margin: 0; -} - -table.action td a { - color: inherit; - text-decoration: none; -} - -table.action td a:hover { - text-decoration: underline; -} - -table.action div.inlinepie { - margin: 0.5em 0.25em 0.5em 0.25em; -} - -.dashboard table.action div.inlinepie { - margin: 0em 0.25em 0em 0.25em; -} - -/* END of Action table */ - - -/* Table behaviour */ - -tr[href] { - cursor: pointer; -} - -/* End of table behaviour */ - - -table.action td.state { - font-size: 0.7em; - text-align: center; -} - -table.action td.timesince { - width: 3.5em; -} - - -/* State row behaviour */ - -tr.state img.icon { - margin-right: 2px; -} - -tr.state a { - font-weight: bold; -} - -tr.state a.active { -} - -tr.state.new td.state { - font-weight: bold; -} - -tr.state td.state strong { - font-size: 1.2em; -} - -tr.state td.state { - width: 9em; - color: white; - border-bottom: none; -} - -tr.state.handled td.state, tr.state.ok td.state, tr.state.up td.state, tr.state.pending td.state { - border-left-style: solid; - border-left-width: 1.5em; - padding-left: 0em; - padding-right: 0.5em; - color: black; - background-color: transparent; -} - -tr[href].active { - background-color: #eee; - color: black; -} - -tr.state.ok td.state, tr.state.up td.state { - border-left-color: @colorOk; -} - -tr.state.warning td.state { - background-color: @colorWarning; -} - -tr.state.warning.handled td.state { - border-left-color: @colorWarningHandled; -} - -tr.state.critical td.state, tr.state.down td.state { - background-color: @colorCritical; -} - -tr.state.critical.handled td.state, tr.state.down.handled td.state { - border-left-color: @colorCriticalHandled; -} - -tr.state.unreachable td.state { - background-color: @colorUnreachable; -} - -tr.state.unreachable.handled td.state { - border-left-color: @colorUnreachableHandled; -} - -tr.state.unknown td.state { - background-color: @colorUnknown; -} - -tr.state.unknown.handled td.state { - border-left-color: @colorUnknownHandled; -} - -tr.state.pending td.state { - border-left-color: @colorPending; -} - -tr.state.invalid td.state { - background-color: @colorInvalid; -} - -tr.state.unreachable td.state { - background-color: @colorUnreachable; -} - -tr.state.unreachable.handled td.state { - border-left-color: @colorUnreachableHandled; -} - -tr.state.handled td.state { - color: inherit; - background-color: transparent !important; -} - -/* HOVER colors */ -tr[href]:hover { - color: black; - background-color: #eee; -} - -tr.state[href]:hover td.state { - color: white; - background-color: #eee; -} - -tr.state.ok[href]:hover, tr.state.up[href]:hover { - background-color: @colorOk; -} - -tr.state.handled[href]:hover, tr.state.handled[href]:hover td.state { - color: #121212 !important; -} - -tr.state.warning[href]:hover { - background-color: @colorWarning; -} - -tr.state.warning.handled[href]:hover { - background-color: @colorWarningHandled; -} - -tr.state.critical[href]:hover, tr.state.down[href]:hover { - background-color: @colorCritical; -} - -tr.state.critical.handled[href]:hover, tr.state.down.handled[href]:hover { - background-color: @colorCriticalHandled; - color: #333; -} - -tr.state.unknown[href]:hover { - background-color: @colorUnknown; -} - -tr.state.unknown.handled[href]:hover { - background-color: @colorUnknownHandled; -} - -tr.state.pending[href]:hover { - background-color: @colorPending; -} - -tr.state.invalid[href]:hover { - background-color: @colorInvalid; -} - -tr.state.unreachable[href]:hover { - background-color: @colorUnreachable; -} - -tr.state.unreachable.handled[href]:hover { - background-color: @colorUnreachableHandled; -} - -tr.state[href]:hover td.state { - background-color: inherit !important; -} - -/* END of HOVER colors */ - -/* END of special tables and states */ - - -/* Generic colors */ - -a.critical { - color: @colorCritical; -} - -/* END of Generic colors */ - - -/* Generic box element */ - -.boxview a { - text-decoration: none; -} - -.boxview > div.box { - text-align: center; - vertical-align: top; - display: inline-block; - padding: 0.4em; - margin: 0.4em; - border: 1px solid #d9d9d9; - background: #eee; -} - -/* Box caption */ -.boxview div.box h2 { - margin-top: 0; - margin-bottom: 0.6em; - font-size: 0.8em; - color: @colorTextDefault; -} - -.boxview div.box h2:first-child { - margin-top: 0.2em; - padding-bottom: 0.5em; - font-size: inherit; - color: @colorTextDefault; - border-bottom: 1px solid #d9d9d9; -} - -.boxview div.box h2 > a { - color: inherit; - margin-bottom: 0.2em; -} - -.boxview div.box h2 > a:hover { - text-decoration: underline; -} - -.boxview div.box h2:first-child > a:hover { -} - -.boxview div.box h3 { - line-height: 1.5em; - font-size: 0.9em; - color: #555; - border-bottom: 1px solid #d9d9d9; -} - -/* Box body of contents */ -.boxview div.box.contents { - padding: 0.2em; -} - -.boxview div.box.contents table { - width: 100%; -} - -.boxview div.box.contents td { - width: 13em; - vertical-align: top; -} - -/* Box separator */ -.boxview div.box-separator:first-child { - border-top-width: 0; -} - -.boxview div.box-separator { - font-size: 0.8em; - padding: 0.4em 0 0.4em; - border: 1px solid #d9d9d9; - - font-weight: bold; - letter-spacing: 0.1em; -} - -/* Box entry */ -.boxview div.box.entry { - min-height: 2.7em; - margin: 0.2em; - font-size: 0.9em; - white-space: nowrap; - - color: @colorTextDefault; -} - -/* Any line of a box entry */ -.boxview div.box.entry a { - display: block; - - color: inherit; -} - -.boxview div.box.entry a:hover { - color: @colorTextDefault; -} - -/* First line of a box entry */ -.boxview div.box.entry a:first-child { - font-size: 1em; -} - -/* End of generic box element */ - - -/* Monitoring box element styles */ - -/* Host- and Servicegroup element styles */ - -div.box.entry.state_up, div.box.entry.state_ok { - border: 1px solid @colorOk; - border-left: 1em solid @colorOk; -} - -div.box.entry.state_pending { - border: 1px solid @colorPending; - border-left: 1em solid @colorPending; -} - -div.box.entry.state_down, div.box.entry.state_critical { - border: 1px solid @colorCritical; - border-left: 1em solid @colorCritical; - background-color: @colorCritical; - color: white; -} - -div.box.entry.state_down a:hover, div.box.entry.state_critical a:hover { - color: #dcdcdc; -} - -div.box.entry.state_warning { - border: 1px solid @colorWarning; - border-left: 1em solid @colorWarning; - background-color: @colorWarning; - color: white; -} - -div.box.entry.state_warning a:hover { - color: #dcdcdc; -} - -div.box.entry.state_unreachable, div.box.entry.state_unknown { - border: 1px solid @colorUnknown; - border-left: 1em solid @colorUnknown; - background-color: @colorUnknown; - color: white; -} - -div.box.entry.state_unreachable a:hover, div.box.entry.state_unknown a:hover { - color: #dcdcdc; -} - -div.box.entry.handled { - background-color: transparent; - color: inherit; -} - -div.box.entry.handled a:hover { - color: @colorTextDefault; -} - -/* Tactical overview element styles */ - -.tactical > .boxview > div.box { - min-height: 20em; - min-width: 12.1em; -} - -.tactical div.box.contents { - min-height: 14.5em; -} - -div.box.contents.zero { - min-width: 11.1em; - - background-color: transparent; -} - -div.box.contents.zero span { - font-weight: bold; - - color: white; -} - -div.box.contents.zero h3 { - margin: 0; - font-size: 12em; - line-height: 1em; - - color: white; -} - -div.box.ok_hosts.state_up { - border: 5px solid @colorOk; -} - -div.box.ok_hosts.state_pending { - background-color: @colorPending; -} - -div.box.problem_hosts.state_down { - border: 5px solid @colorCritical; -} - -div.box.problem_hosts.state_down.handled { - background-color: @colorCriticalHandled; -} - -div.box.problem_hosts.state_unreachable { - background-color: @colorUnreachable; -} - -div.box.problem_hosts.state_unreachable.handled { - background-color: @colorUnreachableHandled; -} - -div.box.ok_hosts div.box.entry, div.box.problem_hosts div.box.entry { - min-width: 11.1em; -} - -div.box.monitoringfeatures div.box.contents { - padding: 0 2 0em; -} - -div.box.monitoringfeatures { - border: 5px solid #d9d9d9; -} - -div.box.monitoringfeatures div.box-separator { - color: white; - background-color: @colorOk; -} - -div.box.monitoringfeatures div.feature-highlight { - background-color: @colorCritical; -} - -div.box.monitoringfeatures a.feature-highlight { - font-weight: bold; -} - -div.box.hostservicechecks { - border: 5px solid #d9d9d9; -} - -/* Contactgroup element styles */ - -div.box.contactgroup { - width: 18em; - padding: 0.8em; -} - -div.box.contactgroup div.box.contents { - padding: 0.6em; -} - -div.box.contactgroup div.box.entry { - overflow: hidden; - clear: left; -} - -div.box.contactgroup div.box.entry img { - width: 80px; - height: 80px; - float: left; - -} - -div.box.contactgroup div.box.entry a { - margin-top: 0.4em; - - font-weight: bold; -} - -div.box.contactgroup div.box.entry p { - margin: 0.4em 0 0; -} - -div.circular { - margin-top: 0.5em; - margin-left: 2em; - margin-right: 1em; - width: 80px; - height: 80px; - float: left; - background-size: 100% 100%; -} - -/* End of monitoring box element styles */ - - -/* Monitoring pivot table styles */ - -div.pivot-pagination { - margin: 1em; - - table { - table-layout: fixed; - border-spacing: 1px; - border-collapse: separate; - border: 1px solid LightGrey; - border-radius: 0.3em; - - td { - width: 16px; - height: 16px; - padding: 0; - background-color: #fbfbfb; - - &:hover, &.active { - background-color: #e5e5e5; - } - - a { - width: 16px; - height: 16px; - display: block; - } - } - } -} - -table.joystick-pagination { - margin-top: -1.5em; - - td { - width: 1.25em; - height: 1.3em; - } -} - -table.pivot { - a { - text-decoration: none; - color: black; - - &:hover { - color: @colorTextDefault; - } - } - - & > thead { - th { - height: 6em; - - div { - margin-right: -1.5em; - padding-left: 1.3em; - - span { - width: 1.5em; - margin-right: 0.25em; - margin-top: 4em; - line-height: 2em; - white-space: nowrap; - display: block; - float: left; - - transform: rotate(-45deg); - transform-origin: bottom left; - -o-transform: rotate(-45deg); - -o-transform-origin: bottom left; - -ms-transform: rotate(-45deg); - -ms-transform-origin: bottom left; - -moz-transform: rotate(-45deg); - -moz-transform-origin: bottom left; - -webkit-transform: rotate(-45deg); - -webkit-transform-origin: bottom left; - //filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); - - abbr { - border: 0; // Remove highlighting in firefox - font-size: 0.8em; - } - } - } - } - } - - & > tbody { - th { - padding: 0 14px 0 0; - white-space: nowrap; - - a { - font-size: 0.8em; - } - } - - td { - padding: 2px; - min-width: 1.5em; - line-height: 1.5em; - text-align: center; - - a { - width: 1.5em; - height: 1.5em; - display: block; - border-radius: 0.5em; - - &.state_ok { - background-color: @colorOk; - } - - &.state_pending { - background-color: @colorPending; - } - - &.state_warning { - background-color: @colorWarning; - - &.handled { - background-color: @colorWarningHandled; - } - } - - &.state_critical { - background-color: @colorCritical; - - &.handled { - background-color: @colorCriticalHandled; - } - } - - &.state_unknown { - background-color: @colorUnknown; - - &.handled { - background-color: @colorUnknownHandled; - } - } - } - } - } -} - -/* End of monitoring pivot table styles */ - -/* Monitoring timeline styles */ - -div.timeline-legend { - float: left; - padding: 0.5em; - border: 1px solid #d9d9d9; - background-color: #eee; - - h2 { - margin: 0; - margin-left: 0.5em; - line-height: 1.1em; - } - - & > span { - display: inline-block; - padding: 0.5em; - margin: 0.5em; - - span { - color: white; - font-size: 0.8em; - font-weight: bold; - white-space: nowrap; - } - } -} - -div.timeline { - div.timeframe { - height: 7em; - margin-bottom: 1em; - clear: left; - - span { - width: 8em; - margin-top: 2.3em; - margin-right: 1.5em; - display: block; - float: left; - text-align: center; - - a { - color: @colorTextDefault; - font-size: 0.8em; - font-weight: bold; - text-decoration: none; - white-space: nowrap; - - &:hover { - color: @colorTextDefault; - text-decoration: underline; - - } - } - } - - div.circle-box { - // width: inline-style; - height: 100%; - margin-right: 0.5em; - position: relative; - float: left; - - div.outer-circle { - // width: inline-style; - // height: inline-style; - position: absolute; - top: 50%; - // margin-top: inline-style; - - &.extrapolated { - border-width: 2px; - border-style: dotted; - //border-color: inline-style; - border-radius: 100%; - // background-color: inline-style; - } - - a.inner-circle { - // width: inline-style; - // height: inline-style; - display: block; - position: absolute; - top: 50%; - left: 50%; - // margin-top: inline-style; - // margin-left: inline-style; - border-radius: 100%; - // background-color: inline-style; - } - } - } - } - - hr { - border: 0; - height: 1px; - background-image: linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); - background-image: -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); - background-image: -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); - background-image: -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); - background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0)); - } -} - -/* End of monitoring timeline styles */ - -/* Monitoring groupsummary styles */ - -table.groupview { - width: 100%; - margin-top: 1em; - border-collapse: separate; - border-spacing: 0.1em; - - th { - font-size: 0.9em; - text-align: left; - white-space: nowrap; - } - - td { - &.groupname { - width: 60%; - - a { - color: inherit; - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } - } - - &.total { - width: 10%; - } - - &.state { - width: 20%; - white-space: nowrap; - - &.change { - width: 10%; - text-align: center; - border-left-width: 1.5em; - border-left-style: solid; - padding: 0.3em 0.5em 0.3em 0.5em; - - strong { - font-size: 0.8em; - } - - span.timesince { - font-size: 0.8em; - } - - &.ok { - border-color: @colorOk; - } - - &.pending { - border-color: @colorPending; - } - - &.warning { - border-color: @colorWarningHandled; - - &.unhandled { - color: white; - border-left-width: 0; - background-color: @colorWarning; - } - } - - &.unknown { - border-color: @colorUnknownHandled; - - &.unhandled { - color: white; - border-left-width: 0; - background-color: @colorUnknown; - } - } - - &.critical { - border-color: @colorCriticalHandled; - - &.unhandled { - color: white; - border-left-width: 0; - background-color: @colorCritical; - } - } - } - - span.state { - &.handled { - margin-right: 2px; - } - - a { - font-size: 0.9em; - color: white; - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } - } - } - } -} - -/* End of monitoring groupsummary styles */ diff --git a/public/css/icinga/pagination.less b/public/css/icinga/pagination.less index e52b28403..35e0e7edd 100644 --- a/public/css/icinga/pagination.less +++ b/public/css/icinga/pagination.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ ul.pagination { font-size: 0.68em; @@ -63,3 +62,18 @@ ul.pagination { cursor: default; } +a.show-more, a.load-more { + display: block; + margin: 0.5em; +} + +a.load-more-hint { + display: inline-block; + margin-left: 1em; +} + +div.load-more-container { + display: table; + margin: 0 auto; + margin-top: 0.5em; +} \ No newline at end of file diff --git a/public/css/icinga/selection-toolbar.less b/public/css/icinga/selection-toolbar.less index ca177ebbb..05de1392f 100644 --- a/public/css/icinga/selection-toolbar.less +++ b/public/css/icinga/selection-toolbar.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ div.selection-toolbar { float: right; diff --git a/public/css/icinga/setup.less b/public/css/icinga/setup.less index 166e47ff8..1f71be9e0 100644 --- a/public/css/icinga/setup.less +++ b/public/css/icinga/setup.less @@ -1,3 +1,5 @@ +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + #layout { overflow: auto; // TODO: Shouldn't be necessary, here, IMHO } @@ -33,6 +35,7 @@ color: white; font-size: 0.9em; text-align: center; + border-bottom: none; } table { @@ -100,83 +103,92 @@ form { h2 { font-size: 2.0em; - color: @colorTextDefault; - border-bottom: 2px solid @colorPetrol; } } } } #setup div.buttons { - margin: 1.5em 0; + margin-top: 1.5em; // Yes, -top and -bottom, keep it like that... + margin-bottom: 1.5em; .double { position: absolute; left: -1337px; } - #btn_prev { - margin-right: 1em; - font-size: 0.9em; - } - - #btn_next { - font-size: 0.9em; - } - - button, .button-like { - font-size: 0.9em; - font-weight: bold; - outline: 0; - color: #fff; - border: 2px solid; - border-color: @colorPetrol; - background: @colorPetrol; - - &[disabled="1"] { - background-color: #aaa; - border: 1px solid black; - } - - &:hover, &:focus, &:active { - background-color: #666; - border-color: #666; - - &[disabled="1"] { - background-color: #aaa; - } - } - - &.finish, &.login { - min-width: 25em; - color: #fffafa; - background: @colorPetrol; - - &:hover, &:focus, &:active { - background: #666; - } - } - } - - a.button-like { - cursor: default; - text-decoration: none; + button.finish, a.button-like.login { + min-width: 25em; } } -#setup table.requirements { - font-size: 0.9em; - margin: -1em -1em 2em; - border-spacing: 1em; - border-collapse: separate; - border-bottom: 2px solid @colorPetrol; +#setup div.buttons + ul.hints { + margin-top: -1.5em; + margin-bottom: 1.5em; +} - td { - h2 { - margin: 0 1em 0 0; +form#setup_requirements { + margin-top: 2em; + padding-top: 0.5em; + border-top: 1px solid #888; + + div.buttons div.requirements-refresh { + width: 25%; + float: right; + text-align: center; + + a.button-like { + padding: 0.1em 0.4em; + } + } +} + +#setup ul.requirements { + margin: 0; + padding: 0; + list-style-type: none; + + li { + margin-bottom: 1em; + + & > ul { + margin: 0; + padding: 0; + list-style-type: none; } - &.state { + div { + float: left; + padding-top: 0.4em; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + } + + div.title { + width: 25%; + + h3 { + padding: 0; + margin: 0 1em 0 0; + border-bottom: 0; + } + } + + div.description { + width: 50%; + border-left: 0.4em solid transparent; + border-right: 0.4em solid transparent; + + ul { + margin: 0; + padding-left: 1em; + list-style-type: square; + } + } + + div.state { + width: 25%; color: white; padding: 0.4em; @@ -193,24 +205,6 @@ background-color: @colorCritical; } } - - &.btn-update { - padding-top: 0.3em; - text-align: center; - - div.buttons { - margin: 0; - - a.button-like { - padding: 0.2em 0.5em; - background-color: @colorPetro; - - &:hover, &:focus { - background-color: #666; - } - } - } - } } } @@ -270,55 +264,41 @@ } } - form#setup_summary { + form.summary { clear: left; } } -.conspicuous-state-notification { - width: 66%; - margin: 0 auto; - padding: 0.5em; - color: white; - font-weight: bold; -} - #setup-finish { - div.report { - padding: 1em; - border: 1px solid lightgrey; - border-radius: 0em; + h2 { + padding: 0.5em; + border-bottom: 0; + font-variant: normal; + font-weight: bold; + color: white; - div.line-separator { - width: 50%; - height: 1px; - margin: 0 auto; - background-color: white; + &.success { + background-color: @colorOk; } - p { - margin: 1em; - color: #444; - text-align: center; - - &.error { - color: red; - } - - &.failure { - .conspicuous-state-notification; - background-color: @colorCritical; - } - - &.success { - .conspicuous-state-notification; - background-color: @colorOk; - } + &.failure { + background-color: @colorCritical; } } + pre.log-output { + width: 66%; + height: 25em; + max-height: none; + } + div.buttons { + margin-top: 0; text-align: center; + + a { + padding: 0.5em; + } } } @@ -328,10 +308,7 @@ h2 { font-size: 2.0em; - color: @colorTextDefault; - border-bottom: 2px solid @colorPetrol; margin-bottom: 2em; - } div.info { @@ -352,14 +329,12 @@ font-size: 0.9em; border: 1px solid lightgrey; - div.title { - color: white; + h3 { padding: 0.2em; margin: -1em -1em 1em; text-align: center; color: @colorTextDefault; - border-bottom: 2px solid @colorPetrol; - + background-color: #f6fafa; } img { @@ -395,71 +370,45 @@ } } -#setup { - div.module-wizard { - width: auto; - padding: 1em; - overflow: hidden; - border: solid 1px lightgrey; +#setup_modules { + div.module { + float: left; + width: 15em; + height: 15em; + margin: 1em; + padding: 0.3em; + border: 1px solid #ccc; + background-color: snow; - div.buttons { - margin: 1.5em 0 0; - float: right; + h3 { + border: none; + margin: 0.5em 0; + text-align: center; - button[type=submit] { - padding: 0.5em; - line-height: 0.5em; - background-color: @colorPetrol; - - &:hover, &:focus, &:active { - background-color: #666; - } + label { + margin: 0; + width: 15em; + cursor: pointer; } } + + h3 + label { + width: 13.5em; + height: 13.9em; + overflow: auto; + cursor: pointer; + font-weight: normal; + } + + input[type=checkbox] { + height: 10em; + float: right; + margin: 0; + } } - div.module-menu { - font-size: 0.9em; - width: 25%; - float: right; - margin-left: 1.5em; - - p { - margin-top: 0; - - &.all-completed { - .conspicuous-state-notification; - text-align: center; - font-size: 90%; - background-color: @colorOk; - } - } - - ul { - padding-left: 1.2em; - - button { - margin: 0 0 0.8em; - padding: 0; - color: black; - border: none; - outline: none; - font-size: 90%; - background-color: white; - - &:hover { - color: #666; - cursor: pointer; - } - - &:focus, &:active { - color: #666; - } - } - - img { - margin: 0 0.5em 0.2em; - } - } + div.buttons { + padding-top: 1em; + clear: both; } } diff --git a/public/css/icinga/tabs.less b/public/css/icinga/tabs.less index 84f58e2a3..2c02bfb94 100644 --- a/public/css/icinga/tabs.less +++ b/public/css/icinga/tabs.less @@ -1,13 +1,13 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ ul.tabs { - padding: 1em 0 0 0; + padding: 0; list-style-type: inside; } .controls ul.tabs { - margin-left: 0em; + margin-left: -1em; + margin-right: -1em; margin-top: -3.54em; height: 2.6em; overflow: hidden; @@ -137,3 +137,31 @@ ul.tabs img.icon { a.close-tab { display: none; } + +.spinner > i { + line-height: 1; +} + +.spinner.active > i { + .animate(spin 2s infinite linear); + &:before { + // icon-spin6 + content: '\e874'; + } +} + +span.display-on-hover { + font-size: 0.8em; + left: -9000px; + position: relative; + display: inline; + width: 0; + overflow: hidden; + color: #000000; + text-decoration: none; +} + +:hover > span.display-on-hover, :focus > span.display-on-hover { + left:1em; width:12em; + text-align: center +} diff --git a/public/css/icinga/widgets.less b/public/css/icinga/widgets.less index 93625b0dc..7e6789dff 100644 --- a/public/css/icinga/widgets.less +++ b/public/css/icinga/widgets.less @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ table.historycolorgrid { font-size: 1.5em; @@ -30,7 +29,7 @@ table.historycolorgrid td.weekday { opacity: 1.0; } -table.historycolorgrid a { +table.historycolorgrid a, table.historycolorgrid span { margin: 0; text-decoration: none; display: block; @@ -49,8 +48,22 @@ table.multiselect tr[href] td { -ms-user-select: none; } -#main form.filterEditor input[type=text], #main form.filterEditor select { - width: 12em; +#main div.filter { + margin-top: 1em; + + form.editor { + input[type=text], select { + width: 12em; + } + + ul.tree li.active { + background-color: #eee; + } + + div.buttons { + float: right; + } + } } ul.datafilter li { @@ -204,23 +217,13 @@ li li .badge-container { margin-right: 0.75em; } -/* -#layout.hoveredmenu .active > .badge-container { - display: none; -} - -#layout.hoveredmenu .hover > .badge-container { - //margin-right: 14.15em; - display: none; -} -*/ - .badge { position: relative; - top: 0.3em; + top: -0.15em; display: inline-block; min-width: 1em; padding: 3px 7px; + margin: 0 0.2em 0 0.2em; font-size: 0.8em; font-weight: 700; line-height: 1.1em; @@ -231,29 +234,34 @@ li li .badge-container { background-color: @colorInvalid; } -#menu > ul > li.active > .badge-container { +#menu nav ul .badge { + margin-right: 0em; + top: 0.3em; +} + +#menu nav > ul > li.active > .badge-container { display: none; } -#menu > ul > li.hover > .badge-container { +#menu nav > ul > li.hover > .badge-container { display: none; } -#menu > ul > li.active > ul > li .badge-container { +#menu nav > ul > li.active > ul > li .badge-container { position: relative; top: -0.5em; } -#menu > ul > li.hover > ul > li > a { +#menu nav > ul > li.hover > ul > li > a { width: 12.5em; } -#menu > ul > li.hover > ul > li .badge-container { +#menu nav > ul > li.hover > ul > li .badge-container { position: relative; top: -0.5em; } -#menu > ul > li.hover > ul > li { +#menu nav > ul > li.hover > ul > li { // prevent floating badges from resizing list items in webkit //max-height: 2em; } @@ -266,6 +274,10 @@ li li .badge { background-color: @colorCritical; } +.badge-down { + background-color: @colorCritical; +} + .badge-warning { background-color: @colorWarning; } @@ -274,14 +286,55 @@ li li .badge { background-color: @colorOk; } +.badge-up { + background-color: @colorOk; +} + .badge-pending { background-color: @colorPending; } -.badge-pending { +.badge-unknown { background-color: @colorUnknown; } -.widgetFilter li.active { - background-color: #eee; +.sparkline-box { + position: relative; + top: -3px; + float: right; } + +.dashboard .sparkline-box { + top: -3px; +} + +.sparkline { + width: 12px; + height: 12px; + position: relative; + top: 4px; + + margin: 0em 0em 0em 0.1em; +} + +.tipsy .tipsy-inner { + // overwrite tooltip max width, we need them to grow bigger + max-width: 300px; + text-align: left; +} + +.color-box { + position: relative; + top: 2px; + margin: 0px 3px 0px 3px; + display: inline-block; + width: 12px; + height: 12px; +} + +.oneline { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + diff --git a/public/css/pdf/pdfprint.less b/public/css/pdf/pdfprint.less index 874a5e518..7b7855eaf 100644 --- a/public/css/pdf/pdfprint.less +++ b/public/css/pdf/pdfprint.less @@ -1,10 +1,14 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ -.controls form, .controls .pagination, .controls > .tabs, .dontprint, .inlinepie { +.controls form, .controls .pagination, .controls .widgetLimiter, .controls > .tabs, .dontprint { display: none !important; } +table.action img.inlinepie { + width: 50%; + height: 50%; +} + @page { margin: 2cm; } diff --git a/application/fonts/fontello-ifont/font/ifont.eot b/public/font/ifont.eot similarity index 93% rename from application/fonts/fontello-ifont/font/ifont.eot rename to public/font/ifont.eot index 22e4b3011..d249dc858 100644 Binary files a/application/fonts/fontello-ifont/font/ifont.eot and b/public/font/ifont.eot differ diff --git a/application/fonts/fontello-ifont/font/ifont.svg b/public/font/ifont.svg similarity index 99% rename from application/fonts/fontello-ifont/font/ifont.svg rename to public/font/ifont.svg index ae7e08c46..e712cc6d6 100644 --- a/application/fonts/fontello-ifont/font/ifont.svg +++ b/public/font/ifont.svg @@ -1,7 +1,7 @@ -Copyright (C) 2014 by original authors @ fontello.com +Copyright (C) 2015 by original authors @ fontello.com @@ -122,6 +122,7 @@ + \ No newline at end of file diff --git a/application/fonts/fontello-ifont/font/ifont.ttf b/public/font/ifont.ttf similarity index 93% rename from application/fonts/fontello-ifont/font/ifont.ttf rename to public/font/ifont.ttf index 931b3d157..e222d9669 100644 Binary files a/application/fonts/fontello-ifont/font/ifont.ttf and b/public/font/ifont.ttf differ diff --git a/public/font/ifont.woff b/public/font/ifont.woff new file mode 100644 index 000000000..9cde04cd0 Binary files /dev/null and b/public/font/ifont.woff differ diff --git a/public/index.php b/public/index.php index 48a500666..3040f0589 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,4 @@ 1 && self.hasMultiselection()) { + selections.each(function (i, el) { + var parts = []; + $.each(self.getRowData($(el)), function(key, value) { + parts.push(utils.fixedEncodeURIComponent(key) + '=' + utils.fixedEncodeURIComponent(value)); + }); + queries.push('(' + parts.join('&') + ')'); + }); + return self.getMultiselectionUrl() + '?(' + queries.join('|') + ')'; + } else { + return ''; + } + }, + + /** + * Refresh the displayed active columns using the current page location + */ + refresh: function() { + this.clear(); + var hash = this.icinga.utils.parseUrl(window.location.href).hash; + if (this.hasMultiselection()) { + var query = parseSelectionQuery(hash); + if (query.length > 1 && this.getMultiselectionUrl() === this.icinga.utils.parseUrl(hash.substr(1)).path) { + // select all rows with matching filters + var self = this; + $.each(query, function(i, selection) { + self.select(selection); + }); + } + if (query.length > 1) { + return; + } + } + this.selectUrl(hash.substr(1)); + } + }; + + Icinga.Behaviors = Icinga.Behaviors || {}; + + var ActionTable = function (icinga) { + Icinga.EventListener.call(this, icinga); + + /** + * If currently loading + * + * @var Boolean + */ + this.loading = false; + + this.on('rendered', this.onRendered, this); + this.on('click', 'table.action tr[href]', this.onRowClicked, this); + }; + ActionTable.prototype = new Icinga.EventListener(); + + /** + * Return all active tables in this table, or in the context as jQuery selector + * + * @param context {HTMLElement} + * @returns {jQuery} + */ + ActionTable.prototype.tables = function(context) { + if (context) { + return $(context).find('table.action'); + } + return $('table.action'); + }; + + /** + * Handle clicks on table rows and update selection and history + */ + ActionTable.prototype.onRowClicked = function (event) { + var self = event.data.self; + var $target = $(event.target); + var $tr = $target.closest('tr'); + var table = new Selection($tr.closest('table.action')[0], self.icinga); + + // some rows may contain form actions that trigger a different action, pass those through + if (!$target.hasClass('rowaction') && $target.closest('form').length && + ($target.closest('a').length || $target.closest('button').length)) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + + // update selection + if (table.hasMultiselection()) { + if (event.ctrlKey || event.metaKey) { + // add to selection + table.toggle($tr); + } else if (event.shiftKey) { + // range selection + table.range($tr); + } else { + // single selection + table.clear(); + table.select($tr); + } + } else { + table.clear(); + table.select($tr); + } + + // update history + var url = self.icinga.utils.parseUrl(window.location.href.split('#')[0]); + var count = table.selections().length; + var state = url.path + url.query; + if (count > 0) { + var query = table.toQuery(); + self.icinga.loader.loadUrl(query, self.icinga.events.getLinkTargetFor($tr)); + state += '#!' + query; + } else { + if (self.icinga.events.getLinkTargetFor($tr).attr('id') === 'col2') { + self.icinga.ui.layout1col(); + } + } + self.icinga.history.pushUrl(state); + + // redraw all table selections + self.tables().each(function () { + new Selection(this, self.icinga).refresh(); + }); + + // update selection info + $('.selection-info-count').text(table.selections().size()); + return false; + }; + + /** + * Render the selection and prepare selection rows + */ + ActionTable.prototype.onRendered = function(evt) { + var container = evt.target; + var self = evt.data.self; + + // initialize all rows with the correct link + $('table.action tr', container).each(function(idx, el) { + // IE will not ignore user-select unless we cancel selectstart + $(el).on('selectstart', false); + + var $a = $('a[href].rowaction', el).first(); + if ($a.length) { + // TODO: Find out whether we leak memory on IE with this: + $(el).attr('href', $a.attr('href')); + return; + } + $a = $('a[href]', el).first(); + if ($a.length) { + $(el).attr('href', $a.attr('href')); + } + }); + + // draw all active selections that have disappeared on reload + self.tables().each(function(i, el) { + new Selection(el, self.icinga).refresh(); + }); + + // update displayed selection counter + var table = new Selection(self.tables(container).first()); + $(container).find('.selection-info-count').text(table.selections().size()); + }; + + ActionTable.prototype.clearAll = function () { + var self = this; + this.tables().each(function () { + new Selection(this, self.icinga).clear(); + }); + }; + + Icinga.Behaviors.ActionTable = ActionTable; + +}) (Icinga, jQuery); diff --git a/public/js/icinga/behavior/form.js b/public/js/icinga/behavior/form.js index d289183d6..f2a7ed8cd 100644 --- a/public/js/icinga/behavior/form.js +++ b/public/js/icinga/behavior/form.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * Controls behavior of form elements, depending reload and diff --git a/public/js/icinga/behavior/navigation.js b/public/js/icinga/behavior/navigation.js index fde043c01..0d3300944 100644 --- a/public/js/icinga/behavior/navigation.js +++ b/public/js/icinga/behavior/navigation.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ (function(Icinga, $) { @@ -15,7 +14,7 @@ this.on('click', '#menu tr[href]', this.linkClicked, this); this.on('mouseenter', 'li.dropdown', this.dropdownHover, this); this.on('mouseleave', 'li.dropdown', this.dropdownLeave, this); - this.on('mouseenter', '#menu > ul > li', this.menuTitleHovered, this); + this.on('mouseenter', '#menu > nav > ul > li', this.menuTitleHovered, this); this.on('mouseleave', '#sidebar', this.leaveSidebar, this); this.on('rendered', this.onRendered); }; @@ -32,6 +31,15 @@ if ($outerMenu.size()) { $outerMenu.addClass('active'); } + + /* + Recreate the html content of the menu item to force the browser to update the layout, or else + the link would only be visible as active after another click or page reload in Gecko and WebKit. + + fixes #7897 + */ + $selectedMenu.html($selectedMenu.html()); + } else { // store menu state var $menus = $('#menu li.active', el); @@ -77,8 +85,7 @@ $menu.data('icinga-url', menuDataUrl); }; - Navigation.prototype.setActiveByUrl = function(url) - { + Navigation.prototype.setActiveByUrl = function(url) { this.resetActive(); this.setActive($('#menu [href="' + url + '"]')); } @@ -121,23 +128,26 @@ } setTimeout(function () { - if (! $li.is('li:hover')) { - return; - } - if ($li.hasClass('active')) { - return; - } + try { + if (!$li.is('li:hover')) { + return; + } + if ($li.hasClass('active')) { + return; + } + } catch(e) { /* Bypass because if IE8 */ } $li.siblings().each(function () { var $sibling = $(this); - if ($sibling.is('li:hover')) { - return; - } + try { + if ($sibling.is('li:hover')) { + return; + } + } catch(e) { /* Bypass because if IE8 */ }; if ($sibling.hasClass('hover')) { $sibling.removeClass('hover'); } }); - self.hoverElement($li); }, delay); }; @@ -152,9 +162,11 @@ } setTimeout(function () { - if ($li.is('li:hover') || $sidebar.is('sidebar:hover') ) { - return; - } + try { + if ($li.is('li:hover') || $sidebar.is('sidebar:hover')) { + return; + } + } catch(e) { /* Bypass because if IE8 */ }; $li.removeClass('hover'); $('#layout').removeClass('hoveredmenu'); }, 500); @@ -174,9 +186,11 @@ self = event.data.self; setTimeout(function () { // TODO: make this behave well together with keyboard navigation - if (! $li.is('li:hover') /*&& ! $li.find('a:focus')*/) { - $li.removeClass('hover'); - } + try { + if (!$li.is('li:hover') /*&& ! $li.find('a:focus')*/) { + $li.removeClass('hover'); + } + } catch(e) { /* Bypass because if IE8 */ } }, 300); }; Icinga.Behaviors.Navigation = Navigation; diff --git a/public/js/icinga/behavior/sparkline.js b/public/js/icinga/behavior/sparkline.js index 33cc0d34b..ee352002a 100644 --- a/public/js/icinga/behavior/sparkline.js +++ b/public/js/icinga/behavior/sparkline.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ (function(Icinga, $) { @@ -16,36 +15,37 @@ Sparkline.prototype.onRendered = function(evt) { var el = evt.target; - $('span.sparkline', el).each(function(i, element) { + $('.sparkline', el).each(function(i, element) { // read custom options - var $spark = $(element); - var labels = $spark.attr('labels').split('|'); - var formatted = $spark.attr('formatted').split('|'); - var tooltipChartTitle = $spark.attr('sparkTooltipChartTitle') || ''; - var format = $spark.attr('tooltipformat'); - var hideEmpty = $spark.attr('hideEmptyLabel') === 'true'; - $spark.sparkline( - 'html', - { + var $spark = $(element); + var title = $spark.attr('title'); + + if ($spark.attr('labels')) { + $spark.removeAttr('original-title'); + } + + var options; + if ($spark.hasClass('sparkline-perfdata')) { + options = { enableTagOptions: true, - tooltipFormatter: function (sparkline, options, fields) { - var out = format; - if (hideEmpty && fields.offset === 3) { - return ''; - } - var replace = { - title: tooltipChartTitle, - label: labels[fields.offset] ? labels[fields.offset] : fields.offset, - formatted: formatted[fields.offset] ? formatted[fields.offset] : '', - value: fields.value, - percent: Math.round(fields.percent * 100) / 100 - }; - $.each(replace, function(key, value) { - out = out.replace('{{' + key + '}}', value); - }); - return out; - } - }); + width: 16, + height: 16, + title: title, + disableTooltips: true, + borderWidth: 1.4, + borderColor: '#FFF' + }; + $spark.sparkline('html', options); + } else if ($spark.hasClass('sparkline-multi')) { + options = { + width: 100, + height: 100, + title: title, + enableTagOptions: true + }; + $spark.sparkline('html', options); + } + }); }; diff --git a/public/js/icinga/behavior/tooltip.js b/public/js/icinga/behavior/tooltip.js index de5b66d05..e0826b005 100644 --- a/public/js/icinga/behavior/tooltip.js +++ b/public/js/icinga/behavior/tooltip.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ (function(Icinga, $) { @@ -28,10 +27,30 @@ var $el = $(this); $el.attr('title', $el.data('title-rich') || $el.attr('title')); }); - $('svg rect.chart-data[title]', el).tipsy({ gravity: 'se', html: true }); - $('.historycolorgrid a[title]', el).tipsy({ gravity: 's', offset: 2 }); - $('img.icon[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, offset: 2 }); - $('[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, delayIn: 500 }); + $('svg .chart-data', el).tipsy({ gravity: 'se', html: true }); + $('i[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, offset: 2 }); + $('[title]', el).each(function (i, el) { + var $el = $(el); + var delay, gravity; + if ($el.data('tooltip-delay') !== undefined) { + delay = $el.data('tooltip-delay'); + } + if ($el.data('tooltip-gravity')) { + gravity = $el.data('tooltip-gravity'); + } + if (delay === undefined && + gravity === undefined && + !$el.data('title-rich')) { + // use native tooltips for everything that doesn't + // require specific behavior or html markup + return; + } + delay = delay === undefined ? 500 : delay; + $el.tipsy({ + gravity: gravity || $.fn.tipsy.autoNS, + delayIn: delay + }); + }); // migrate or remove all orphaned tooltips $('.tipsy').each(function () { diff --git a/public/js/icinga/behavior/tristate.js b/public/js/icinga/behavior/tristate.js index 036c25cea..7170feb06 100644 --- a/public/js/icinga/behavior/tristate.js +++ b/public/js/icinga/behavior/tristate.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ (function(Icinga, $) { @@ -43,7 +42,7 @@ } else { $tristate.parent().find('b.tristate-changed').css('visibility', 'hidden'); } - self.icinga.ui.setTriState(value.toString(), $tristate); + self.icinga.ui.setTriState(value.toString(), $tristate); }; Icinga.Behaviors.Tristate = Tristate; diff --git a/public/js/icinga/eventlistener.js b/public/js/icinga/eventlistener.js index e1946c6ae..ce1e3715e 100644 --- a/public/js/icinga/eventlistener.js +++ b/public/js/icinga/eventlistener.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * EventListener contains event handlers and can bind / and unbind them from diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index e7ac3e3f1..71408d712 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * Icinga.Events @@ -14,16 +13,11 @@ this.icinga = icinga; this.searchValue = ''; + this.initializeModules = true; }; Icinga.Events.prototype = { - keyboard: { - ctrlKey: false, - altKey: false, - shiftKey: false - }, - /** * Icinga will call our initialize() function once it's ready */ @@ -37,51 +31,63 @@ }, // TODO: What's this? - applyHandlers: function (evt) { - var el = $(evt.target), self = evt.data.self; + applyHandlers: function (event) { + var $target = $(event.target); + var self = event.data.self; var icinga = self.icinga; - $('.dashboard > div', el).each(function(idx, el) { - var url = $(el).data('icingaUrl'); - if (typeof url === 'undefined') return; - icinga.loader.loadUrl(url, $(el)).autorefresh = true; - }); - - // Set first links href in a action table tr as row href: - $('table.action tr', el).each(function(idx, el) { - var $a = $('a[href]', el).first(); - if ($a.length) { - // TODO: Find out whether we leak memory on IE with this: - $(el).attr('href', $a.attr('href')); + if (self.initializeModules) { + var loaded = false; + var moduleName = $target.data('icingaModule'); + if (moduleName) { + if (icinga.hasModule(moduleName) && !icinga.isLoadedModule(moduleName)) { + loaded |= icinga.loadModule(moduleName); + } } - }); - $('td.state span.timesince').attr('title', null); + $('.icinga-module', $target).each(function(idx, mod) { + moduleName = $(mod).data('icingaModule'); + if (icinga.hasModule(moduleName) && !icinga.isLoadedModule(moduleName)) { + loaded |= icinga.loadModule(moduleName); + } + }); - var moduleName = el.data('icingaModule'); - if (moduleName) { - if (icinga.hasModule(moduleName)) { - var module = icinga.module(moduleName); - // NOT YET, the applyOnloadDings: module.applyEventHandlers(mod); + if (loaded) { + // Modules may register their own handler for the 'renderend' event + // so we need to ensure that it is called the first time they are + // initialized + event.stopImmediatePropagation(); + self.initializeModules = false; + + var $container = $target.closest('.container'); + if (! $container.length) { + // The page obviously got loaded for the first time, + // so we'll trigger the event for all containers + $container = $('.container'); + } + + $container.trigger('rendered'); + + // But since we're listening on this event by ourself, we'll have + // to abort our own processing as we'll process it twice otherwise + return false; } + } else { + self.initializeModules = true; } - $('.icinga-module', el).each(function(idx, mod) { - var $mod = $(mod); - moduleName = $mod.data('icingaModule'); - if (icinga.hasModule(moduleName)) { - var module = icinga.module(moduleName); - // NOT YET, the applyOnloadDings: module.applyEventHandlers(mod); + $('.dashboard > div', $target).each(function(idx, el) { + var $element = $(el); + var $url = $element.data('icingaUrl'); + if (typeof $url !== 'undefined') { + icinga.loader.loadUrl($url, $element).autorefresh = true; } }); - if (document.activeElement === document.body) { - $('input.autofocus', el).focus(); - } - var searchField = $('#menu input.search', el); + var $searchField = $('#menu input.search', $target); // Remember initial search field value if any - if (searchField.length && searchField.val().length) { - self.searchValue = searchField.val(); + if ($searchField.length && $searchField.val().length) { + self.searchValue = $searchField.val(); } if (icinga.ui.isOneColLayout()) { @@ -95,13 +101,14 @@ * Global default event handlers */ applyGlobalDefaults: function () { + // Apply element-specific behavior whenever the layout is rendered + // Note: It is important that this is the first handler for this event! + $(document).on('rendered', { self: this }, this.applyHandlers); + $.each(self.icinga.behaviors, function (name, behavior) { behavior.bind($(document)); }); - // Apply element-specific behavior whenever the layout is rendered - $(document).on('rendered', { self: this }, this.applyHandlers); - // We catch resize events $(window).on('resize', { self: this.icinga.ui }, this.icinga.ui.onWindowResize); @@ -115,15 +122,13 @@ // We catch scroll events in our containers $('.container').on('scroll', { self: this }, this.icinga.events.onContainerScroll); + // Remove notifications on click + $(document).on('click', '#notifications li', function () { $(this).remove(); }); + // We want to catch each link click $(document).on('click', 'a', { self: this }, this.linkClicked); $(document).on('click', 'tr[href]', { self: this }, this.linkClicked); - // Select a table row - $(document).on('click', 'table.multiselect tr[href]', { self: this }, this.rowSelected); - - $(document).on('click', 'button', { self: this }, this.submitForm); - // We catch all form submit events $(document).on('submit', 'form', { self: this }, this.submitForm); @@ -131,6 +136,10 @@ $(document).on('change', 'form select.autosubmit', { self: this }, this.autoSubmitForm); $(document).on('change', 'form input.autosubmit', { self: this }, this.autoSubmitForm); + // Automatically check a radio button once a specific input is focused + $(document).on('focus', 'form select[data-related-radiobtn]', { self: this }, this.autoCheckRadioButton); + $(document).on('focus', 'form input[data-related-radiobtn]', { self: this }, this.autoCheckRadioButton); + $(document).on('keyup', '#menu input.search', {self: this}, this.autoSubmitSearch); $(document).on('click', '.tree .handle', { self: this }, this.treeNodeToggle); @@ -169,6 +178,15 @@ icinga.ui.fixControls(); }, + autoCheckRadioButton: function (event) { + var $input = $(event.currentTarget); + var $radio = $('#' + $input.attr('data-related-radiobtn')); + if ($radio.length) { + $radio.prop('checked', true); + } + return true; + }, + autoSubmitSearch: function(event) { var self = event.data.self; if ($('#menu input.search').val() === self.searchValue) { @@ -186,14 +204,13 @@ * */ submitForm: function (event, autosubmit) { - //return false; var self = event.data.self; var icinga = self.icinga; // .closest is not required unless subelements to trigger this var $form = $(event.currentTarget).closest('form'); - var regex = new RegExp('&', 'g'); - var url = $form.attr('action').replace(regex, '&'); // WHY?? + var url = $form.attr('action'); var method = $form.attr('method'); + var encoding = $form.attr('enctype'); var $button = $('input[type=submit]:focus', $form).add('button[type=submit]:focus', $form); var $target; var data; @@ -204,16 +221,16 @@ if (typeof event.originalEvent !== 'undefined' && typeof event.originalEvent.explicitOriginalTarget === 'object') { // Firefox $el = $(event.originalEvent.explicitOriginalTarget); - icinga.logger.info('events/submitForm: Button is event.originalEvent.explicitOriginalTarget'); + icinga.logger.debug('events/submitForm: Button is event.originalEvent.explicitOriginalTarget'); } else { $el = $(event.currentTarget); - icinga.logger.info('events/submitForm: Button is event.currentTarget'); + icinga.logger.debug('events/submitForm: Button is event.currentTarget'); } if ($el && ($el.is('input[type=submit]') || $el.is('button[type=submit]'))) { $button = $el; } else { - icinga.logger.error( + icinga.logger.debug( 'events/submitForm: Can not determine submit button, using the first one in form' ); } @@ -225,17 +242,32 @@ method = method.toUpperCase(); } + if (typeof encoding === 'undefined') { + encoding = 'application/x-www-form-urlencoded'; + } + if ($button.length === 0) { $button = $('input[type=submit]', $form).add('button[type=submit]', $form).first(); } - event.stopPropagation(); - event.preventDefault(); + if ($button.length) { + // Activate spinner + if ($button.hasClass('spinner')) { + $button.addClass('active'); + } + + $target = self.getLinkTargetFor($button); + } else { + $target = self.getLinkTargetFor($form); + } + + if (! url) { + // Use the URL of the target container if the form's action is not set + url = $target.closest('.container').data('icinga-url'); + } icinga.logger.debug('Submitting form: ' + method + ' ' + url, method); - $target = self.getLinkTargetFor($form); - if (method === 'GET') { var dataObj = $form.serializeObject(); @@ -247,19 +279,59 @@ url = icinga.utils.addUrlParams(url, dataObj); } else { - data = $form.serializeArray(); + if (encoding === 'multipart/form-data') { + if (typeof window.FormData === 'undefined') { + icinga.loader.submitFormToIframe($form, url, $target); + + // Disable all form controls to prevent resubmission as early as possible. + // (This relies on native form submission, so using setTimeout is the only possible solution) + setTimeout(function () { + $form.find(':input:not(:disabled)').prop('disabled', true); + }, 0); + + if (! typeof autosubmit === 'undefined' && autosubmit) { + if ($button.length) { + // We're autosubmitting the form so the button has not been clicked, however, + // to be really safe, we're disabling the button explicitly, just in case.. + $button.prop('disabled', true); + } + + $form[0].submit(); // This should actually not trigger the onSubmit event, let's hope that this is true for all browsers.. + event.stopPropagation(); + event.preventDefault(); + return false; + } else { + return true; + } + } + + data = new window.FormData($form[0]); + } else { + data = $form.serializeArray(); + } if (typeof autosubmit === 'undefined' || ! autosubmit) { if ($button.length && $button.attr('name') !== 'undefined') { - data.push({ - name: $button.attr('name'), - value: $button.attr('value') - }); + if (encoding === 'multipart/form-data') { + data.append($button.attr('name'), $button.attr('value')); + } else { + data.push({ + name: $button.attr('name'), + value: $button.attr('value') + }); + } } } } + + // Disable all form controls to prevent resubmission except for our search input + // Note that disabled form inputs will not be enabled via JavaScript again + $form.find(':input:not(#search):not(:disabled)').prop('disabled', true); + icinga.loader.loadUrl(url, $target, data, method); + event.stopPropagation(); + event.preventDefault(); return false; }, @@ -276,71 +348,6 @@ return false; }, - /** - * Handle table selection. - */ - rowSelected: function(event) { - var self = event.data.self; - var icinga = self.icinga; - var $tr = $(this); - var $table = $tr.closest('table.multiselect'); - var data = self.icinga.ui.getSelectionKeys($table); - var url = $table.data('icinga-multiselect-url'); - - event.stopPropagation(); - event.preventDefault(); - - if (!data) { - icinga.logger.error('multiselect table has no data-icinga-multiselect-data'); - return; - } - if (!url) { - icinga.logger.error('multiselect table has no data-icinga-multiselect-url'); - return; - } - - // update selection - if (event.ctrlKey || event.metaKey) { - icinga.ui.toogleTableRowSelection($tr); - // multi selection - } else if (event.shiftKey) { - // range selection - icinga.ui.addTableRowRangeSelection($tr); - } else { - // single selection - icinga.ui.setTableRowSelection($tr); - } - // focus only the current table. - icinga.ui.focusTable($table[0]); - - var $target = self.getLinkTargetFor($tr); - - var $trs = $table.find('tr[href].active'); - if ($trs.length > 1) { - var selectionData = icinga.ui.getSelectionSetData($trs, data); - var query = icinga.ui.selectionDataToQuery(selectionData); - icinga.loader.loadUrl(url + '?' + query, $target); - icinga.ui.storeSelectionData(selectionData); - icinga.ui.provideSelectionCount(); - } else if ($trs.length === 1) { - // display a single row - $tr = $trs.first(); - icinga.loader.loadUrl($tr.attr('href'), $target); - icinga.ui.storeSelectionData($tr.attr('href')); - icinga.ui.provideSelectionCount(); - } else { - // display nothing - if ($target.attr('id') === 'col2') { - icinga.ui.layout1col(); - } - icinga.ui.storeSelectionData(null); - icinga.ui.provideSelectionCount(); - } - - return false; - }, - - /** * Someone clicked a link or tr[href] */ @@ -348,6 +355,7 @@ var self = event.data.self; var icinga = self.icinga; var $a = $(this); + var $eventTarget = $(event.target); var href = $a.attr('href'); var linkTarget = $a.attr('target'); var $target; @@ -357,19 +365,17 @@ return true; } - // Special checks for link clicks in multiselect rows - if (! $a.is('tr[href]') && $a.closest('tr[href]').length > 0 && $a.closest('table.multiselect').length > 0) { + // Special checks for link clicks in action tables + if (! $a.is('tr[href]') && $a.closest('table.action').length > 0) { - // Forward clicks to ANY link with special key pressed to rowSelected - if (event.ctrlKey || event.metaKey || event.shiftKey) - { - return self.rowSelected.call($a.closest('tr[href]'), event); + // ignoray clicks to ANY link with special key pressed + if ($a.closest('table.multiselect').length > 0 && (event.ctrlKey || event.metaKey || event.shiftKey)) { + return true; } - // Forward inner links matching the row URL to rowSelected - if ($a.attr('href') === $a.closest('tr[href]').attr('href')) - { - return self.rowSelected.call($a.closest('tr[href]'), event); + // ignore inner links matching the row URL + if ($a.attr('href') === $a.closest('tr[href]').attr('href')) { + return true; } } @@ -384,12 +390,19 @@ return false; } - // Ignore form elements in action rows - if ($(event.target).is('input') || $(event.target).is('button')) { - return; + if (! $eventTarget.is($a)) { + if ($eventTarget.is('input') || $eventTarget.is('button')) { + // Ignore form elements in action rows + return; + } else { + var $button = $('input[type=submit]:focus').add('button[type=submit]:focus'); + if ($button.length > 0 && $.contains($button[0], $eventTarget[0])) { + // Ignore any descendant of form elements + return; + } + } } - // ignore multiselect table row clicks if ($a.is('tr') && $a.closest('table.multiselect').length > 0) { return; @@ -399,6 +412,18 @@ event.stopPropagation(); event.preventDefault(); + // This is an anchor only + if (href.substr(0, 1) === '#' && href.length > 1 + && href.substr(1, 1) !== '!') { + icinga.ui.focusElement(href.substr(1), $a.closest('.container')); + return; + } + + // activate spinner indicator + if ($a.hasClass('spinner')) { + $a.addClass('active'); + } + // If link has hash tag... if (href.match(/#/)) { if (href === '#') { @@ -420,7 +445,7 @@ formerUrl = $target.data('icingaUrl'); if (typeof formerUrl !== 'undefined' && formerUrl.split(/#/)[0] === href.split(/#/)[0]) { - icinga.ui.scrollContainerToAnchor($target, href.split(/#/)[1]); + icinga.ui.focusElement(href.split(/#/)[1], $target); $target.data('icingaUrl', href); if (formerUrl !== href) { icinga.history.pushCurrentState(); @@ -496,14 +521,6 @@ return $target; }, - /* - hrefIsHashtag: function(href) { - // WARNING: IE gives full URL :( - // Also it doesn't support negativ indexes in substr - return href.substr(href.length - 1, 1) == '#'; - }, - */ - unbindGlobalHandlers: function () { $.each(self.icinga.behaviors, function (name, behavior) { behavior.unbind($(document)); @@ -514,11 +531,11 @@ $(window).off('beforeunload', this.onUnload); $(document).off('scroll', '.container', this.onContainerScroll); $(document).off('click', 'a', this.linkClicked); - $(document).off('click', 'table.action tr[href]', this.rowSelected); - $(document).off('click', 'table.action tr a', this.rowSelected); $(document).off('submit', 'form', this.submitForm); - $(document).off('click', 'button', this.submitForm); $(document).off('change', 'form select.autosubmit', this.submitForm); + $(document).off('change', 'form input.autosubmit', this.submitForm); + $(document).off('focus', 'form select[data-related-radiobtn]', this.autoCheckRadioButton); + $(document).off('focus', 'form input[data-related-radiobtn]', this.autoCheckRadioButton); }, destroy: function() { diff --git a/public/js/icinga/history.js b/public/js/icinga/history.js index 9f05def2d..a94e5aaab 100644 --- a/public/js/icinga/history.js +++ b/public/js/icinga/history.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * Icinga.History @@ -93,7 +92,7 @@ // TODO: update navigation // Did we find any URL? Then push it! if (url !== '') { - window.history.pushState({icinga: true}, null, this.cleanupUrl(url)); + this.push(url); } }, @@ -102,13 +101,16 @@ if (!this.enabled) { return; } - window.history.pushState({icinga: true}, null, this.cleanupUrl(url)); + this.push(url); }, - cleanupUrl: function(url) { - url = url.replace(/_render=[a-z0-9]+&/, '').replace(/&_render=[a-z0-9]+/, '').replace(/\?_render=[a-z0-9]+$/, ''); - url = url.replace(/_reload=[a-z0-9]+&/, '').replace(/&_reload=[a-z0-9]+/, '').replace(/\?_reload=[a-z0-9]+$/, ''); - return url; + push: function (url) { + url = url.replace(/[\?&]?_(render|reload)=[a-z0-9]+/g, ''); + if (this.lastPushUrl === url) { + return; + } + this.lastPushUrl = url; + window.history.pushState({icinga: true}, null, url); }, /** @@ -137,8 +139,10 @@ icinga.logger.debug('History state', event.originalEvent.state); } - self.applyLocationBar(); + // keep the last pushed url in sync with history changes + self.lastPushUrl = location.href; + self.applyLocationBar(); }, applyLocationBar: function (onload) { @@ -156,7 +160,7 @@ icinga.loader.loadUrl( main, $('#col1') - ).historyTriggered = true; + ).addToHistory = false; } if (document.location.hash && document.location.hash.match(/^#!/)) { @@ -164,16 +168,17 @@ parts = document.location.hash.split(/#!/); if ($('#layout > #login').length) { - // We are on the login page! - $('#login form #redirect').val( - $('#login form #redirect').val() + '#!' + parts[1] + // We are on the login page + var redirect = $('#login form input[name=redirect]').first(); + redirect.val( + redirect.val() + '#!' + parts[1] ); } else { - if ($('#col2').data('icingaUrl') !== main) { + if ($('#col2').data('icingaUrl') !== parts[1]) { icinga.loader.loadUrl( parts[1], $('#col2') - ).historyTriggered = true; + ).addToHistory = false; } } diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index 31a0d10a8..56e2f8532 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * Icinga.Loader @@ -24,8 +23,6 @@ this.failureNotice = null; - this.exception = null; - /** * Pending requests */ @@ -103,13 +100,25 @@ headers['X-Icinga-WindowId'] = 'undefined'; } + // This is jQuery's default content type + var contentType = 'application/x-www-form-urlencoded; charset=UTF-8'; + + var isFormData = typeof window.FormData !== 'undefined' && data instanceof window.FormData; + if (isFormData) { + // Setting false is mandatory as the form's data + // won't be recognized by the server otherwise + contentType = false; + } + var self = this; var req = $.ajax({ type : method, url : url, data : data, headers: headers, - context: self + context: self, + contentType: contentType, + processData: ! isFormData }); req.$target = $target; @@ -117,9 +126,10 @@ req.done(this.onResponse); req.fail(this.onFailure); req.complete(this.onComplete); - req.historyTriggered = false; req.autorefresh = autorefresh; req.action = action; + req.addToHistory = true; + if (id) { this.requests[id] = req; } @@ -130,6 +140,41 @@ return req; }, + /** + * Mimic XHR form submission by using an iframe + * + * @param {object} $form The form being submitted + * @param {string} action The form's action URL + * @param {object} $target The target container + */ + submitFormToIframe: function ($form, action, $target) { + var self = this; + + $form.prop('action', self.icinga.utils.addUrlParams(action, { + '_frameUpload': true + })); + $form.prop('target', 'fileupload-frame-target'); + $('#fileupload-frame-target').on('load', function (event) { + var $frame = $(event.target); + var $contents = $frame.contents(); + + var $redirectMeta = $contents.find('meta[name="redirectUrl"]'); + if ($redirectMeta.length) { + self.redirectToUrl($redirectMeta.attr('content'), $target); + } else { + // Fetch the frame's new content and paste it into the target + self.renderContentToContainer( + $contents.find('body').html(), + $target, + 'replace' + ); + } + + $frame.prop('src', 'about:blank'); // Clear the frame's dom + $frame.off('load'); // Unbind the event as it's set on demand + }); + }, + /** * Create an URL relative to the Icinga base Url, still unused * @@ -246,49 +291,96 @@ } }, + /** + * Process the X-Icinga-Redirect HTTP Response Header + * + * If the response includes the X-Icinga-Redirect header, redirects to the URL associated with the header. + * + * @param {object} req Current request + * + * @returns {boolean} Whether we're about to redirect + */ processRedirectHeader: function(req) { - var icinga = this.icinga; - var redirect = req.getResponseHeader('X-Icinga-Redirect'); - if (! redirect) return false; + var icinga = this.icinga, + redirect = req.getResponseHeader('X-Icinga-Redirect'); + + if (! redirect) { + return false; + } + redirect = decodeURIComponent(redirect); if (redirect.match(/__SELF__/)) { - redirect = redirect.replace(/__SELF__/, encodeURIComponent(document.location.pathname + document.location.search + document.location.hash)); + if (req.autorefresh) { + // Redirect to the current window's URL in case it's an auto-refresh request. If authenticated + // externally this ensures seamless re-login if the session's expired + redirect = redirect.replace( + /__SELF__/, + encodeURIComponent( + document.location.pathname + document.location.search + document.location.hash + ) + ); + } else { + // Redirect to the URL which required authentication. When clicking a link this ensures that we + // redirect to the link's URL instead of the current window's URL (see above) + redirect = redirect.replace(/__SELF__/, req.url); + } } + + this.redirectToUrl(redirect, req.$target, req.getResponseHeader('X-Icinga-Rerender-Layout')); + return true; + }, + + /** + * Redirect to the given url + * + * @param {string} url + * @param {object} $target + * @param {boolean} rerenderLayout + */ + redirectToUrl: function (url, $target, rerenderLayout) { + var icinga = this.icinga; + + if (typeof rerenderLayout === 'undefined') { + rerenderLayout = false; + } + icinga.logger.debug( - 'Got redirect for ', req.$target, ', URL was ' + redirect + 'Got redirect for ', $target, ', URL was ' + url ); - if (req.getResponseHeader('X-Icinga-Rerender-Layout')) { - var parts = redirect.split(/#!/); - redirect = parts.shift(); - var redirectionUrl = this.addUrlFlag(redirect, 'renderLayout'); + if (rerenderLayout) { + var parts = url.split(/#!/); + url = parts.shift(); + var redirectionUrl = this.addUrlFlag(url, 'renderLayout'); var r = this.loadUrl(redirectionUrl, $('#layout')); - r.url = redirect; + r.url = url; if (parts.length) { r.loadNext = parts; + } else if (!! document.location.hash) { + // Retain detail URL if the layout is rerendered + parts = document.location.hash.split('#!').splice(1); + if (parts.length) { + r.loadNext = parts; + } } - } else { - - if (redirect.match(/#!/)) { - var parts = redirect.split(/#!/); + if (url.match(/#!/)) { + var parts = url.split(/#!/); icinga.ui.layout2col(); this.loadUrl(parts.shift(), $('#col1')); this.loadUrl(parts.shift(), $('#col2')); } else { - - if (req.$target.attr('id') === 'col2') { // TODO: multicol - if ($('#col1').data('icingaUrl') === redirect) { + if ($target.attr('id') === 'col2') { // TODO: multicol + if ($('#col1').data('icingaUrl').split('?')[0] === url.split('?')[0]) { icinga.ui.layout1col(); - req.$target = $('#col1'); + $target = $('#col1'); delete(this.requests['col2']); } } - this.loadUrl(redirect, req.$target); + this.loadUrl(url, $target); } } - return true; }, cacheLoadedIcons: function($container) { @@ -313,20 +405,12 @@ onResponse: function (data, textStatus, req) { var self = this; if (this.failureNotice !== null) { - this.failureNotice.remove(); + if (! this.failureNotice.hasClass('fading-out')) { + this.failureNotice.remove(); + } this.failureNotice = null; } - if (this.exception !== null) { - this.exception.remove(); - this.exception = null; - } - - // Remove 'impact' class if there was such - if (req.$target.hasClass('impact')) { - req.$target.removeClass('impact'); - } - var url = req.url; this.icinga.logger.debug( 'Got response for ', req.$target, ', URL was ' + url @@ -346,42 +430,11 @@ var rendered = false; var classes; - if (! req.autorefresh) { - // TODO: Hook for response/url? - var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]'); - var $matches = $.merge($('[href="' + url + '"]'), $forms); - $matches.each(function (idx, el) { - if ($(el).closest('#menu').length) { - if (req.$target[0].id === 'col1') { - self.icinga.behaviors.navigation.resetActive(); - } - } else if ($(el).closest('table.action').length) { - $(el).closest('table.action').find('.active').removeClass('active'); - } - }); - - $matches.each(function (idx, el) { - var $el = $(el); - if ($el.closest('#menu').length) { - if ($el.is('form')) { - $('input', $el).addClass('active'); - } else { - if (req.$target[0].id === 'col1') { - self.icinga.behaviors.navigation.setActive($el); - } - } - // Interrupt .each, only on menu item shall be active - return false; - } else if ($(el).closest('table.action').length) { - $el.addClass('active'); - } - }); - } else { + if (req.autorefresh) { // TODO: next container url active = $('[href].active', req.$target).attr('href'); } - var target = req.getResponseHeader('X-Icinga-Container'); var newBody = false; var oldNotifications = false; @@ -507,57 +560,55 @@ oldNotifications.appendTo($('#notifications')); } if (url.match(/#/)) { - this.icinga.ui.scrollContainerToAnchor(req.$target, url.split(/#/)[1]); + this.icinga.ui.focusElement(url.split(/#/)[1], req.$target); } if (newBody) { this.icinga.ui.fixDebugVisibility().triggerWindowResize(); } self.cacheLoadedIcons(req.$target); - - if (active) { - var focusedUrl = this.icinga.ui.getFocusedContainerDataUrl(); - var oldSelectionData = this.icinga.ui.loadSelectionData(); - if (typeof oldSelectionData === 'string') { - $('[href="' + oldSelectionData + '"]', req.$target).addClass('active'); - - } else if (oldSelectionData !== null) { - var $container; - if (!focusedUrl) { - $container = $('document').first(); - } else { - $container = $('.container[data-icinga-url="' + focusedUrl + '"]'); - } - - var $table = $container.find('table.action').first(); - var keys = self.icinga.ui.getSelectionKeys($table); - - // build map of selected queries - var oldSelectionQueries = {}; - $.each(oldSelectionData, function(i, query){ - oldSelectionQueries[self.icinga.ui.selectionDataToQueryComp(query)] = true; - }); - - // set all new selections to active - $table.find('tr[href]').filter(function(){ - var $tr = $(this); - var rowData = self.icinga.ui.getSelectionData($tr, keys, self.icinga); - var newSelectionQuery = self.icinga.ui.selectionDataToQueryComp(rowData); - if (oldSelectionQueries[newSelectionQuery]) { - return true; - } - return false; - }).addClass('active'); - } - } }, /** * Regardless of whether a request succeeded of failed, clean up */ onComplete: function (req, textStatus) { + // Remove 'impact' class if there was such + if (req.$target.hasClass('impact')) { + req.$target.removeClass('impact'); + } + + if (! req.autorefresh) { + // TODO: Hook for response/url? + var url = req.url; + var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]'); + var $matches = $.merge($('[href="' + url + '"]'), $forms); + $matches.each(function (idx, el) { + if ($(el).closest('#menu').length) { + if (req.$target[0].id === 'col1') { + self.icinga.behaviors.navigation.resetActive(); + } + } + }); + + $matches.each(function (idx, el) { + var $el = $(el); + if ($el.closest('#menu').length) { + if ($el.is('form')) { + $('input', $el).addClass('active'); + } else { + if (req.$target[0].id === 'col1') { + self.icinga.behaviors.navigation.setActive($el); + } + } + // Interrupt .each, only on menu item shall be active + return false; + } + }); + } + // Update history when necessary. Don't do so for requests triggered // by history or autorefresh events - if (! req.historyTriggered && ! req.autorefresh) { + if (! req.autorefresh && req.addToHistory) { if (req.$target.hasClass('container')) { // We only want to care about top-level containers if (req.$target.parent().closest('.container').length === 0) { @@ -594,22 +645,20 @@ onFailure: function (req, textStatus, errorThrown) { var url = req.url; - if (req.status === 500) { - if (this.exception === null) { - req.$target.addClass('impact'); + /* + * Test if a manual actions comes in and autorefresh is active: Stop refreshing + */ + if (req.addToHistory && ! req.autorefresh && req.$target.data('icingaRefresh') > 0 + && req.$target.data('icingaUrl') !== url) { + req.$target.data('icingaRefresh', 0); + req.$target.data('icingaUrl', url); + } - this.exception = this.createNotice( - 'error', - $('h1', $(req.responseText)).first().html(), - true - ); - this.icinga.ui.fixControls(); - } - } else if (req.status > 0) { + if (req.status > 0) { this.icinga.logger.error( req.status, errorThrown + ':', - $(req.responseText).text().slice(0, 100) + $(req.responseText).text().replace(/\s+/g, ' ').slice(0, 100) ); this.renderContentToContainer( req.responseText, @@ -617,15 +666,15 @@ req.action, req.autorefresh ); - - // Header example: - // Icinga.debug(req.getResponseHeader('X-Icinga-Redirect')); } else { if (errorThrown === 'abort') { this.icinga.logger.debug( 'Request to ' + url + ' has been aborted for ', req.$target ); + + // Aborted requests should not be added to browser history + req.addToHistory = false; } else { if (this.failureNotice === null) { this.failureNotice = this.createNotice( @@ -660,7 +709,13 @@ var $notice = $( '
  • ' + message + '
  • ' ).appendTo($('#notifications')); + this.icinga.ui.fixControls(); + + if (!persist) { + this.icinga.ui.fadeNotificationsAway(); + } + return $notice; }, @@ -671,7 +726,6 @@ // Container update happens here var scrollPos = false; var self = this; - var origFocus = document.activeElement; var containerId = $container.attr('id'); if (typeof containerId !== 'undefined') { if (autorefresh) { @@ -680,6 +734,9 @@ scrollPos = 0; } } + if (autorefresh && $.contains($container[0], document.activeElement)) { + var origFocus = self.icinga.utils.getDomPath(document.activeElement); + } $container.trigger('beforerender'); @@ -721,11 +778,8 @@ // $container.html(content); } else { - if ($container.closest('.dashboard').length && - ! $('h1', $content).length - ) { + if ($container.closest('.dashboard').length) { var title = $('h1', $container).first().detach(); - $('h1', $content).first().detach(); $container.html(title).append(content); } else if (action === 'replace') { $container.html(content); @@ -735,8 +789,10 @@ } this.icinga.ui.assignUniqueContainerIds(); - if (origFocus) { - $(origFocus).focus(); + if (origFocus && origFocus.length > 0 && origFocus[0] !== '') { + setTimeout(function() { + $(self.icinga.utils.getElementByDomPath(origFocus)).focus(); + }, 0); } // TODO: this.icinga.events.refreshContainer(container); diff --git a/public/js/icinga/logger.js b/public/js/icinga/logger.js index 07135e007..1898bf2a4 100644 --- a/public/js/icinga/logger.js +++ b/public/js/icinga/logger.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * Icinga.Logger diff --git a/public/js/icinga/module.js b/public/js/icinga/module.js index 047911434..650f8c4cc 100644 --- a/public/js/icinga/module.js +++ b/public/js/icinga/module.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * This is how we bootstrap JS code in our modules diff --git a/public/js/icinga/timer.js b/public/js/icinga/timer.js index 8d90f7c5c..baa8a7e7b 100644 --- a/public/js/icinga/timer.js +++ b/public/js/icinga/timer.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * Icinga.Timer diff --git a/public/js/icinga/timezone.js b/public/js/icinga/timezone.js index 15205e9cb..349867eb6 100644 --- a/public/js/icinga/timezone.js +++ b/public/js/icinga/timezone.js @@ -1,3 +1,5 @@ +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + (function(Icinga, $) { 'use strict'; @@ -114,4 +116,4 @@ } }; -})(Icinga, jQuery); \ No newline at end of file +})(Icinga, jQuery); diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 3c03bdcf5..5d65e1277 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * Icinga.UI @@ -10,13 +9,6 @@ 'use strict'; - // Stores the icinga-data-url of the last focused table. - var focusedTableDataUrl = null; - - // The stored selection data, useful for preserving selections over - // multiple reload-cycles. - var selectionData = null; - Icinga.UI = function (icinga) { this.icinga = icinga; @@ -39,8 +31,7 @@ this.fadeNotificationsAway(); }, - fadeNotificationsAway: function() - { + fadeNotificationsAway: function() { var icinga = this.icinga; $('#notifications li') .not('.fading-out') @@ -131,6 +122,33 @@ return this; }, + /** + * Focus the given element and scroll to its position + * + * @param {string} element The name or id of the element to focus + * @param {object} $container The container containing the element + */ + focusElement: function(element, $container) { + var $element = $('#' + element, $container); + + if (! $element.length) { + // The name attribute is actually deprecated, on anchor tags, + // but we'll possibly handle links from another source + // (module etc) so that's used as a fallback + $element = $('[name="' + element.replace(/'/, '\\\'') + '"]', $container); + } + + if ($element.length) { + if (typeof $element.attr('tabindex') === 'undefined') { + $element.attr('tabindex', -1); + } + + $element.focus(); + $container.scrollTop(0); + $container.scrollTop($element.first().position().top); + } + }, + moveToLeft: function () { var col2 = this.cutContainer($('#col2')); var kill = this.cutContainer($('#col1')); @@ -141,7 +159,7 @@ cutContainer: function ($col) { var props = { - 'elements': $('#' + $col.attr('id') + ' > div').detach(), + 'elements': $('#' + $col.attr('id') + ' > *').detach(), 'data': { 'data-icinga-url': $col.data('icingaUrl'), 'data-icinga-refresh': $col.data('icingaRefresh'), @@ -168,18 +186,6 @@ $col.data('icingaModule', backup['data']['data-icinga-module']); }, - scrollContainerToAnchor: function ($container, anchorName) { - // TODO: Generic issue -> we probably should escape attribute value selectors!? - var $anchor = $("a[name='" + anchorName.replace(/'/, '\\\'') + "']", $container); - if ($anchor.length) { - $container.scrollTop(0); - $container.scrollTop($anchor.first().position().top); - this.icinga.logger.debug('Scrolling ', $container, ' to ', anchorName); - } else { - this.icinga.logger.info('Anchor "' + anchorName + '" not found in ', $container); - } - }, - triggerWindowResize: function () { this.onWindowResize({data: {self: this}}); }, @@ -250,6 +256,9 @@ $('#layout').removeClass('twocols'); this.closeContainer($('#col2')); this.disableCloseButtons(); + + // one-column layouts never have any selection active + this.icinga.behaviors.actiontable.clearAll(); }, closeContainer: function($c) { @@ -299,226 +308,6 @@ return $('#main > .container').length; }, - /** - * Add the given table-row to the selection of the closest - * table and deselect all other rows of the closest table. - * - * @param $tr {jQuery} The selected table row. - * @returns {boolean} If the selection was changed. - */ - setTableRowSelection: function ($tr) { - var $table = $tr.closest('table.multiselect'); - $table.find('tr[href].active').removeClass('active'); - $tr.addClass('active'); - return true; - }, - - /** - * Toggle the given table row to "on" when not selected, or to "off" when - * currently selected. - * - * @param $tr {jQuery} The table row. - * @returns {boolean} If the selection was changed. - */ - toogleTableRowSelection: function ($tr) { - // multi selection - if ($tr.hasClass('active')) { - $tr.removeClass('active'); - } else { - $tr.addClass('active'); - } - return true; - }, - - /** - * Add a new selection range to the closest table, using the selected row as - * range target. - * - * @param $tr {jQuery} The target of the selected range. - * @returns {boolean} If the selection was changed. - */ - addTableRowRangeSelection: function ($tr) { - var $table = $tr.closest('table.multiselect'); - var $rows = $table.find('tr[href]'), - from, to; - var selected = $tr.first().get(0); - $rows.each(function(i, el) { - if ($(el).hasClass('active') || el === selected) { - if (!from) { - from = el; - } - to = el; - } - }); - var inRange = false; - $rows.each(function(i, el){ - if (el === from) { - inRange = true; - } - if (inRange) { - $(el).addClass('active'); - } - if (el === to) { - inRange = false; - } - }); - return false; - }, - - - /** - * Read the data from a whole set of selections. - * - * @param $selections {jQuery} All selected rows in a jQuery-selector. - * @param keys {Array} An array containing all valid keys. - * @returns {Array} An array containing an object with the data for each selection. - */ - getSelectionSetData: function($selections, keys) { - var selections = []; - var icinga = this.icinga; - - // read all current selections - $selections.each(function(ind, selected) { - selections.push(icinga.ui.getSelectionData($(selected), keys, icinga)); - }); - return selections; - }, - - getSelectionKeys: function($selection) - { - var d = $selection.data('icinga-multiselect-data') && $selection.data('icinga-multiselect-data').split(','); - return d || []; - }, - - /** - * Read the data from the given selected object. - * - * @param $selection {jQuery} The selected object. - * @param keys {Array} An array containing all valid keys. - * @param icinga {Icinga} The main icinga object. - * @returns {Object} An object containing all key-value pairs associated with this selection. - */ - getSelectionData: function($selection, keys, icinga) - { - var url = $selection.attr('href'); - var params = this.icinga.utils.parseUrl(url).params; - var tuple = {}; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (params[key]) { - tuple[key] = params[key]; - } - } - return tuple; - }, - - /** - * Convert a set of selection data to a single query. - * - * @param selectionData {Array} The selection data generated from getSelectionData - * @returns {String} The formatted and uri-encoded query-string. - */ - selectionDataToQuery: function (selectionData) { - var queries = []; - - // create new url - if (selectionData.length < 2) { - this.icinga.logger.error('Something went wrong, we should never multiselect just one row'); - } else { - $.each(selectionData, function(i, el){ - var parts = [] - $.each(el, function(key, value) { - parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); - }); - queries.push('(' + parts.join('&') + ')'); - }); - } - return '(' + queries.join('|') + ')'; - }, - - /** - * Create a single query-argument (not compatible to selectionDataToQuery) - * - * @param data - * @returns {string} - */ - selectionDataToQueryComp: function(data) { - var queries = []; - $.each(data, function(key, value){ - queries.push(key + '=' + encodeURIComponent(value)); - }); - return queries.join('&'); - }, - - /** - * Store a set of selection-data to preserve it accross page-reloads - * - * @param data {Array|String|Null} The selection-data be an Array of Objects, - * containing the selection data (when multiple rows where selected), a - * String containing a single url (when only a single row was selected) or - * Null when nothing was selected. - */ - storeSelectionData: function(data) { - selectionData = data; - }, - - /** - * Load the last stored set of selection-data - * - * @returns {Array|String|Null} May be an Array of Objects, containing the selection data - * (when multiple rows where selected), a String containing a single url - * (when only a single row was selected) or Null when nothing was selected. - */ - loadSelectionData: function() { - this.provideSelectionCount(); - return selectionData; - }, - - /** - * Set the selections row count hint info - */ - provideSelectionCount: function() { - var $count = $('.selection-info-count'); - - if (typeof selectionData === 'undefined' || selectionData === null) { - $count.text(0); - return; - } - - if (typeof selectionData === 'string') { - $count.text(1); - } else if (selectionData.length > 1) { - $count.text(selectionData.length); - } else { - $count.text(0); - } - }, - - /** - * Focus the given table by deselecting all selections on all other tables. - * - * Focusing a table is important for environments with multiple tables like - * the dashboard. It should only be possible to select rows at one table at a time, - * when a user selects a row on a table all rows that are not child of the given table - * will be removed from the selection. - * - * @param table {htmlElement} The table to focus. - */ - focusTable: function (table) { - $('table').filter(function(){ return this !== table; }).find('tr[href]').removeClass('active'); - var n = $(table).closest('div.container').attr('data-icinga-url'); - focusedTableDataUrl = n; - }, - - /** - * Return the URL of the last focused table container. - * - * @returns {String} The data-icinga-url of the last focused table, which should be unique in each site. - */ - getFocusedContainerDataUrl: function() { - return focusedTableDataUrl; - }, - /** * Assign a unique ID to each .container without such * @@ -578,70 +367,60 @@ ); }, + /** + * Refresh partial time counters + * + * This function runs every second. + */ refreshTimeSince: function () { - - $('.timesince').each(function (idx, el) { - - // todo remove after replace timeSince - var mp = el.innerHTML.match(/^(.*?)(-?\d+)d\s(-?\d+)h/); - if (mp !== null) { - return true; - } - - var m = el.innerHTML.match(/^(.*?)(-?\d+)(.+\s)(-?\d+)(.+)/); - if (m !== null) { - var nm = parseInt(m[2]); - var ns = parseInt(m[4]); - if (ns < 59) { - ns++; + $('.time-ago, .time-since').each(function (idx, el) { + var partialTime = /(\d{1,2})m (\d{1,2})s/.exec(el.innerHTML); + if (partialTime !== null) { + var minute = parseInt(partialTime[1], 10), + second = parseInt(partialTime[2], 10); + if (second < 59) { + ++second; } else { - ns = 0; - nm++; + ++minute; + second = 0; } - $(el).html(m[1] + nm + m[3] + ns + m[5]); + el.innerHTML = el.innerHTML.substr(0, partialTime.index) + minute.toString() + 'm ' + + second.toString() + 's' + el.innerHTML.substr(partialTime.index + partialTime[0].length); } }); - $('.timeuntil').each(function (idx, el) { - - // todo remove after replace timeUntil - var mp = el.innerHTML.match(/^(.*?)(-?\d+)d\s(-?\d+)h/); - if (mp !== null) { - return true; - } - - var m = el.innerHTML.match(/^(.*?)(-?\d+)(.+\s)(-?\d+)(.+)/); - if (m !== null) { - var nm = parseInt(m[2]); - var ns = parseInt(m[4]); - var signed = ''; - var sec = 0; - - if (nm < 0) { - signed = '-'; - nm = nm * -1; - sec = nm * 60 + ns; - sec++; - } else if (nm == 0 && ns == 0) { - signed = '-'; - sec = 1; - } else if (nm == 0 && m[2][0] == '-') { - signed = '-'; - sec = ns; - sec++; - } else if (nm == 0 && m[2][0] != '-') { - sec = ns; - sec--; + $('.time-until').each(function (idx, el) { + var partialTime = /(-?)(\d{1,2})m (\d{1,2})s/.exec(el.innerHTML); + if (partialTime !== null) { + var minute = parseInt(partialTime[2], 10), + second = parseInt(partialTime[3], 10), + invert = partialTime[1]; + if (invert.length) { + // Count up because partial time is negative + if (second < 59) { + ++second; + } else { + ++minute; + second = 0; + } } else { - signed = ''; - sec = nm * 60 + ns; - sec--; - } - - nm = Math.floor(sec/60); - ns = sec - nm * 60; - - $(el).html(m[1] + signed + nm + m[3] + ns + m[5]); + // Count down because partial time is positive + if (second === 0) { + if (minute === 0) { + // Invert counter + minute = 0; + second = 1; + invert = '-'; + } else { + --minute; + second = 59; + } + } else { + --second; + } + } + el.innerHTML = el.innerHTML.substr(0, partialTime.index) + invert + minute.toString() + 'm ' + + second.toString() + 's' + el.innerHTML.substr(partialTime.index + partialTime[0].length); } }); }, @@ -671,24 +450,24 @@ // hide input boxess and remove text nodes $target.find("input").hide(); $target.contents().filter(function() { return this.nodeType === 3; }).remove(); - + // has three states? var triState = $target.find('input[value="unchanged"]').size() > 0 ? 1 : 0; - + // fetch current value from radiobuttons var value = $target.find('input:checked').first().val(); - + $target.append( ' ' - ); + ); if (triState) { // TODO: find a better way to activate indeterminate checkboxes after load. $target.append( '' ); } @@ -717,8 +496,9 @@ }, initializeControls: function (parent) { - - var self = this; + if ($(parent).closest('.dashboard').length || $('#layout').hasClass('fullscreen-layout')) { + return; + } $('.controls', parent).each(function (idx, el) { var $el = $(el); @@ -745,16 +525,20 @@ }, fixControls: function ($parent) { - var self = this; + if ($('#layout').hasClass('fullscreen-layout')) { + return; + } if ('undefined' === typeof $parent) { - $('#header').css({height: 'auto'}); - $('#main').css({top: $('#header').css('height')}); - $('#sidebar').css({top: $('#header').height() + 'px'}); - $('#header').css({height: $('#header').height() + 'px'}); - $('#inner-layout').css({top: $('#header').css('height')}); + if (! $('#layout').hasClass('fullscreen-layout')) { + $('#header').css({height: 'auto'}); + $('#main').css({top: $('#header').css('height')}); + $('#sidebar').css({top: $('#header').height() + 'px'}); + $('#header').css({height: $('#header').height() + 'px'}); + $('#inner-layout').css({top: $('#header').css('height')}); + } $('.container').each(function (idx, container) { self.fixControls($(container)); }); @@ -762,11 +546,20 @@ return; } + if ($parent.closest('.dashboard').length) { + return; + } + // Enable this only in case you want to track down UI problems // self.icinga.logger.debug('Fixing controls for ', $parent); $('.controls', $parent).each(function (idx, el) { var $el = $(el); + + if ($el.closest('.dashboard').length) { + return; + } + var $fake = $el.next('.fake-controls'); var y = $parent.scrollTop(); diff --git a/public/js/icinga/utils.js b/public/js/icinga/utils.js index ba4d7a040..659be9ea9 100644 --- a/public/js/icinga/utils.js +++ b/public/js/icinga/utils.js @@ -1,5 +1,4 @@ -// {{{ICINGA_LICENSE_HEADER}}} -// {{{ICINGA_LICENSE_HEADER}}} +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /** * Icinga utility functions @@ -234,12 +233,93 @@ return !(at > (bt + bh) || bt > (at + ah)) && !(bl > (al + aw) || al > (bl + bw)); }, + /** + * Create a selector that can be used to fetch the element the same position in the DOM-Tree + * + * Create the path to the given element in the DOM-Tree, comparable to an X-Path. Climb the + * DOM tree upwards until an element with an unique ID is found, this id is used as the anchor, + * all other elements will be addressed by their position in the parent. + * + * @param {HTMLElement} el The element to extract the path for. + * + * @returns {Array} The path of the element, that can be passed to getElementByPath + */ + getDomPath: function (el) { + if (! el) { + return []; + } + if (el.id !== '') { + return ['#' + el.id]; + } + if (el === document.body) { + return ['body']; + } + + var siblings = el.parentNode.childNodes; + var index = 0; + for (var i = 0; i < siblings.length; i ++) { + if (siblings[i].nodeType === 1) { + index ++; + } + + if (siblings[i] === el) { + var p = this.getDomPath(el.parentNode); + p.push(':nth-child(' + (index) + ')'); + return p; + } + } + }, + + /** + * Climbs up the given dom path and returns the element + * + * This is the counterpart + * + * @param path {Array} The selector + * @returns {HTMLElement} The corresponding element + */ + getElementByDomPath: function (path) { + var $element; + $.each(path, function (i, selector) { + if (! $element) { + $element = $(selector); + } else { + $element = $element.children(selector).first(); + if (! $element[0]) { + return false; + } + } + }); + return $element[0]; + }, + + objectKeys: Object.keys || function (obj) { + var keys = []; + $.each(obj, function (key) { + keys.push(key); + }); + return keys; + }, + /** * Cleanup */ destroy: function () { this.urlHelper = null; this.icinga = null; + }, + + /** + * Encode the parenthesis too + * + * @param str {String} A component of a URI + * + * @returns {String} Encoded component + */ + fixedEncodeURIComponent: function (str) { + return encodeURIComponent(str).replace(/[()]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); } }; diff --git a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php deleted file mode 100644 index 6af628b06..000000000 --- a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php +++ /dev/null @@ -1,76 +0,0 @@ -setUpResourceFactoryMock(); - Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend') - ->shouldReceive('count') - ->andReturn(2); - - $form = new DbBackendForm(); - $form->setTokenDisabled(); - $form->setResources(array('test_db_backend')); - $form->populate(array('resource' => 'test_db_backend')); - - $this->assertTrue( - DbBackendForm::isValidAuthenticationBackend($form), - 'DbBackendForm claims that a valid authentication backend with users is not valid' - ); - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testInvalidBackendIsNotValid() - { - $this->setUpResourceFactoryMock(); - Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend') - ->shouldReceive('count') - ->andReturn(0); - - $form = new DbBackendForm(); - $form->setTokenDisabled(); - $form->setResources(array('test_db_backend')); - $form->populate(array('resource' => 'test_db_backend')); - - $this->assertFalse( - DbBackendForm::isValidAuthenticationBackend($form), - 'DbBackendForm claims that an invalid authentication backend without users is valid' - ); - } - - protected function setUpResourceFactoryMock() - { - Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('createResource') - ->andReturn(Mockery::mock('Icinga\Data\Db\DbConnection')) - ->shouldReceive('getResourceConfig') - ->andReturn(new ConfigObject()); - } -} diff --git a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php deleted file mode 100644 index 046730f57..000000000 --- a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php +++ /dev/null @@ -1,75 +0,0 @@ -setUpResourceFactoryMock(); - Mockery::mock('overload:Icinga\Authentication\Backend\LdapUserBackend') - ->shouldReceive('assertAuthenticationPossible')->andReturnNull(); - - $form = new LdapBackendForm(); - $form->setTokenDisabled(); - $form->setResources(array('test_ldap_backend')); - $form->populate(array('resource' => 'test_ldap_backend')); - - $this->assertTrue( - LdapBackendForm::isValidAuthenticationBackend($form), - 'LdapBackendForm claims that a valid authentication backend with users is not valid' - ); - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testInvalidBackendIsNotValid() - { - $this->setUpResourceFactoryMock(); - Mockery::mock('overload:Icinga\Authentication\Backend\LdapUserBackend') - ->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException); - - $form = new LdapBackendForm(); - $form->setTokenDisabled(); - $form->setResources(array('test_ldap_backend')); - $form->populate(array('resource' => 'test_ldap_backend')); - - $this->assertFalse( - LdapBackendForm::isValidAuthenticationBackend($form), - 'LdapBackendForm claims that an invalid authentication backend without users is valid' - ); - } - - protected function setUpResourceFactoryMock() - { - Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('createResource') - ->andReturn(Mockery::mock('Icinga\Protocol\Ldap\Connection')) - ->shouldReceive('getResourceConfig') - ->andReturn(new ConfigObject()); - } -} diff --git a/test/php/application/forms/Config/Resource/DbResourceFormTest.php b/test/php/application/forms/Config/Resource/DbResourceFormTest.php deleted file mode 100644 index 53dd6bf56..000000000 --- a/test/php/application/forms/Config/Resource/DbResourceFormTest.php +++ /dev/null @@ -1,62 +0,0 @@ -setUpResourceFactoryMock( - Mockery::mock()->shouldReceive('getConnection')->atMost()->twice()->andReturn(Mockery::self())->getMock() - ); - - $this->assertTrue( - DbResourceForm::isValidResource(new DbResourceForm()), - 'ResourceForm claims that a valid db resource is not valid' - ); - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testInvalidDbResourceIsNotValid() - { - $this->setUpResourceFactoryMock( - Mockery::mock()->shouldReceive('getConnection')->once()->andThrow('\Exception')->getMock() - ); - - $this->assertFalse( - DbResourceForm::isValidResource(new DbResourceForm()), - 'ResourceForm claims that an invalid db resource is valid' - ); - } - - protected function setUpResourceFactoryMock($resourceMock) - { - Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('createResource') - ->with(Mockery::type('Icinga\Data\ConfigObject')) - ->andReturn($resourceMock); - } -} diff --git a/test/php/application/forms/Config/Resource/LdapResourceFormTest.php b/test/php/application/forms/Config/Resource/LdapResourceFormTest.php deleted file mode 100644 index 9ef8880e7..000000000 --- a/test/php/application/forms/Config/Resource/LdapResourceFormTest.php +++ /dev/null @@ -1,68 +0,0 @@ -setUpResourceFactoryMock( - Mockery::mock()->shouldReceive('testCredentials')->once()->andReturn(true)->getMock() - ); - - $form = new LdapResourceForm(); - $form->setTokenDisabled(); - - $this->assertTrue( - LdapResourceForm::isValidResource($form->create()), - 'ResourceForm claims that a valid ldap resource is not valid' - ); - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testInvalidLdapResourceIsNotValid() - { - $this->setUpResourceFactoryMock( - Mockery::mock()->shouldReceive('testCredentials')->once()->andThrow('\Exception')->getMock() - ); - - $form = new LdapResourceForm(); - $form->setTokenDisabled(); - - $this->assertFalse( - LdapResourceForm::isValidResource($form->create()), - 'ResourceForm claims that an invalid ldap resource is valid' - ); - } - - protected function setUpResourceFactoryMock($resourceMock) - { - Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('createResource') - ->with(Mockery::type('Icinga\Data\ConfigObject')) - ->andReturn($resourceMock); - } -} diff --git a/test/php/application/forms/Config/Resource/LivestatusResourceFormTest.php b/test/php/application/forms/Config/Resource/LivestatusResourceFormTest.php deleted file mode 100644 index 3f5de5844..000000000 --- a/test/php/application/forms/Config/Resource/LivestatusResourceFormTest.php +++ /dev/null @@ -1,63 +0,0 @@ -setUpResourceFactoryMock( - Mockery::mock()->shouldReceive('connect')->andReturn(Mockery::self()) - ->shouldReceive('disconnect')->getMock() - ); - - $this->assertTrue( - LivestatusResourceForm::isValidResource(new LivestatusResourceForm()), - 'ResourceForm claims that a valid livestatus resource is not valid' - ); - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testInvalidLivestatusResourceIsNotValid() - { - $this->setUpResourceFactoryMock( - Mockery::mock()->shouldReceive('connect')->once()->andThrow('\Exception')->getMock() - ); - - $this->assertFalse( - LivestatusResourceForm::isValidResource(new LivestatusResourceForm()), - 'ResourceForm claims that an invalid livestatus resource is valid' - ); - } - - protected function setUpResourceFactoryMock($resourceMock) - { - Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('createResource') - ->with(Mockery::type('Icinga\Data\ConfigObject')) - ->andReturn($resourceMock); - } -} diff --git a/test/php/application/forms/Config/AuthenticationBackendReorderFormTest.php b/test/php/application/forms/Config/UserBackendReorderFormTest.php similarity index 61% rename from test/php/application/forms/Config/AuthenticationBackendReorderFormTest.php rename to test/php/application/forms/Config/UserBackendReorderFormTest.php index 88de869a1..240d578be 100644 --- a/test/php/application/forms/Config/AuthenticationBackendReorderFormTest.php +++ b/test/php/application/forms/Config/UserBackendReorderFormTest.php @@ -1,15 +1,14 @@ setIniConfig($this->config); return $form; } @@ -46,7 +45,7 @@ class AuthenticationBackendReorderFormTest extends BaseTestCase ->shouldReceive('isPost')->andReturn(true) ->shouldReceive('getPost')->andReturn(array('backend_newpos' => 'test3|1')); - $form = new AuthenticationBackendReorderFormProvidingConfigFormWithoutSave(); + $form = new UserBackendReorderFormProvidingConfigFormWithoutSave(); $form->setIniConfig($config); $form->setTokenDisabled(); $form->setUidDisabled(); @@ -54,8 +53,8 @@ class AuthenticationBackendReorderFormTest extends BaseTestCase $this->assertEquals( array('test1', 'test3', 'test2'), - AuthenticationBackendConfigFormWithoutSave::$newConfig->keys(), - 'Moving elements with AuthenticationBackendReorderForm does not seem to properly work' + UserBackendConfigFormWithoutSave::$newConfig->keys(), + 'Moving elements with UserBackendReorderForm does not seem to properly work' ); } } diff --git a/test/php/application/views/helpers/DateFormatTestBroken.php b/test/php/application/views/helpers/DateFormatTestBroken.php index 4f3cfc184..75ee10286 100644 --- a/test/php/application/views/helpers/DateFormatTestBroken.php +++ b/test/php/application/views/helpers/DateFormatTestBroken.php @@ -1,6 +1,5 @@ register(); +set_include_path( + implode( + PATH_SEPARATOR, + array($libraryPath . '/vendor', get_include_path()) + ) +); + require_once 'Zend/Loader/Autoloader.php'; \Zend_Loader_Autoloader::getInstance(); diff --git a/test/php/library/Icinga/Application/ConfigTest.php b/test/php/library/Icinga/Application/ConfigTest.php index 1e64da808..06249bb1a 100644 --- a/test/php/library/Icinga/Application/ConfigTest.php +++ b/test/php/library/Icinga/Application/ConfigTest.php @@ -1,6 +1,5 @@ query('//x:path[@data-icinga-graph-type="pieslice"]'); $this->assertEquals(3, $path->length, 'Assert the correct number of datapoints being drawn as SVG bars'); } -} \ No newline at end of file +} diff --git a/test/php/library/Icinga/Data/ConfigObjectTest.php b/test/php/library/Icinga/Data/ConfigObjectTest.php index c4b825e58..820d17b83 100644 --- a/test/php/library/Icinga/Data/ConfigObjectTest.php +++ b/test/php/library/Icinga/Data/ConfigObjectTest.php @@ -1,6 +1,5 @@ 'b', 'c' => array('d' => 'e'))); - - $this->assertInstanceOf('Countable', $config, 'ConfigObject objects do not implement interface `Countable\''); - $this->assertEquals(2, count($config), 'ConfigObject objects do not count properties and sections correctly'); - } - public function testWhetherConfigObjectsAreTraversable() { $config = new ConfigObject(array('a' => 'b', 'c' => 'd')); @@ -125,7 +116,7 @@ class ConfigObjectTest extends BaseTestCase } /** - * @expectedException LogicException + * @expectedException \Icinga\Exception\ProgrammingError */ public function testWhetherItIsNotPossibleToAppendProperties() { @@ -143,9 +134,6 @@ class ConfigObjectTest extends BaseTestCase $this->assertFalse(isset($config->c), 'ConfigObjects do not allow to unset sections'); } - /** - * @depends testWhetherConfigObjectsAreCountable - */ public function testWhetherOneCanCheckIfAConfigObjectHasAnyPropertiesOrSections() { $config = new ConfigObject(); diff --git a/test/php/library/Icinga/Data/DataArray/ArrayDatasourceTest.php b/test/php/library/Icinga/Data/DataArray/ArrayDatasourceTest.php index bdb719f8c..e44df672c 100644 --- a/test/php/library/Icinga/Data/DataArray/ArrayDatasourceTest.php +++ b/test/php/library/Icinga/Data/DataArray/ArrayDatasourceTest.php @@ -1,6 +1,5 @@ assertNotEquals((string) $c, (string) $d); } - public function testLeadingAndTrailingWhitespacesSanitizing() + public function testLeadingAndTrailingWhitespaces() { - $columnHasWhitespaces = Filter::where(' host ', 'localhost'); - $expressionHasWhitespaces = Filter::where('host', ' localhost '); - $bothHaveWhitespaces = Filter::fromQueryString(' host = localhost '); - $withArray = Filter::where(' host ', array(' no match ', ' localhost ')); - $this->assertTrue($columnHasWhitespaces->matches($this->sampleData[0])); - $this->assertTrue($expressionHasWhitespaces->matches($this->sampleData[0])); - $this->assertTrue($bothHaveWhitespaces->matches($this->sampleData[0])); - $this->assertTrue($withArray->matches($this->sampleData[0])); + $columnWithWhitespaces = Filter::where(' host ', 'localhost'); + $this->assertTrue($columnWithWhitespaces->matches((object) array( + 'host' => 'localhost' + )), + 'Filter doesn\'t remove leading and trailing whitespaces from columns' + ); + $expressionWithLeadingWhitespaces = Filter::where('host', ' localhost'); + $this->assertTrue($expressionWithLeadingWhitespaces->matches((object) array( + 'host' => ' localhost' + )), + 'Filter doesn\'t take leading whitespaces of expressions into account' + ); + $this->assertFalse($expressionWithLeadingWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take trailing whitespaces of expressions into account' + ); + $expressionWithTrailingWhitespaces = Filter::where('host', 'localhost '); + $this->assertTrue($expressionWithTrailingWhitespaces->matches((object) array( + 'host' => 'localhost ' + )), + 'Filter doesn\'t take trailing whitespaces of expressions into account' + ); + $this->assertFalse($expressionWithTrailingWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take leading whitespaces of expressions into account' + ); + $expressionWithLeadingAndTrailingWhitespaces = Filter::where('host', ' localhost '); + $this->assertTrue($expressionWithLeadingAndTrailingWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take leading and trailing whitespaces of expressions into account' + ); + $this->assertFalse($expressionWithLeadingAndTrailingWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take leading and trailing whitespaces of expressions into account' + ); + $queryStringWithWhitespaces = Filter::fromQueryString(' host = localhost '); + $this->assertTrue($queryStringWithWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take leading and trailing whitespaces of expressions in query strings into account' + ); } private function row($idx) diff --git a/test/php/library/Icinga/File/CsvTest.php b/test/php/library/Icinga/File/CsvTest.php index f4ae27c63..3ee64b2b5 100644 --- a/test/php/library/Icinga/File/CsvTest.php +++ b/test/php/library/Icinga/File/CsvTest.php @@ -1,6 +1,5 @@ tempFile = tempnam(sys_get_temp_dir(), 'icinga-ini-parser-test'); + } + + public function tearDown() + { + parent::tearDown(); + unlink($this->tempFile); + } + + public function testSectionNameEscaping() + { + $config = <<<'EOD' +[title with \]bracket] +key1 = "1" +key2 = "2" + +[title with \"quote] +key1 = "1" +key2 = "2" +EOD; + $doc = IniParser::parseIni($config); + $this->assertTrue( + $doc->hasSection('title with ]bracket'), + 'IniParser does not recognize escaped bracket in section' + ); + $this->assertTrue( + $doc->hasSection('title with "quote'), + 'IniParser does not recognize escaped quote in section' + ); + } + + public function testDirectiveValueEscaping() + { + $config = <<<'EOD' +[section] +key1 = "key with escaped \"quote" +EOD; + $doc = IniParser::parseIni($config); + $this->assertEquals( + 'key with escaped "quote', + $doc->getSection('section')->getDirective('key1')->getValue(), + 'IniParser does not recognize escaped bracket in section' + ); + } + + public function testMultilineValues() + { + $config = <<<'EOD' +[section] +key1 = "with some +newline in the value" +EOD; + $doc = IniParser::parseIni($config); + $this->assertEquals( + 2, + count(explode("\n", $doc->getSection('section')->getDirective('key1')->getValue())), + 'IniParser does not recognize multi-line values' + ); + } +} diff --git a/test/php/library/Icinga/File/Ini/IniWriterTest.php b/test/php/library/Icinga/File/Ini/IniWriterTest.php index 63cb0e517..a78e96229 100644 --- a/test/php/library/Icinga/File/Ini/IniWriterTest.php +++ b/test/php/library/Icinga/File/Ini/IniWriterTest.php @@ -1,6 +1,5 @@ Config::fromArray( - array( - 'section' => array( - 'foo.bar' => 1337 - ), - 'section.with.multiple.dots' => array( - 'some more' => array( - 'nested stuff' => 'With more values' - ) - ) - ) + Config::fromArray( + array( + 'section' => array( + 'foo.bar' => 1337 ), - 'filename' => $this->tempFile - ) + 'section.with.multiple.dots' => array( + 'some more.nested stuff' => 'With more values' + ) + ) + ), + $this->tempFile ); $writer->write(); $config = Config::fromIni($this->tempFile)->toArray(); $this->assertTrue(array_key_exists('section.with.multiple.dots', $config), 'Section names not normalized'); } - public function testWhetherSimplePropertiesAreInsertedInEmptyFiles() - { - $this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore'); - $target = $this->writeConfigToTemporaryFile(''); - $config = Config::fromArray(array('key' => 'value')); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertEquals('value', $newConfig->get('key'), 'IniWriter does not insert in empty files'); - } - - public function testWhetherSimplePropertiesAreInsertedInExistingFiles() - { - $this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore'); - $target = $this->writeConfigToTemporaryFile('key1 = "1"'); - $config = Config::fromArray(array('key2' => '2')); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertEquals('2', $newConfig->get('key2'), 'IniWriter does not insert in existing files'); - } - - /** - * @depends testWhetherSimplePropertiesAreInsertedInExistingFiles - */ - public function testWhetherSimplePropertiesAreUpdated() - { - $this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore'); - $target = $this->writeConfigToTemporaryFile('key = "value"'); - $config = Config::fromArray(array('key' => 'eulav')); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertEquals('eulav', $newConfig->get('key'), 'IniWriter does not update simple properties'); - } - - /** - * @depends testWhetherSimplePropertiesAreInsertedInExistingFiles - */ - public function testWhetherSimplePropertiesAreDeleted() - { - $this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore'); - $target = $this->writeConfigToTemporaryFile('key = "value"'); - $config = new Config(); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertNull($newConfig->get('key'), 'IniWriter does not delete simple properties'); - } - public function testWhetherNestedPropertiesAreInserted() { $target = $this->writeConfigToTemporaryFile(''); $config = Config::fromArray(array('a' => array('b' => 'c'))); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); + $writer = new IniWriter($config, $target); $writer->write(); $newConfig = Config::fromIni($target); @@ -127,454 +68,54 @@ class IniWriterTest extends BaseTestCase ); } - /** - * @depends testWhetherNestedPropertiesAreInserted - */ - public function testWhetherNestedPropertiesAreUpdated() - { - $this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore'); - $target = $this->writeConfigToTemporaryFile('a.b = "c"'); - $config = Config::fromArray(array('a' => array('b' => 'cc'))); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertInstanceOf( - get_class($newConfig), - $newConfig->get('a'), - 'IniWriter does not update nested properties' - ); - $this->assertEquals( - 'cc', - $newConfig->get('a')->get('b'), - 'IniWriter does not update nested properties' - ); - } - - /** - * @depends testWhetherNestedPropertiesAreInserted - */ - public function testWhetherNestedPropertiesAreDeleted() - { - $this->markTestSkipped('Implementation has changed. Section-less properties are not supported anymore'); - $target = $this->writeConfigToTemporaryFile('a.b = "c"'); - $config = new Config(); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertNull( - $newConfig->get('a'), - 'IniWriter does not delete nested properties' - ); - } - - public function testWhetherSimpleSectionPropertiesAreInserted() - { - $target = $this->writeConfigToTemporaryFile(''); - $config = Config::fromArray(array('section' => array('key' => 'value'))); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertInstanceOf( - 'Icinga\Data\ConfigObject', - $newConfig->getSection('section'), - 'IniWriter does not insert sections' - ); - $this->assertEquals( - 'value', - $newConfig->getSection('section')->get('key'), - 'IniWriter does not insert simple section properties' - ); - } - - /** - * @depends testWhetherSimpleSectionPropertiesAreInserted - */ - public function testWhetherSimpleSectionPropertiesAreUpdated() - { - $target = $this->writeConfigToTemporaryFile(<<<'EOD' -[section] -key = "value" -EOD - ); - $config = Config::fromArray(array('section' => array('key' => 'eulav'))); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertEquals( - 'eulav', - $newConfig->getSection('section')->get('key'), - 'IniWriter does not update simple section properties' - ); - } - - /** - * @depends testWhetherSimpleSectionPropertiesAreInserted - */ - public function testWhetherSimpleSectionPropertiesAreDeleted() - { - $target = $this->writeConfigToTemporaryFile(<<<'EOD' -[section] -key = "value" -EOD - ); - $config = Config::fromArray(array('section' => array())); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertNull( - $newConfig->getSection('section')->get('key'), - 'IniWriter does not delete simple section properties' - ); - } - - public function testWhetherNestedSectionPropertiesAreInserted() - { - $this->markTestSkipped('Implementation has changed. Config::fromIni cannot handle nested properties anymore'); - $target = $this->writeConfigToTemporaryFile(''); - $config = Config::fromArray(array('section' => array('a' => array('b' => 'c')))); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertInstanceOf( - get_class($newConfig), - $newConfig->get('section'), - 'IniWriter does not insert sections' - ); - $this->assertInstanceOf( - get_class($newConfig), - $newConfig->get('section')->get('a'), - 'IniWriter does not insert nested section properties' - ); - $this->assertEquals( - 'c', - $newConfig->get('section')->get('a')->get('b'), - 'IniWriter does not insert nested section properties' - ); - } - - /** - * @depends testWhetherNestedSectionPropertiesAreInserted - */ - public function testWhetherNestedSectionPropertiesAreUpdated() - { - $target = $this->writeConfigToTemporaryFile(<<<'EOD' -[section] -a.b = "c" -EOD - ); - $config = Config::fromArray(array('section' => array('a' => array('b' => 'cc')))); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertEquals( - 'cc', - $newConfig->get('section')->get('a')->get('b'), - 'IniWriter does not update nested section properties' - ); - } - - /** - * @depends testWhetherNestedSectionPropertiesAreInserted - */ - public function testWhetherNestedSectionPropertiesAreDeleted() - { - $target = $this->writeConfigToTemporaryFile(<<<'EOD' -[section] -a.b = "c" -EOD - ); - $config = Config::fromArray(array('section' => array())); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertNull( - $newConfig->get('section')->get('a'), - 'IniWriter does not delete nested section properties' - ); - } - - public function testWhetherSimplePropertiesOfExtendingSectionsAreInserted() - { - $this->markTestSkipped( - 'Implementation has changed. There is no "Extend" functionality anymore in our Config object' - ); - $target = $this->writeConfigToTemporaryFile(''); - $config = Config::fromArray( - array( - 'foo' => array('key1' => '1'), - 'bar' => array('key2' => '2') - ) - ); - $config->setExtend('bar', 'foo'); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertInstanceOf( - get_class($newConfig), - $newConfig->get('foo'), - 'IniWriter does not insert extended sections' - ); - $this->assertInstanceOf( - get_class($newConfig), - $newConfig->get('bar'), - 'IniWriter does not insert extending sections' - ); - $this->assertEquals( - '2', - $newConfig->get('bar')->get('key2'), - 'IniWriter does not insert simple properties into extending sections' - ); - $this->assertEquals( - '1', - $newConfig->get('foo')->get('key1'), - 'IniWriter does not properly define extending sections' - ); - } - - /** - * @depends testWhetherSimplePropertiesOfExtendingSectionsAreInserted - */ - public function testWhetherSimplePropertiesOfExtendingSectionsAreUpdated() - { - $this->markTestSkipped( - 'Implementation has changed. There is no "Extend" functionality anymore in our Config object' - ); - $target = $this->writeConfigToTemporaryFile(<<<'EOD' -[foo] -key1 = "1" - -[bar : foo] -key2 = "2" -EOD - ); - $config = Config::fromArray( - array( - 'foo' => array('key1' => '1'), - 'bar' => array('key2' => '22') - ) - ); - $config->setExtend('bar', 'foo'); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertEquals( - '22', - $newConfig->get('bar')->get('key2'), - 'IniWriter does not update simple properties of extending sections' - ); - } - - /** - * @depends testWhetherSimplePropertiesOfExtendingSectionsAreInserted - */ - public function testWhetherSimplePropertiesOfExtendingSectionsAreDeleted() - { - $this->markTestSkipped( - 'Implementation has changed. There is no "Extend" functionality anymore in our Config object' - ); - $target = $this->writeConfigToTemporaryFile(<<<'EOD' -[foo] -key1 = "1" - -[bar : foo] -key2 = "2" -EOD - ); - $config = Config::fromArray( - array( - 'foo' => array('key1' => '1'), - 'bar' => array() - ) - ); - $config->setExtend('bar', 'foo'); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertNull( - $newConfig->get('bar')->get('key2'), - 'IniWriter does not delete simple properties of extending sections' - ); - } - - public function testWhetherNestedPropertiesOfExtendingSectionsAreInserted() - { - $this->markTestSkipped( - 'Implementation has changed. There is no "Extend" functionality anymore in our Config object' - ); - $target = $this->writeConfigToTemporaryFile(''); - $config = Config::fromArray( - array( - 'foo' => array('a' => array('b' => 'c')), - 'bar' => array('d' => array('e' => 'f')) - ) - ); - $config->setExtend('bar', 'foo'); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertInstanceOf( - get_class($newConfig), - $newConfig->get('foo'), - 'IniWriter does not insert extended sections' - ); - $this->assertInstanceOf( - get_class($newConfig), - $newConfig->get('bar'), - 'IniWriter does not insert extending sections' - ); - $this->assertInstanceOf( - get_class($newConfig), - $newConfig->get('bar')->get('d'), - 'IniWriter does not insert nested properties into extending sections' - ); - $this->assertEquals( - 'f', - $newConfig->get('bar')->get('d')->get('e'), - 'IniWriter does not insert nested properties into extending sections' - ); - $this->assertEquals( - 'c', - $newConfig->get('bar')->get('a')->get('b'), - 'IniWriter does not properly define extending sections with nested properties' - ); - } - - /** - * @depends testWhetherNestedPropertiesOfExtendingSectionsAreInserted - */ - public function testWhetherNestedPropertiesOfExtendingSectionsAreUpdated() - { - $target = $this->writeConfigToTemporaryFile(<<<'EOD' -[foo] -a.b = "c" - -[bar : foo] -d.e = "f" -EOD - ); - $config = Config::fromArray( - array( - 'foo' => array('a' => array('b' => 'c')), - 'bar' => array('d' => array('e' => 'ff')) - ) - ); - $config->setExtend('bar', 'foo'); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertEquals( - 'ff', - $newConfig->get('bar')->get('d')->get('e'), - 'IniWriter does not update nested properties of extending sections' - ); - } - - /** - * @depends testWhetherNestedPropertiesOfExtendingSectionsAreInserted - */ - public function testWhetherNestedPropertiesOfExtendingSectionsAreDeleted() - { - $this->markTestSkipped( - 'Implementation has changed. There is no "Extend" functionality anymore in our Config object' - ); - $target = $this->writeConfigToTemporaryFile(<<<'EOD' -[foo] -a.b = "c" - -[bar : foo] -d.e = "f" -EOD - ); - $config = Config::fromArray( - array( - 'foo' => array('a' => array('b' => 'c')), - 'bar' => array() - ) - ); - $config->setExtend('bar', 'foo'); - $writer = new IniWriter(array('config' => $config, 'filename' => $target)); - $writer->write(); - - $newConfig = Config::fromIni($target); - $this->assertNull( - $newConfig->get('bar')->get('d'), - 'IniWriter does not delete nested properties of extending sections' - ); - } - public function testWhetherSectionOrderIsUpdated() { $config = <<<'EOD' [one] -key1 = "1" -key2 = "2" - +key1 = "1" +key2 = "2" [two] -a.b = "c" -d.e = "f" - +a.b = "c" +d.e = "f" [three] -key = "value" -foo.bar = "raboof" +key = "value" +foo.bar = "raboof" EOD; $reverted = <<<'EOD' [three] -key = "value" -foo.bar = "raboof" - +key = "value" +foo.bar = "raboof" [two] -a.b = "c" -d.e = "f" - +a.b = "c" +d.e = "f" [one] -key1 = "1" -key2 = "2" +key1 = "1" +key2 = "2" EOD; $target = $this->writeConfigToTemporaryFile($config); $writer = new IniWriter( - array( - 'config' => Config::fromArray( - array( - 'three' => array( - 'foo' => array( - 'bar' => 'raboof' - ), - 'key' => 'value' - ), - 'two' => array( - 'd' => array( - 'e' => 'f' - ), - 'a' => array( - 'b' => 'c' - ) - ), - 'one' => array( - 'key2' => '2', - 'key1' => '1' - ) + Config::fromArray( + array( + 'three' => array( + 'foo.bar' => 'raboof', + 'key' => 'value' + ), + 'two' => array( + 'd.e' => 'f', + 'a.b' => 'c' + ), + 'one' => array( + 'key2' => '2', + 'key1' => '1' ) - ), - 'filename' => $target - ) + ) + ), + $target ); $this->assertEquals( @@ -590,7 +131,6 @@ EOD; ; comment 1 [one] - ; comment 2 [two] EOD; @@ -599,21 +139,18 @@ EOD; ; comment 2 [two] - ; comment 1 [one] EOD; $target = $this->writeConfigToTemporaryFile($config); $writer = new IniWriter( - array( - 'config' => Config::fromArray( - array( - 'two' => array(), - 'one' => array() - ) - ), - 'filename' => $target - ) + Config::fromArray( + array( + 'two' => array(), + 'one' => array() + ) + ), + $target ); $this->assertEquals( @@ -628,18 +165,18 @@ EOD; { $config = <<<'EOD' ; some interesting comment -key = "value" -; another interesting comment +[blarg] +key = "value" + +; some dangling comment ; boring comment EOD; $target = $this->writeConfigToTemporaryFile($config); - $writer = new IniWriter( - array('config' => Config::fromArray(array('key' => 'value')), 'filename' => $target) - ); + $writer = new IniWriter(Config::fromArray(array('blarg' => array('key' => 'value'))), $target); $this->assertEquals( - $config, - $writer->render(), + trim($config), + trim($writer->render()), 'IniWriter does not preserve comments on empty lines' ); } @@ -647,29 +184,27 @@ EOD; public function testWhetherCommentsOnPropertyLinesArePreserved() { $config = <<<'EOD' -foo = 1337 ; I know what a " and a ' is -bar = 7331 ; I; tend; to; overact; !1!1!!11!111! ; -key = "value" ; some comment for a small sized property -xxl = "very loooooooooooooooooooooong" ; my value is very lo... +[blarg] +foo = "1337" ; I know what a " and a ' is +bar = "7331" ; I; tend; to; overact; !1!1!!11!111! ; +key = "value" ; some comment for a small sized property +xxl = "very loooooooooooooooooooooong" ; my value is very lo... EOD; $target = $this->writeConfigToTemporaryFile($config); $writer = new IniWriter( - array( - 'config' => Config::fromArray( - array( - 'foo' => 1337, - 'bar' => 7331, - 'key' => 'value', - 'xxl' => 'very loooooooooooooooooooooong' - ) - ), - 'filename' => $target - ) + Config::fromArray( + array('blarg' => array( + 'foo' => 1337, + 'bar' => 7331, + 'key' => 'value', + 'xxl' => 'very loooooooooooooooooooooong' + )) + ), + $target ); - $this->assertEquals( - $config, - $writer->render(), + trim($config), + trim($writer->render()), 'IniWriter does not preserve comments on property lines' ); } @@ -679,16 +214,14 @@ EOD; $config = <<<'EOD' [section] ; some interesting comment, in a section -key = "value" +key = "value" EOD; $target = $this->writeConfigToTemporaryFile($config); - $writer = new IniWriter( - array('config' => Config::fromArray(array('section' => array('key' => 'value'))), 'filename' => $target) - ); + $writer = new IniWriter(Config::fromArray(array('section' => array('key' => 'value'))), $target); $this->assertEquals( - $config, - $writer->render(), + trim($config), + trim($writer->render()), 'IniWriter does not preserve comments on empty section lines' ); } @@ -697,37 +230,166 @@ EOD; { $config = <<<'EOD' [section] -foo = 1337 ; I know what a " and a ' is -bar = 7331 ; I; tend; to; overact; !1!1!!11!111! ; -key = "value" ; some comment for a small sized property -xxl = "very loooooooooooooooooooooong" ; my value is very lo... +foo = "1337" ; I know what a " and a ' is +bar = "7331" ; I; tend; to; overact; !1!1!!11!111! ; +key = "value" ; some comment for a small sized property +xxl = "very loooooooooooooooooooooong" ; my value is very lo... EOD; $target = $this->writeConfigToTemporaryFile($config); $writer = new IniWriter( - array( - 'config' => Config::fromArray( - array( - 'section' => array( - 'foo' => 1337, - 'bar' => 7331, - 'key' => 'value', - 'xxl' => 'very loooooooooooooooooooooong' - ) + Config::fromArray( + array( + 'section' => array( + 'foo' => 1337, + 'bar' => 7331, + 'key' => 'value', + 'xxl' => 'very loooooooooooooooooooooong' ) - ), - 'filename' => $target - ) + ) + ), + $target ); $this->assertEquals( - $config, - $writer->render(), + trim($config), + trim($writer->render()), 'IniWriter does not preserve comments on property lines' ); } + public function testWhetherLinebreaksAreProcessed() + { + $target = $this->writeConfigToTemporaryFile(''); + $writer = new IniWriter( + Config::fromArray( + array( + 'section' => array( + 'foo' => 'linebreak +in line', + 'linebreak +inkey' => 'blarg' + ) + ) + ), + $target + ); + + $rendered = $writer->render(); + $this->assertEquals( + count(explode("\n", $rendered)), + 5, + 'generated config should not contain more than three line breaks' + ); + } + + public function testSectionNameEscaping() + { + $config = <<<'EOD' +[section [brackets\]] +foo = "bar" + +[section \;comment] +foo = "bar" + +[section \"quotes\"] +foo = "bar" + +[section with \\] +foo = "bar" + +[section with newline] +foo = "bar" +EOD; + $target = $this->writeConfigToTemporaryFile($config); + $writer = new IniWriter( + Config::fromArray( + array( + 'section [brackets]' => array('foo' => 'bar'), + 'section ;comment' => array('foo' => 'bar'), + 'section "quotes"' => array('foo' => 'bar'), + 'section with \\' => array('foo' => 'bar'), + 'section with' . PHP_EOL . 'newline' => array('foo' => 'bar') + ) + ), + $target + ); + + $this->assertEquals( + trim($config), + trim($writer->render()), + 'IniWriter does not handle special chars in section names properly.' + ); + } + + public function testDirectiveValueEscaping() + { + $config = <<<'EOD' +[section] +key1 = "value with \"quotes\"" +key2 = "value with \\" + +EOD; + $target = $this->writeConfigToTemporaryFile($config); + $writer = new IniWriter( + Config::fromArray( + array( + 'section' => array( + 'key1' => 'value with "quotes"', + 'key2' => 'value with \\' + ) + ) + ), + $target + ); + + $this->assertEquals( + trim($config), + trim($writer->render()), + 'IniWriter does not handle special chars in directives properly.' + ); + } + + public function testSectionDeleted() + { + $config = <<<'EOD' +[section 1] +guarg = "1" + +[section 2] +foo = "1337" +foo2 = "baz" +foo3 = "nope" +foo4 = "bar" + +[section 3] +guard = "2" +EOD; + $deleted = <<<'EOD' +[section 1] +guarg = "1" + +[section 3] +guard = "2" +EOD; + + $target = $this->writeConfigToTemporaryFile($config); + $writer = new IniWriter( + Config::fromArray(array( + 'section 1' => array('guarg' => 1), + 'section 3' => array('guard' => 2) + )), + $target + ); + + $this->assertEquals( + trim($deleted), + trim($writer->render()), + 'IniWriter does not delete sections properly' + ); + } + /** - * Write a INI-configuration string to a temporary file and return it's path + * Write a INI-configuration string to a temporary file and return its path * * @param string $config The config string to write * diff --git a/test/php/library/Icinga/Logger/Writer/StreamWriterTest.php b/test/php/library/Icinga/Logger/Writer/StreamWriterTest.php index 0c0d55498..c67e5433e 100644 --- a/test/php/library/Icinga/Logger/Writer/StreamWriterTest.php +++ b/test/php/library/Icinga/Logger/Writer/StreamWriterTest.php @@ -1,6 +1,5 @@ select(); } @@ -37,51 +36,11 @@ class QueryTest extends BaseTestCase return $select; } - public function testLimit() - { - $select = $this->prepareSelect(); - $this->assertEquals(10, $select->getLimit()); - $this->assertEquals(4, $select->getOffset()); - } - - public function testHasLimit() - { - $select = $this->emptySelect(); - $this->assertFalse($select->hasLimit()); - $select = $this->prepareSelect(); - $this->assertTrue($select->hasLimit()); - } - - public function testHasOffset() - { - $select = $this->emptySelect(); - $this->assertFalse($select->hasOffset()); - $select = $this->prepareSelect(); - $this->assertTrue($select->hasOffset()); - } - - public function testGetLimit() - { - $select = $this->prepareSelect(); - $this->assertEquals(10, $select->getLimit()); - } - - public function testGetOffset() - { - $select = $this->prepareSelect(); - $this->assertEquals(10, $select->getLimit()); - } - public function testFetchTree() { $this->markTestIncomplete('testFetchTree is not implemented yet - requires real LDAP'); } - public function testFrom() - { - return $this->testListFields(); - } - public function testWhere() { $this->markTestIncomplete('testWhere is not implemented yet'); @@ -89,30 +48,13 @@ class QueryTest extends BaseTestCase public function testOrder() { - $select = $this->emptySelect()->order('bla'); - // tested by testGetSortColumns + $this->markTestIncomplete('testOrder is not implemented yet, order support for ldap queries is incomplete'); } - public function testListFields() - { - $select = $this->prepareSelect(); - $this->assertEquals( - array('testIntColumn', 'testStringColumn'), - $select->listFields() - ); - } - - public function testGetSortColumns() - { - $select = $this->prepareSelect(); - $cols = $select->getSortColumns(); - $this->assertEquals('testIntColumn', $cols[0][0]); - } - - public function testCreateQuery() + public function testRenderFilter() { $select = $this->prepareSelect(); $res = '(&(objectClass=dummyClass)(testIntColumn=1)(testStringColumn=test)(testWildcard=abc*))'; - $this->assertEquals($res, $select->create()); + $this->assertEquals($res, (string) $select); } } diff --git a/test/php/library/Icinga/Test/BaseTestCaseTest.php b/test/php/library/Icinga/Test/BaseTestCaseTest.php index 2f8e12f0c..a0d6cbce9 100644 --- a/test/php/library/Icinga/Test/BaseTestCaseTest.php +++ b/test/php/library/Icinga/Test/BaseTestCaseTest.php @@ -1,6 +1,5 @@ setPermissions(array( 'test', 'test/some/specific', - 'test/more/*' + 'test/more/*', + 'test/wildcard-with-wildcard/*', + 'test/even-more/specific-with-wildcard/*' )); $this->assertTrue($user->can('test')); $this->assertTrue($user->can('test/some/specific')); $this->assertTrue($user->can('test/more/everything')); + $this->assertTrue($user->can('test/wildcard-with-wildcard/*')); + $this->assertTrue($user->can('test/wildcard-with-wildcard/sub/sub')); + $this->assertTrue($user->can('test/even-more/*')); $this->assertFalse($user->can('not/test')); $this->assertFalse($user->can('test/some/not/so/specific')); + $this->assertFalse($user->can('test/wildcard2/*')); } } diff --git a/test/php/library/Icinga/Util/DateTimeFactoryTest.php b/test/php/library/Icinga/Util/DateTimeFactoryTest.php deleted file mode 100644 index 1b7778587..000000000 --- a/test/php/library/Icinga/Util/DateTimeFactoryTest.php +++ /dev/null @@ -1,70 +0,0 @@ - 'invalid')); - } - - public function testWhetherParseWorksWithASpecificTimezone() - { - $dt = DateTimeFactory::parse('17-04-14 17:00', 'd-m-y H:i', new DateTimeZone('Europe/Berlin')); - $dt->setTimezone(new DateTimeZone('UTC')); - - $this->assertEquals( - '15', - $dt->format('H'), - 'DateTimeFactory::parse does not properly parse a given datetime or does not respect the given timezone' - ); - } - - public function testWhetherParseWorksWithoutASpecificTimezone() - { - $this->assertEquals( - '15', - DateTimeFactory::parse('17-04-14 15:00', 'd-m-y H:i')->format('H'), - 'DateTimeFactory::parse does not properly parse a given datetime' - ); - } - - public function testWhetherCreateWorksWithASpecificTimezone() - { - $dt = DateTimeFactory::create('2014-04-17 5PM', new DateTimeZone('Europe/Berlin')); - $dt->setTimezone(new DateTimeZone('UTC')); - - $this->assertEquals( - '15', - $dt->format('H'), - 'DateTimeFactory::create does not properly parse a given datetime or does not respect the given timezone' - ); - } - - public function testWhetherCreateWorksWithoutASpecificTimezone() - { - $this->assertEquals( - '15', - DateTimeFactory::create('2014-04-17 3PM')->format('H'), - 'DateTimeFactory::create does not properly parse a given datetime' - ); - } -} diff --git a/test/php/library/Icinga/Util/DimensionTest.php b/test/php/library/Icinga/Util/DimensionTest.php index d6678258f..9144778df 100644 --- a/test/php/library/Icinga/Util/DimensionTest.php +++ b/test/php/library/Icinga/Util/DimensionTest.php @@ -1,6 +1,5 @@ getResponseMock()->shouldReceive('redirectAndExit')->atLeast()->once()->with('special/route'); + $this->getResponseMock()->shouldReceive('redirectAndExit')->atLeast()->once() + ->with(Mockery::on(function ($url) { return $url->getRelativeUrl() === 'special/route'; })); $form = new SuccessfulForm(); $form->setTokenDisabled(); @@ -239,7 +239,7 @@ class FormTest extends BaseTestCase } /** - * @expectedException LogicException + * @expectedException \Icinga\Exception\ProgrammingError */ public function testWhetherTheOnSuccessOptionMustBeCallable() { diff --git a/test/php/library/Icinga/Web/HookTest.php b/test/php/library/Icinga/Web/HookTest.php index 16300f5ca..97c628e5b 100644 --- a/test/php/library/Icinga/Web/HookTest.php +++ b/test/php/library/Icinga/Web/HookTest.php @@ -1,6 +1,5 @@ assertInstanceOf( - 'Icinga\Web\View\DateTimeRenderer', - $dt, - 'Dashboard::create() could not create DateTimeRenderer' - ); - } - - /** - * @depends testWhetherCreateCreatesDateTimeRenderer - */ - public function testWhetherIsDateTimeReturnsRightType() - { - $dateTime = new DateTime('+1 day'); - $dt = DateTimeRenderer::create($dateTime); - - $this->assertTrue( - $dt->isDateTime(), - 'Dashboard::isDateTime() returns wrong type' - ); - } - - /** - * @depends testWhetherCreateCreatesDateTimeRenderer - */ - public function testWhetherIsTimeReturnsRightType() - { - $dateTime = new DateTime('+1 hour'); - $dt = DateTimeRenderer::create($dateTime); - - $this->assertTrue( - $dt->isTime(), - 'Dashboard::isTime() returns wrong type' - ); - } - - /** - * @depends testWhetherCreateCreatesDateTimeRenderer - */ - public function testWhetherIsTimespanReturnsRightType() - { - $dateTime = new DateTime('+1 minute'); - $dt = DateTimeRenderer::create($dateTime); - - $this->assertTrue( - $dt->isTimespan(), - 'Dashboard::isTimespan() returns wrong type' - ); - } - - /** - * @depends testWhetherCreateCreatesDateTimeRenderer - */ - public function testWhetherNormalizeReturnsNormalizedDateTime() - { - $dateTime = time(); - $dt = DateTimeRenderer::normalize($dateTime); - - $this->assertInstanceOf( - 'DateTime', - $dt, - 'Dashboard::normalize() returns wrong instance' - ); - } - - /** - * @depends testWhetherCreateCreatesDateTimeRenderer - */ - public function testWhetherRenderReturnsRightText() - { - $dateTime = new DateTime('+1 minute'); - $dt = DateTimeRenderer::create($dateTime); - - $text = $dt->render( - '#1 The service is down since %s', - '#2 The service is down since %s o\'clock.', - '#3 The service is down for %s.' - ); - - $this->assertRegExp( - '/#3/', - $text, - 'Dashboard::render() returns wrong text' - ); - } -} diff --git a/test/php/library/Icinga/Web/Widget/DashboardTest.php b/test/php/library/Icinga/Web/Widget/DashboardTest.php index 2aa2bdcf4..181dec0a9 100644 --- a/test/php/library/Icinga/Web/Widget/DashboardTest.php +++ b/test/php/library/Icinga/Web/Widget/DashboardTest.php @@ -1,6 +1,5 @@ shouldReceive('getPaneItems')->andReturn(array( 'test-pane' => new Pane('Test Pane') )); + $moduleMock->shouldReceive('getName')->andReturn('test'); $moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager'); $moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array( @@ -131,7 +131,10 @@ class DashboardTest extends BaseTestCase */ public function testLoadPaneItemsProvidedByEnabledModules() { + $user = new User('test'); + $user->setPermissions(array('*' => '*')); $dashboard = new Dashboard(); + $dashboard->setUser($user); $dashboard->load(); $this->assertCount( diff --git a/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php b/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php index 5e6e1cc69..6f5063946 100644 --- a/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php +++ b/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php @@ -1,11 +1,11 @@ shouldReceive('getSearchUrls')->andReturn(array( $searchUrl )); + $moduleMock->shouldReceive('getName')->andReturn('test'); $moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager'); $moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array( @@ -35,14 +36,22 @@ class SearchDashboardTest extends BaseTestCase */ public function testWhetherRenderThrowsAnExceptionWhenHasNoDashlets() { - $dashboard = SearchDashboard::search('pending'); + $user = new User('test'); + $user->setPermissions(array('*' => '*')); + $dashboard = new SearchDashboard(); + $dashboard->setUser($user); + $dashboard = $dashboard->search('pending'); $dashboard->getPane('search')->removeDashlets(); $dashboard->render(); } public function testWhetherSearchLoadsSearchDashletsFromModules() { - $dashboard = SearchDashboard::search('pending'); + $user = new User('test'); + $user->setPermissions(array('*' => '*')); + $dashboard = new SearchDashboard(); + $dashboard->setUser($user); + $dashboard = $dashboard->search('pending'); $result = $dashboard->getPane('search')->hasDashlet('Hosts: pending'); @@ -51,7 +60,11 @@ class SearchDashboardTest extends BaseTestCase public function testWhetherSearchProvidesHintWhenSearchStringIsEmpty() { - $dashboard = SearchDashboard::search(); + $user = new User('test'); + $user->setPermissions(array('*' => '*')); + $dashboard = new SearchDashboard(); + $dashboard->setUser($user); + $dashboard = $dashboard->search(); $result = $dashboard->getPane('search')->hasDashlet('Ready to search'); diff --git a/test/php/regression/Bug4102Test.php b/test/php/regression/Bug4102Test.php index ce5ea2290..b5586926e 100644 --- a/test/php/regression/Bug4102Test.php +++ b/test/php/regression/Bug4102Test.php @@ -1,6 +1,5 @@