-
data-icinga-module="" data-icinga-url="" style="display: block"> +
data-icinga-module="" data-icinga-url="without('renderLayout') ?>" style="display: block"> render('inline.phtml') ?>
diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml index b4fd504ed..32c4879b4 100644 --- a/application/layouts/scripts/layout.phtml +++ b/application/layouts/scripts/layout.phtml @@ -44,6 +44,7 @@ $iframeClass = $isIframe ? ' iframe' : ''; + diff --git a/application/layouts/scripts/parts/navigation.phtml b/application/layouts/scripts/parts/navigation.phtml index c689db54a..8b557e32f 100644 --- a/application/layouts/scripts/parts/navigation.phtml +++ b/application/layouts/scripts/parts/navigation.phtml @@ -14,5 +14,5 @@ if (! $this->auth()->isAuthenticated()) {
-order(), Url::fromRequest()->getRelativeUrl()); ?> +order(), Url::fromRequest()->without('renderLayout')->getRelativeUrl()); ?>
diff --git a/application/views/scripts/authentication/logout.phtml b/application/views/scripts/authentication/logout.phtml index eb0d6dc44..9b03d190f 100644 --- a/application/views/scripts/authentication/logout.phtml +++ b/application/views/scripts/authentication/logout.phtml @@ -7,10 +7,7 @@ in every further request until the browser was closed. To allow logout and to allow the user to change the logged-in user this JavaScript provides a workaround to force a new authentication prompt in most browsers. --> - -
-
-
+

-
-
-
-
- +
-
- diff --git a/config/authentication.ini.in b/config/authentication.ini.in index 554356492..71d9e4402 100644 --- a/config/authentication.ini.in +++ b/config/authentication.ini.in @@ -1,7 +1,7 @@ ; authentication.ini ; ; Each section listed in this configuration represents a backend used to authenticate users. The backend configurations -: must define a resource referring to a configured database or LDAP connection in the INI file resources.ini. +; must define a resource referring to a configured database or LDAP connection in the INI file resources.ini. ; ; The backends will be processed from top to bottom using the first backend for authentication which reports that ; the user trying to log in is available. diff --git a/config/dashboard/dashboard.ini b/config/dashboard/dashboard.ini index 50e5a6373..022b34be5 100644 --- a/config/dashboard/dashboard.ini +++ b/config/dashboard/dashboard.ini @@ -23,13 +23,17 @@ sort = host_severity title = "Landing page" [Landing.Hostgroups] -url = "monitoring/chart/hostgroup?height=400&width=500" +url = "monitoring/chart/hostgroup" [Landing.Servicegroups] -url = "monitoring/chart/servicegroup?height=360&width=450" +url = "monitoring/chart/servicegroup" [Landing.Unhandled Problem Services] -url = "monitoring/list/services?service_handled=0&service_problem=1" +url = "monitoring/list/services" +service_handled = 0 +service_problem = 1 [Landing.Unhandled Problem Hosts] -url = "monitoring/list/hosts?host_handled=0&host_problem=1" +url = "monitoring/list/hosts" +host_handled = 0 +host_problem = 1 diff --git a/config/menu.ini b/config/menu.ini index 65959c93a..075f4230e 100644 --- a/config/menu.ini +++ b/config/menu.ini @@ -18,10 +18,15 @@ title = "Configuration" url = "config" priority = 300 +[System.Modules] +title = "Modules" +url = "config/modules" +priority = 400 + [System.ApplicationLog] title = "Application log" url = "list/applicationlog" -priority = 400 +priority = 500 [Logout] url = "authentication/logout" diff --git a/config/modules/doc/menu.ini b/config/modules/doc/menu.ini new file mode 100644 index 000000000..86889b239 --- /dev/null +++ b/config/modules/doc/menu.ini @@ -0,0 +1,5 @@ +[Documentation] +title = "Documentation" +icon = "img/icons/comment.png" +url = "doc" +priority = 80 diff --git a/config/modules/monitoring/menu.ini b/config/modules/monitoring/menu.ini index c66a611a9..c185fb87d 100644 --- a/config/modules/monitoring/menu.ini +++ b/config/modules/monitoring/menu.ini @@ -68,7 +68,7 @@ priority = 70 [Overview.Comments] title = "Comments" -url = "monitoring/list/comments" +url = "monitoring/list/comments?comment_type=(comment|ack)" priority = 70 [Overview.Contacts] diff --git a/etc/apache/icingaweb.conf.in b/etc/apache/icingaweb.conf.in index 69335368e..3884aea5a 100644 --- a/etc/apache/icingaweb.conf.in +++ b/etc/apache/icingaweb.conf.in @@ -3,8 +3,19 @@ Alias @web_path@ "@prefix@/public" Options SymLinksIfOwnerMatch AllowOverride None - Order allow,deny - Allow from all + + + # Apache 2.4 + + Require all granted + + + + + # Apache 2.2 + Order allow,deny + Allow from all + SetEnv ICINGAWEB_CONFIGDIR @icingaweb_config_path@ diff --git a/icingaweb2.spec b/icingaweb2.spec index b1cb3d2e5..cfdcdafc2 100644 --- a/icingaweb2.spec +++ b/icingaweb2.spec @@ -1,16 +1,37 @@ -# $Id$ -# Authority: The icinga devel team -# Upstream: The icinga devel team -# ExcludeDist: el4 el3 +#/** +# * 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 +# * +# */ -%define revision 0 +%define revision 1 %define configdir %{_sysconfdir}/icingaweb %define sharedir %{_datadir}/icingaweb %define prefixdir %{_datadir}/icingaweb %define logdir %{sharedir}/log %define usermodparam -a -G -#%define logdir %{_localstatedir}/log/icingaweb +%define logdir %{_localstatedir}/log/icingaweb %if "%{_vendor}" == "suse" %define phpname php5 @@ -19,11 +40,12 @@ %endif # SLE 11 = 1110 %if 0%{?suse_version} == 1110 +%define phpname php53 %define apache2modphpname apache2-mod_php53 %define usermodparam -A %endif -%if "%{_vendor}" == "redhat" || 0%{?suse_version} == 1110 +%if "%{_vendor}" == "redhat" %define phpname php %define phpzendname php-ZendFramework %endif @@ -37,15 +59,15 @@ %define apacheconfdir %{_sysconfdir}/apache2/conf.d %define apacheuser wwwrun %define apachegroup www -%define extcmdfile1x %{_localstatedir}/icinga/rw/icinga.cmd -%define livestatussocket1x %{_localstatedir}/icinga/rw/live +%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-1x %{_localstatedir}/spool/icinga/cmd/icinga.cmd -%define livestatussocket1x %{_localstatedir}/spool/icinga/cmd/live +%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 @@ -109,8 +131,8 @@ Requires: php-Icinga %description -IcingaWeb for Icinga 2 or Icinga 1.x using status data, -IDOUtils or Livestatus as backend provider. +Icinga Web 2 for Icinga 2 or Icinga 1.x using multiple backends +for example DB IDO. %package -n icingacli Summary: Icinga CLI @@ -130,58 +152,54 @@ Requires: %{phpzendname} %description -n php-Icinga -Icinga Web 2 PHP Libraries shared with icingacli. - - +Icinga Web 2 PHP Libraries required by the web frontend and cli tool. %prep -#%setup -q -n %{name}-%{version} -%setup -q -n %{name} +#VERSION=0.0.1; git archive --format=tar --prefix=icingaweb2-$VERSION/ HEAD | gzip >icingaweb2-$VERSION.tar.gz +%setup -q -n %{name}-%{version} %build -cat > README.RHEL.SUSE <<"EOF" -IcingaWeb for RHEL and SUSE -=========================== - -Please check ./doc/installation.md -for requirements and database setup. -EOF - %install [ "%{buildroot}" != "/" ] && [ -d "%{buildroot}" ] && rm -rf %{buildroot} # prepare configuration for sub packages # install rhel apache config -install -D -m0644 packages/rhel/etc/httpd/conf.d/icingaweb.conf %{buildroot}/%{apacheconfdir}/icingaweb.conf +install -D -m0644 packages/rpm/etc/httpd/conf.d/icingaweb.conf %{buildroot}/%{apacheconfdir}/icingaweb.conf # install public, library, modules %{__mkdir} -p %{buildroot}/%{sharedir} %{__mkdir} -p %{buildroot}/%{logdir} +%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb +%{__mkdir} -p %{buildroot}/%{_sysconfdir}/dashboard +%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/modules +%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring %{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules %{__cp} -r application library modules public %{buildroot}/%{sharedir}/ -# install index.php -install -m0644 packages/rhel/usr/share/icingaweb/public/index.php %{buildroot}/%{sharedir}/public/index.php - -# use the vagrant config for configuration for now - TODO -%{__cp} -r .vagrant-puppet/files/etc/icingaweb %{buildroot}/%{_sysconfdir}/ - -# we use the default 'icinga' database -sed -i 's/icinga2/icinga/g' %{buildroot}/%{_sysconfdir}/icingaweb/resources.ini +## config +# use the default menu.ini for application and monitoring mobule +install -D -m0644 config/menu.ini %{buildroot}/%{_sysconfdir}/icingaweb/menu.ini +install -D -m0644 config/modules/monitoring/menu.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/menu.ini +# authentication is db only +install -D -m0644 packages/rpm/etc/icingaweb/authentication.ini %{buildroot}/%{_sysconfdir}/icingaweb/authentication.ini +# custom resource paths +install -D -m0644 packages/rpm/etc/icingaweb/resources.ini %{buildroot}/%{_sysconfdir}/icingaweb/resources.ini +# dashboard +install -D -m0644 config/dashboard/dashboard.ini %{buildroot}/%{_sysconfdir}/icingaweb/dashboard/dashboard.ini +# monitoring module (icinga2) +install -D -m0644 packages/rpm/etc/icingaweb/modules/monitoring/backends.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/backends.ini +install -D -m0644 packages/rpm/etc/icingaweb/modules/monitoring/instances.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/instances.ini # enable the monitoring module by default ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules/monitoring +## config # install icingacli -install -D -m0755 bin/icingacli %{buildroot}/usr/bin/icingacli - -# install sql schema files as example - -# delete all *.in files +install -D -m0755 packages/rpm/usr/bin/icingacli %{buildroot}/usr/bin/icingacli %pre # Add apacheuser in the icingacmd group @@ -196,9 +214,6 @@ if [ $? -eq 0 ]; then %{_sbindir}/usermod %{usermodparam} icingacmd %{apacheuser} fi -# uncomment if building from git -# %{__rm} -rf %{buildroot}%{_datadir}/icinga2-web/.git - %preun %post @@ -209,14 +224,13 @@ fi %files # main dirs %defattr(-,root,root) -%doc etc/schema doc packages/rhel/README +%doc etc/schema doc packages/rpm/README.md %attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/public %attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/modules # configs %defattr(-,root,root) %config(noreplace) %attr(-,root,root) %{apacheconfdir}/icingaweb.conf -%dir %{configdir} -%config(noreplace) %attr(775,%{apacheuser},%{apachegroup}) %{configdir} +%config(noreplace) %attr(-,%{apacheuser},%{apachegroup}) %{configdir} # logs %attr(2775,%{apacheuser},%{apachegroup}) %dir %{logdir} @@ -228,6 +242,3 @@ fi %attr(0755,root,root) /usr/bin/icingacli %changelog -* Tue May 11 2014 Michael Friedrich - 0.0.1-1 -- initial creation - diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 8dd189097..3d5e3752e 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -5,6 +5,7 @@ namespace Icinga\Application\Modules; use Exception; +use Zend_Controller_Router_Route_Abstract; use Zend_Controller_Router_Route as Route; use Icinga\Application\ApplicationBootstrap; use Icinga\Application\Config; @@ -135,6 +136,16 @@ class Module */ private $app; + + /** + * Routes to add to the route chain + * + * @var array Array of name-route pairs + * + * @see addRoute() + */ + protected $routes = array(); + /** * Create a new module object * @@ -166,8 +177,7 @@ class Module */ public function register() { - $this->registerAutoloader() - ->registerWebIntegration(); + $this->registerAutoloader(); try { $this->launchRunScript(); } catch (Exception $e) { @@ -179,6 +189,7 @@ class Module ); return false; } + $this->registerWebIntegration(); return true; } @@ -658,24 +669,29 @@ class Module } /** - * Register routes for web access + * Add routes for static content and any route added via addRoute() to the route chain * - * @return self + * @return self + * @see addRoute() */ protected function registerRoutes() { - $this->app->getFrontController()->getRouter()->addRoute( + $router = $this->app->getFrontController()->getRouter(); + foreach ($this->routes as $name => $route) { + $router->addRoute($name, $route); + } + $router->addRoute( $this->name . '_jsprovider', new Route( 'js/' . $this->name . '/:file', array( 'controller' => 'static', 'action' =>'javascript', - 'module_name' => $this->name + 'module_name' => $this->name ) ) ); - $this->app->getFrontController()->getRouter()->addRoute( + $router->addRoute( $this->name . '_img', new Route( 'img/' . $this->name . '/:file', @@ -750,4 +766,19 @@ class Module return $this; } + + /** + * Add a route which will be added to the route chain + * + * @param string $name Name of the route + * @param Zend_Controller_Router_Route_Abstract $route Instance of the route + * + * @return self + * @see registerRoutes() + */ + protected function addRoute($name, Zend_Controller_Router_Route_Abstract $route) + { + $this->routes[$name] = $route; + return $this; + } } diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/Backend/AutoLoginBackend.php index d793b50dd..16373bb6c 100644 --- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php +++ b/library/Icinga/Authentication/Backend/AutoLoginBackend.php @@ -53,6 +53,7 @@ class AutoLoginBackend extends UserBackend { if (isset($_SERVER['REMOTE_USER'])) { $username = $_SERVER['REMOTE_USER']; + $user->setRemoteUserInformation($username, 'REMOTE_USER'); if ($this->stripUsernameRegexp !== null) { $stripped = preg_replace($this->stripUsernameRegexp, '', $username); if ($stripped !== false) { diff --git a/library/Icinga/Authentication/Manager.php b/library/Icinga/Authentication/Manager.php index 01964ef00..ec49aa416 100644 --- a/library/Icinga/Authentication/Manager.php +++ b/library/Icinga/Authentication/Manager.php @@ -30,12 +30,6 @@ class Manager */ private $user; - /** - * If the user was authenticated from the REMOTE_USER server variable - * - * @var Boolean - */ - private $fromRemoteUser = false; private function __construct() { @@ -117,6 +111,13 @@ class Manager public function authenticateFromSession() { $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(); + } + } } /** @@ -204,35 +205,4 @@ class Manager { return $this->user->getGroups(); } - - /** - * Tries to authenticate the user from the session, and then from the REMOTE_USER superglobal, that can be set by - * an external authentication provider. - */ - public function authenticateFromRemoteUser() - { - if (array_key_exists('REMOTE_USER', $_SERVER)) { - $this->fromRemoteUser = true; - } - $this->authenticateFromSession(); - if ($this->user !== null) { - if (array_key_exists('REMOTE_USER', $_SERVER) && $this->user->getUsername() !== $_SERVER["REMOTE_USER"]) { - // Remote user has changed, clear all sessions - $this->removeAuthorization(); - } - return; - } - if (array_key_exists('REMOTE_USER', $_SERVER) && $_SERVER["REMOTE_USER"]) { - $this->user = new User($_SERVER["REMOTE_USER"]); - $this->persistCurrentUser(); - } - } - - /** - * If the session was established from the REMOTE_USER server variable. - */ - public function isAuthenticatedFromRemoteUser() - { - return $this->fromRemoteUser; - } } diff --git a/library/Icinga/Chart/Axis.php b/library/Icinga/Chart/Axis.php index db1a82a83..00a5f5679 100644 --- a/library/Icinga/Chart/Axis.php +++ b/library/Icinga/Chart/Axis.php @@ -9,6 +9,7 @@ use Icinga\Chart\Primitive\Drawable; use Icinga\Chart\Primitive\Line; use Icinga\Chart\Primitive\Text; use Icinga\Chart\Render\RenderContext; +use Icinga\Chart\Render\Rotator; use Icinga\Chart\Unit\AxisUnit; use Icinga\Chart\Unit\CalendarUnit; use Icinga\Chart\Unit\LinearUnit; @@ -188,11 +189,11 @@ class Axis implements Drawable $labelField->setFontSize('2.5em'); } + if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) { + $labelField = new Rotator($labelField, 45); + } $labelField = $labelField->toSvg($ctx); - if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) { - $labelField = $this->rotate($ctx, $labelField, 45); - } $group->appendChild($labelField); if ($this->drawYGrid) { @@ -214,34 +215,6 @@ class Axis implements Drawable } } - /** - * Rotate the given element. - * - * @param RenderContext $ctx The rendering context - * @param DOMElement $el The element to rotate - * @param $degrees The rotation degrees - * - * @return DOMElement - */ - private function rotate(RenderContext $ctx, DOMElement $el, $degrees) - { - // Create a box containing the rotated element relative to the original text position - $container = $ctx->getDocument()->createElement('g'); - $x = $el->getAttribute('x'); - $y = $el->getAttribute('y'); - $container->setAttribute('transform', 'translate(' . $x . ',' . $y . ')'); - $el->removeAttribute('x'); - $el->removeAttribute('y'); - - // Create a rotated box containing the text - $rotate = $ctx->getDocument()->createElement('g'); - $rotate->setAttribute('transform', 'rotate(' . $degrees . ')'); - $rotate->appendChild($el); - - $container->appendChild($rotate); - return $container; - } - /** * Render the vertical axis * @@ -275,9 +248,9 @@ class Axis implements Drawable if ($this->yLabel) { $axisLabel = new Text(-8, 50, $this->yLabel); $axisLabel->setFontSize('2em') - ->setAdditionalStyle(Text::ORIENTATION_VERTICAL) ->setFontWeight('bold') ->setAlignment(Text::ALIGN_MIDDLE); + $axisLabel = new Rotator($axisLabel, 90); $group->appendChild($axisLabel->toSvg($ctx)); } diff --git a/library/Icinga/Chart/Graph/BarGraph.php b/library/Icinga/Chart/Graph/BarGraph.php index 8bde864ce..968e4c0f7 100644 --- a/library/Icinga/Chart/Graph/BarGraph.php +++ b/library/Icinga/Chart/Graph/BarGraph.php @@ -69,7 +69,7 @@ class BarGraph extends Styleable implements Drawable $group = $doc->createElement('g'); $idx = 0; foreach ($this->dataSet as $point) { - $rect = new Rect($point[0] - 1, $point[1], 2, 100 - $point[1]); + $rect = new Rect($point[0] - 2, $point[1], 4, 100 - $point[1]); $rect->setFill($this->fill); $rect->setStrokeWidth($this->strokeWidth); $rect->setStrokeColor('black'); diff --git a/library/Icinga/Chart/Primitive/Text.php b/library/Icinga/Chart/Primitive/Text.php index 5521d6e27..72bed552b 100644 --- a/library/Icinga/Chart/Primitive/Text.php +++ b/library/Icinga/Chart/Primitive/Text.php @@ -30,16 +30,6 @@ class Text extends Styleable implements Drawable */ const ALIGN_MIDDLE = 'middle'; - /** - * Normal left to right orientation - */ - const ORIENTATION_HORIZONTAL = ""; - - /** - * Top down orientation (rotated by 90°) - */ - const ORIENTATION_VERTICAL = "writing-mode: tb;"; - /** * The x position of the Text * diff --git a/library/Icinga/Chart/Render/Rotator.php b/library/Icinga/Chart/Render/Rotator.php new file mode 100644 index 000000000..934b784b6 --- /dev/null +++ b/library/Icinga/Chart/Render/Rotator.php @@ -0,0 +1,86 @@ +element = $element; + $this->degrees = $degrees; + } + + /** + * Rotate the given element. + * + * @param RenderContext $ctx The rendering context + * @param DOMElement $el The element to rotate + * @param $degrees The amount of degrees + * + * @return DOMElement The rotated DOMElement + */ + private function rotate(RenderContext $ctx, DOMElement $el, $degrees) + { + // Create a box containing the rotated element relative to the original element position + $container = $ctx->getDocument()->createElement('g'); + $x = $el->getAttribute('x'); + $y = $el->getAttribute('y'); + $container->setAttribute('transform', 'translate(' . $x . ',' . $y . ')'); + $el->removeAttribute('x'); + $el->removeAttribute('y'); + + // Put the element into a rotated group + //$rotate = $ctx->getDocument()->createElement('g'); + $el->setAttribute('transform', 'rotate(' . $degrees . ')'); + //$rotate->appendChild($el); + + $container->appendChild($el); + return $container; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + $el = $this->element->toSvg($ctx); + return $this->rotate($ctx, $el, $this->degrees); + } +} \ No newline at end of file diff --git a/library/Icinga/Chart/Unit/LinearUnit.php b/library/Icinga/Chart/Unit/LinearUnit.php index bf27486d2..d776fe304 100644 --- a/library/Icinga/Chart/Unit/LinearUnit.php +++ b/library/Icinga/Chart/Unit/LinearUnit.php @@ -90,17 +90,52 @@ class LinearUnit implements AxisUnit } sort($datapoints); if (!$this->staticMax) { - $this->max = max($this->max, $datapoints[count($datapoints)-1]); + $this->max = max($this->max, $datapoints[count($datapoints) - 1]); } if (!$this->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; + } + } + return $steps[$step] * $factor; + } + /** * Transform the absolute value to an axis relative value * @@ -114,7 +149,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->max - $this->min; } } @@ -176,6 +211,7 @@ class LinearUnit implements AxisUnit if ($max !== null) { $this->max = $max; $this->staticMax = true; + $this->updateMaxValue(); } } @@ -189,6 +225,7 @@ class LinearUnit implements AxisUnit if ($min !== null) { $this->min = $min; $this->staticMin = true; + $this->updateMaxValue(); } } diff --git a/library/Icinga/Cli/Command.php b/library/Icinga/Cli/Command.php index 081be318c..acb2462e4 100644 --- a/library/Icinga/Cli/Command.php +++ b/library/Icinga/Cli/Command.php @@ -7,6 +7,7 @@ namespace Icinga\Cli; use Icinga\Cli\Screen; use Icinga\Util\Translator; use Icinga\Cli\Params; +use Icinga\Application\Config; use Icinga\Application\ApplicationBootstrap as App; use Exception; @@ -23,6 +24,10 @@ abstract class Command protected $commandName; protected $actionName; + private $config; + + private $configs; + protected $defaultActionName = 'default'; public function __construct(App $app, $moduleName, $commandName, $actionName, $initialize = true) @@ -41,6 +46,51 @@ abstract class Command } } + public function Config($file = null) + { + if ($this->isModule()) { + return $this->getModuleConfig($file); + } else { + return $this->getMainConfig($file); + } + } + + private function getModuleConfig($file = null) + { + if ($file === null) { + if ($this->config === null) { + $this->config = Config::module($this->moduleName); + } + return $this->config; + } else { + if (! array_key_exists($file, $this->configs)) { + $this->configs[$file] = Config::module($this->moduleName, $file); + } + return $this->configs[$file]; + } + } + + private function getMainConfig($file = null) + { + if ($file === null) { + if ($this->config === null) { + $this->config = Config::app(); + } + return $this->config; + } else { + if (! array_key_exists($file, $this->configs)) { + $this->configs[$file] = Config::module($module, $file); + } + return $this->configs[$file]; + } + return $this->config; + } + + public function isModule() + { + return substr(get_class($this), 0, 14) === 'Icinga\\Module\\'; + } + public function setParams(Params $params) { $this->params = $params; diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php index 7fef56efc..3fb74a656 100644 --- a/library/Icinga/Data/Db/DbQuery.php +++ b/library/Icinga/Data/Db/DbQuery.php @@ -192,6 +192,24 @@ class DbQuery extends SimpleQuery return $this->escapeForSql(date('Y-m-d H:i:s', $value)); } + /** + * Check for timestamp fields + * + * TODO: This is not here to do automagic timestamp stuff. One may + * override this function for custom voodoo, IdoQuery right now + * does. IMO we need to split whereToSql functionality, however + * I'd prefer to wait with this unless we understood how other + * backends will work. We probably should also rename this + * function to isTimestampColumn(). + * + * @param string $field Field Field name to checked + * @return bool Whether this field expects timestamps + */ + public function isTimestamp($field) + { + return $this; + } + public function whereToSql($col, $sign, $expression) { if ($this->isTimestamp($col)) { diff --git a/library/Icinga/Data/Identifiable.php b/library/Icinga/Data/Identifiable.php new file mode 100644 index 000000000..cfa727a1d --- /dev/null +++ b/library/Icinga/Data/Identifiable.php @@ -0,0 +1,18 @@ +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 new file mode 100644 index 000000000..6953214dc --- /dev/null +++ b/library/Icinga/Data/Tree/NodeInterface.php @@ -0,0 +1,26 @@ +transport->send($command); + $this->transport->send($this->escape($command)); + } + + /** + * Return the given command string with escaped newlines + * + * @param string $command The command string to escape + * + * @return string The escaped command string + */ + public function escape($command) + { + return str_replace(array("\r", "\n"), array('\r', '\n'), $command); } /** @@ -121,16 +133,14 @@ class CommandPipe public function sendCommand(Command $command, array $objects = array()) { if ($command->provideGlobalCommand() === true) { - $this->transport->send($command->getGlobalCommand()); + $this->send($command->getGlobalCommand()); } else { foreach ($objects as $object) { $objectType = $this->getObjectType($object); if ($objectType === self::TYPE_SERVICE) { - $this->transport->send( - $command->getServiceCommand($object->host_name, $object->service_description) - ); + $this->send($command->getServiceCommand($object->host_name, $object->service_description)); } else { - $this->transport->send($command->getHostCommand($object->host_name)); + $this->send($command->getHostCommand($object->host_name)); } } } diff --git a/library/Icinga/Protocol/File/Exception/FileReaderException.php b/library/Icinga/Protocol/File/Exception/FileReaderException.php new file mode 100644 index 000000000..3e0890a57 --- /dev/null +++ b/library/Icinga/Protocol/File/Exception/FileReaderException.php @@ -0,0 +1,10 @@ +sortDir = ($dir === null || strtoupper(trim($dir)) === 'DESC') ? self::SORT_DESC : self::SORT_ASC; + $this->sortDir = ( + $direction === null || strtoupper(trim($direction)) === 'DESC' + ) ? self::SORT_DESC : self::SORT_ASC; return $this; } @@ -80,4 +83,4 @@ class Query extends SimpleQuery { return $this->filters; } -} \ No newline at end of file +} diff --git a/library/Icinga/Protocol/File/Reader.php b/library/Icinga/Protocol/File/Reader.php index f5677e855..50842bc4a 100644 --- a/library/Icinga/Protocol/File/Reader.php +++ b/library/Icinga/Protocol/File/Reader.php @@ -4,38 +4,92 @@ namespace Icinga\Protocol\File; -use Icinga\Data\DatasourceInterface; +use FilterIterator; +use Iterator; +use Zend_Config; +use Icinga\Protocol\File\FileReaderException; +use Icinga\Util\File; /** - * Class Reader - * * Read file line by line - * - * @package Icinga\Protocol\File */ -class Reader implements DatasourceInterface +class Reader extends FilterIterator { /** - * Name of the file to read + * A PCRE string with the fields to extract from the file's lines as named subpatterns * * @var string */ - private $filename; + protected $fields; /** - * Configuration for this Datasource + * An associative array of the current line's fields ($field => $value) * - * @var \Zend_Config + * @var array */ - private $config; + protected $currentData; /** - * @param \Zend_Config $config + * Create a new reader + * + * @param Zend_Config $config + * + * @throws FileReaderException If a required $config directive (filename or fields) is missing */ - public function __construct($config) + public function __construct(Zend_Config $config) { - $this->config = $config; - $this->filename = $config->filename; + foreach (array('filename', 'fields') as $key) { + if (! isset($config->{$key})) { + throw new FileReaderException('The directive `' . $key . '\' is required'); + } + } + $this->fields = $config->fields; + $f = new File($config->filename); + $f->setFlags( + File::DROP_NEW_LINE | + File::READ_AHEAD | + File::SKIP_EMPTY + ); + parent::__construct($f); + } + + /** + * Return the current data + * + * @return array + */ + public function current() + { + return $this->currentData; + } + + /** + * Accept lines matching the given PCRE pattern + * + * @return bool + * + * @throws FileReaderException If PHP failed parsing the PCRE pattern + */ + public function accept() + { + $data = array(); + $matched = @preg_match( + $this->fields, + $this->getInnerIterator()->current(), + $data + ); + if ($matched === false) { + throw new FileReaderException('Failed parsing regular expression!'); + } else if ($matched === 1) { + foreach ($data as $key) { + if (is_int($key)) { + unset($data[$key]); + } + } + $this->currentData = $data; + return true; + } + return false; } /** @@ -48,27 +102,78 @@ class Reader implements DatasourceInterface return new Query($this); } + /** + * Return the number of available valid lines. + * + * @return int + */ + public function count() + { + return iterator_count($this); + } + /** * Fetch result as an array of objects * - * @return array + * @param Query $query + * + * @return array */ public function fetchAll(Query $query) { $all = array(); foreach ($this->fetchPairs($query) as $index => $value) { - $all[$index] = new \stdClass(); - foreach ($value as $key => $value_2) { - $all[$index]->{$key} = $value_2; - } + $all[$index] = (object) $value; } return $all; } + /** + * Fetch result as a key/value pair array + * + * @param Query $query + * + * @return array + */ + public function fetchPairs(Query $query) + { + $skipLines = $query->getOffset(); + $readLines = $query->getLimit(); + if ($skipLines === null) { + $skipLines = 0; + } + $lines = array(); + if ($query->sortDesc()) { + $count = $this->count($query); + if ($count <= $skipLines) { + return $lines; + } else if ($count < ($skipLines + $readLines)) { + $readLines = $count - $skipLines; + $skipLines = 0; + } else { + $skipLines = $count - ($skipLines + $readLines); + } + } + foreach ($this as $index => $line) { + if ($index >= $skipLines) { + if ($index >= $skipLines + $readLines) { + break; + } + $lines[] = $line; + } + } + if ($query->sortDesc()) { + $lines = array_reverse($lines); + } + return $lines; + } + /** * Fetch first result row * - * @return object + * @param Query $query + * + * @return object */ public function fetchRow(Query $query) { @@ -82,14 +187,16 @@ class Reader implements DatasourceInterface /** * Fetch first result column * - * @return array + * @param Query $query + * + * @return array */ public function fetchColumn(Query $query) { $column = array(); - foreach ($this->fetchPairs($query) as $value) { - foreach ($value as $value_2) { - $column[] = $value_2; + foreach ($this->fetchPairs($query) as $pair) { + foreach ($pair as $value) { + $column[] = $value; break; } } @@ -99,7 +206,9 @@ class Reader implements DatasourceInterface /** * Fetch first column value from first result row * - * @return mixed + * @param Query $query + * + * @return mixed */ public function fetchOne(Query $query) { @@ -111,215 +220,4 @@ class Reader implements DatasourceInterface } return null; } - - /** - * Fetch result as a key/value pair array - * - * @return array - */ - public function fetchPairs(Query $query) - { - return $this->read($query); - } - - /** - * If given $line matches the $query's PCRE pattern and contains all the strings in the $query's filters array, - * return an associative array of the matches of the PCRE pattern. - * Otherwise, return false. - * If preg_match returns false, it failed parsing the PCRE pattern. - * In that case, throw an exception. - * - * @param string $line - * @param Query $query - * - * @return array|bool - * - * @throws \Exception - */ - public function validateLine($line, Query $query) - { - $data = array(); - $PCRE_result = @preg_match($this->config->fields, $line, $data); - if ($PCRE_result === false) { - throw new \Exception('Failed parsing regular expression!'); - } else if ($PCRE_result === 1) { - foreach ($query->getFilters() as $filter) { - if (strpos($line, $filter) === false) { - return false; - } - } - foreach ($data as $key => $value) { - if (is_int($key)) { - unset($data[$key]); - } - } - return $data; - } - return false; - } - - /** - * Skip and read as many lines as needed according to given $query. - * - * @param Query $query - * - * @return array result - */ - public function read(Query $query) - { - $skip_lines = $query->getOffset(); - $read_lines = $query->getLimit(); - if ($skip_lines === null) { - $skip_lines = 0; - } - return $this->{$query->sortDesc() ? 'readFromEnd' : 'readFromStart'}($skip_lines, $read_lines, $query); - } - - /** - * Backend for $this->read - * Direction: LIFO - */ - public function readFromEnd($skip_lines, $read_lines, Query $query) - { - $PHP_EOL_len = strlen(PHP_EOL); - $lines = array(); - $s = ''; - $f = @fopen($this->filename, 'rb'); - if ($f !== false) { - $buffer = ''; - fseek($f, 0, SEEK_END); - if (ftell($f) === 0) { - return array(); - } - while ($read_lines === null || count($lines) < $read_lines) { - $c = $this->fgetc($f, $buffer); - if ($c === false) { - $l = $this->validateLine($s, $query); - if (!($l === false || $skip_lines)) { - $lines[] = $l; - } - break; - } - $s = $c . $s; - if (strpos($s, PHP_EOL) === 0) { - $l = $this->validateLine((string)substr($s, $PHP_EOL_len), $query); - if ($l !== false) { - if ($skip_lines) { - $skip_lines--; - } else { - $lines[] = $l; - } - } - $s = ''; - } - } - } - return $lines; - } - - /** - * Backend for $this->readFromEnd - */ - public function fgetc($file, &$buffer) - { - $strlen = strlen($buffer); - if ($strlen === 0) { - $pos = ftell($file); - if ($pos === 0) { - return false; - } - if ($pos < 4096) { - fseek($file, 0); - $buffer = fread($file, $pos); - fseek($file, 0); - } else { - fseek($file, -4096, SEEK_CUR); - $buffer = fread($file, 4096); - fseek($file, -4096, SEEK_CUR); - } - return $this->fgetc($file, $buffer); - } else { - $char = substr($buffer, -1); - $buffer = substr($buffer, 0, $strlen - 1); - return $char; - } - } - - /** - * Backend for $this->read - * Direction: FIFO - */ - public function readFromStart($skip_lines, $read_lines, Query $query) - { - $PHP_EOL_len = strlen(PHP_EOL); - $lines = array(); - $s = ''; - $f = @fopen($this->filename, 'rb'); - if ($f !== false) { - $buffer = ''; - while ($read_lines === null || count($lines) < $read_lines) { - if (strlen($buffer) === 0) { - $buffer = fread($f, 4096); - if (strlen($buffer) === 0) { - $l = $this->validateLine($s, $query); - if (!($l === false || $skip_lines)) { - $lines[] = $l; - } - break; - } - } - $s .= substr($buffer, 0, 1); - $buffer = substr($buffer, 1); - if (strpos($s, PHP_EOL) !== false) { - $l = $this->validateLine((string)substr($s, 0, strlen($s) - $PHP_EOL_len), $query); - if ($l !== false) { - if ($skip_lines) { - $skip_lines--; - } else { - $lines[] = $l; - } - } - $s = ''; - } - } - } - return $lines; - } - - /** - * Return the number of available valid lines. - * - * @param Query $query - * - * @return int - */ - public function count(Query $query) { - $PHP_EOL_len = strlen(PHP_EOL); - $lines = 0; - $s = ''; - $f = @fopen($this->filename, 'rb'); - if ($f !== false) { - $buffer = ''; - while (true) { - if (strlen($buffer) === 0) { - $buffer = fread($f, 4096); - if (strlen($buffer) === 0) { - if ($this->validateLine($s, $query) !== false) { - $lines++; - } - break; - } - } - $s .= substr($buffer, 0, 1); - $buffer = substr($buffer, 1); - if (strpos($s, PHP_EOL) !== false) { - if ($this->validateLine((string)substr($s, 0, strlen($s) - $PHP_EOL_len), $query) !== false) { - $lines++; - } - $s = ''; - } - } - } - return $lines; - } } diff --git a/library/Icinga/User.php b/library/Icinga/User.php index 848877850..13f62881a 100644 --- a/library/Icinga/User.php +++ b/library/Icinga/User.php @@ -58,6 +58,18 @@ class User */ protected $additionalInformation = array(); + /** + * Information if the user is external authenticated + * + * Keys: + * + * 0: origin username + * 1: origin field name + * + * @var array + */ + protected $remoteUserInformation = array(); + /** * Set of permissions * @@ -401,4 +413,35 @@ class User { $this->messages = null; } + + /** + * Set additional remote user information + * + * @param stirng $username + * @param string $field + */ + public function setRemoteUserInformation($username, $field) + { + $this->remoteUserInformation = array($username, $field); + } + + /** + * Get additional remote user information + * + * @return array + */ + public function getRemoteUserInformation() + { + return $this->remoteUserInformation; + } + + /** + * Return true if user has remote user information set + * + * @return bool + */ + public function isRemoteUser() + { + return (count($this->remoteUserInformation)) ? true : false; + } } diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php index 111e414ad..fd01812b3 100644 --- a/library/Icinga/Util/Translator.php +++ b/library/Icinga/Util/Translator.php @@ -115,7 +115,7 @@ class Translator */ public static function getAvailableLocaleCodes() { - $codes = array(); + $codes = array(static::DEFAULT_LOCALE); foreach (array_values(self::$knownDomains) as $directory) { $dh = opendir($directory); while (false !== ($name = readdir($dh))) { diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index b28bddc37..13809e83d 100644 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -254,10 +254,27 @@ class ActionController extends Zend_Controller_Action * * @throws \Exception */ - protected function redirectToLogin($afterLogin = '/dashboard') + protected function redirectToLogin($afterLogin = null) { + $redir = null; + if ($afterLogin !== null) { + if (! $afterLogin instanceof Url) { + $afterLogin = Url::fromPath($afterLogin); + } + if ($this->isXhr()) { + $redir = '__SELF__'; + } else { + // TODO: Ignore /? + $redir = $afterLogin->getRelativeUrl(); + } + } + $url = Url::fromPath('authentication/login'); - $url->setParam('redirect', $afterLogin); + + if ($redir) { + $url->setParam('redirect', $redir); + } + $this->rerenderLayout()->redirectNow($url); } @@ -273,6 +290,27 @@ class ActionController extends Zend_Controller_Action return $this->getRequest()->isXmlHttpRequest(); } + protected function redirectXhr($url) + { + if (! $url instanceof Url) { + $url = Url::fromPath($url); + } + + if ($this->rerenderLayout) { + $this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes'); + } + if ($this->reloadCss) { + $this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now'); + } + + $this->getResponse() + ->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl())) + ->sendHeaders(); + + // TODO: Session shutdown? + exit; + } + /** * Redirect to a specific url, updating the browsers URL field * @@ -280,26 +318,13 @@ class ActionController extends Zend_Controller_Action **/ public function redirectNow($url) { - if (! $url instanceof Url) { - $url = Url::fromPath($url); - } - $url = preg_replace('~&~', '&', $url); if ($this->isXhr()) { - if ($this->rerenderLayout) { - $this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes'); - } - if ($this->reloadCss) { - $this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now'); - } - - $this->getResponse() - ->setHeader('X-Icinga-Redirect', rawurlencode($url)) - ->sendHeaders(); - - // TODO: Session shutdown? - exit; + $this->redirectXhr($url); } else { - $this->_helper->Redirector->gotoUrlAndExit(Url::fromPath($url)->getRelativeUrl()); + if (! $url instanceof Url) { + $url = Url::fromPath($url); + } + $this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl()); } } @@ -362,6 +387,8 @@ class ActionController extends Zend_Controller_Action 'X-Icinga-Title', rawurlencode($this->view->title . ' :: Icinga Web') ); + } else { + $resp->setHeader('X-Icinga-Title', rawurlencode('Icinga Web')); } if ($this->rerenderLayout) { diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index c9da962be..ed3acf6a7 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -127,10 +127,6 @@ class Url $baseUrl = $request->getBaseUrl(); $urlObject->setBaseUrl($baseUrl); - // Fetch fragment manually and remove it from the url, to 'help' the parse_url() function - // parsing the url properly. Otherwise calling the function with a fragment, but without a - // query will cause unpredictable behaviour. - $url = self::stripUrlFragment($url); $urlParts = parse_url($url); if (isset($urlParts['path'])) { if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) { @@ -144,44 +140,14 @@ class Url $params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params); } - $fragment = self::getUrlFragment($url); - if ($fragment !== '') { - $urlObject->setAnchor($fragment); + if (isset($urlParts['fragment'])) { + $urlObject->setAnchor($urlParts['fragment']); } $urlObject->setParams($params); return $urlObject; } - /** - * Get the fragment of a given url - * - * @param string $url The url containing the fragment. - * - * @return string The fragment without the '#' - */ - protected static function getUrlFragment($url) - { - $url = parse_url($url); - if (isset($url['fragment'])) { - return $url['fragment']; - } else { - return ''; - } - } - - /** - * Remove the fragment-part of a given url - * - * @param string $url The url to strip from its fragment - * - * @return string The url without the fragment - */ - protected static function stripUrlFragment($url) - { - return preg_replace('/#.*$/', '', $url); - } - /** * Overwrite the baseUrl * @@ -241,12 +207,12 @@ class Url * * @return string */ - public function getRelativeUrl() + public function getRelativeUrl($separator = '&') { if ($this->params->isEmpty()) { return $this->path . $this->anchor; } else { - return $this->path . '?' . $this->params->setSeparator('&') . $this->anchor; + return $this->path . '?' . $this->params->toString($separator) . $this->anchor; } } @@ -266,9 +232,9 @@ class Url * * @return string */ - public function getAbsoluteUrl() + public function getAbsoluteUrl($separator = '&') { - return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl(); + return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl($separator); } /** @@ -331,11 +297,11 @@ class Url /** * Return all parameters that will be used in the query part * - * @return array An associative key => value array containing all parameters + * @return UrlParams An instance of UrlParam containing all parameters */ public function getParams() { - return $this->params->asArray(); + return $this->params; } /** @@ -450,6 +416,6 @@ class Url */ public function __toString() { - return $this->getAbsoluteUrl(); + return $this->getAbsoluteUrl('&'); } } diff --git a/library/Icinga/Web/UrlParams.php b/library/Icinga/Web/UrlParams.php index 9a8b60929..bdf776401 100644 --- a/library/Icinga/Web/UrlParams.php +++ b/library/Icinga/Web/UrlParams.php @@ -114,6 +114,18 @@ class UrlParams return $ret; } + public function addEncoded($param, $value = true) + { + $this->params[] = array($param, $this->cleanupValue($value)); + $this->indexLastOne(); + return $this; + } + + protected function urlEncode($value) + { + return rawurlencode((string) $value); + } + /** * Add the given parameter with the given value * @@ -127,9 +139,7 @@ class UrlParams */ public function add($param, $value = true) { - $this->params[] = array($param, $this->cleanupValue($value)); - $this->indexLastOne(); - return $this; + return $this->addEncoded($this->urlEncode($param), $this->urlEncode($value)); } /** @@ -198,7 +208,7 @@ class UrlParams */ public function unshift($param, $value) { - array_unshift($this->params, array($param, $this->cleanupValue($value))); + array_unshift($this->params, array($this->urlEncode($param), $this->urlEncode($value))); $this->reIndexAll(); return $this; } @@ -224,7 +234,10 @@ class UrlParams unset($this->params[$remove]); } - $this->params[$this->index[$param][0]] = array($param, $this->cleanupValue($value)); + $this->params[$this->index[$param][0]] = array( + $this->urlEncode($param), + $this->urlEncode($this->cleanupValue($value)) + ); $this->reIndexAll(); return $this; @@ -243,7 +256,7 @@ class UrlParams foreach ($this->index[$p] as $key) { unset($this->params[$key]); } - $this->changed = true; + $changed = true; } } @@ -303,20 +316,23 @@ class UrlParams protected function parseQueryStringPart($part) { if (strpos($part, '=') === false) { - $this->add($part, true); + $this->addEncoded($part, true); } else { list($key, $val) = preg_split('/=/', $part, 2); - $this->add($key, $val); + $this->addEncoded($key, $val); } } - public function asArray() + public function toArray() { return $this->params; } - public function __toString() + public function toString($separator = null) { + if ($separator === null) { + $separator = $this->separator; + } $parts = array(); foreach ($this->params as $p) { if ($p[1] === true) { @@ -325,13 +341,18 @@ class UrlParams $parts[] = $p[0] . '=' . $p[1]; } } - return implode($this->separator, $parts); + return implode($separator, $parts); + } + + public function __toString() + { + return $this->toString(); } public static function fromQueryString($queryString = null) { if ($queryString === null) { - $queryString = $_SERVER['QUERY_STRING']; + $queryString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; } $params = new static(); $params->parseQueryString($queryString); diff --git a/library/Icinga/Web/Widget/Dashboard/Component.php b/library/Icinga/Web/Widget/Dashboard/Component.php index 8bac34359..2d8517526 100644 --- a/library/Icinga/Web/Widget/Dashboard/Component.php +++ b/library/Icinga/Web/Widget/Dashboard/Component.php @@ -126,7 +126,7 @@ EOD; public function toArray() { $array = array('url' => $this->url->getPath()); - foreach ($this->url->getParams() as $param) { + foreach ($this->url->getParams()->toArray() as $param) { $array[$param[0]] = $param[1]; } return $array; diff --git a/library/Icinga/Web/Widget/Limiter.php b/library/Icinga/Web/Widget/Limiter.php index 9c6a726ba..5afa7e4b8 100644 --- a/library/Icinga/Web/Widget/Limiter.php +++ b/library/Icinga/Web/Widget/Limiter.php @@ -81,7 +81,7 @@ class Limiter extends AbstractWidget $this->url->setParam('limit', $limit), null, array( - 'title' => t(sprintf('Show %s rows on one page', $caption)) + 'title' => sprintf(t('Show %s rows on one page'), $caption) ) ); } diff --git a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php index 3d1c97ed0..241b83556 100644 --- a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php +++ b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php @@ -28,7 +28,7 @@ class DashboardAction implements Tabextension 'title' => 'Add To Dashboard', 'url' => Url::fromPath('dashboard/addurl'), 'urlParams' => array( - 'url' => Url::fromRequest()->getRelativeUrl() + 'url' => rawurlencode(Url::fromRequest()->getRelativeUrl()) ) ) ); diff --git a/modules/doc/application/controllers/IcingawebController.php b/modules/doc/application/controllers/IcingawebController.php new file mode 100644 index 000000000..967a2b768 --- /dev/null +++ b/modules/doc/application/controllers/IcingawebController.php @@ -0,0 +1,48 @@ +renderToc(Icinga::app()->getApplicationDir('/../doc'), '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 + */ + public function chapterAction() + { + $chapterId = $this->getParam('chapterId'); + if ($chapterId === null) { + throw new Zend_Controller_Action_Exception( + $this->translate('Missing parameter \'chapterId\''), + 404 + ); + } + $this->renderChapter( + Icinga::app()->getApplicationDir('/../doc'), + $chapterId, + 'doc/icingaweb/toc', + 'doc/icingaweb/chapter' + ); + } + + /** + * View Icinga Web 2's documentation as PDF + */ + public function pdfAction() + { + $this->renderPdf(Icinga::app()->getApplicationDir('/../doc'), 'Icinga Web 2', 'doc/icingaweb/chapter'); + } +} diff --git a/modules/doc/application/controllers/IndexController.php b/modules/doc/application/controllers/IndexController.php index f46fdad87..c83cfabab 100644 --- a/modules/doc/application/controllers/IndexController.php +++ b/modules/doc/application/controllers/IndexController.php @@ -2,34 +2,9 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -use Icinga\Module\Doc\Controller as DocController; - -use Icinga\Module\Doc\DocParser; +use Icinga\Module\Doc\DocController; class Doc_IndexController extends DocController { - protected $parser; - - - public function init() - { - $module = null; - $this->parser = new DocParser($module); - } - - - public function tocAction() - { - // Temporary workaround - list($html, $toc) = $this->parser->getDocumentation(); - $this->view->toc = $toc; - } - - /** - * Display the application's documentation - */ - public function indexAction() - { - $this->populateView(); - } + public function indexAction() {} } diff --git a/modules/doc/application/controllers/ModuleController.php b/modules/doc/application/controllers/ModuleController.php index 41ba42db6..40913368c 100644 --- a/modules/doc/application/controllers/ModuleController.php +++ b/modules/doc/application/controllers/ModuleController.php @@ -2,44 +2,131 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} +use \Zend_Controller_Action_Exception; use Icinga\Application\Icinga; -use Icinga\Module\Doc\Controller as DocController; +use Icinga\Module\Doc\DocController; +use Icinga\Module\Doc\Exception\DocException; class Doc_ModuleController extends DocController { /** - * Display module documentations index + * List modules which are enabled and having the 'doc' directory */ public function indexAction() { - $this->view->enabledModules = Icinga::app()->getModuleManager()->listEnabledModules(); - } - - /** - * Display a module's documentation - */ - public function viewAction() - { - $this->populateView($this->getParam('name')); - } - - /** - * Provide run-time dispatching of module documentation - * - * @param string $methodName - * @param array $args - * - * @return mixed - */ - public function __call($methodName, $args) - { - // TODO(el): Setup routing to retrieve module name as param and point route to moduleAction - $moduleManager = Icinga::app()->getModuleManager(); - $moduleName = substr($methodName, 0, -6); // Strip 'Action' suffix - if (!$moduleManager->hasEnabled($moduleName)) { - // TODO(el): Throw a not found exception once the code has been moved to the moduleAction (see TODO above) - return parent::__call($methodName, $args); + $moduleManager = Icinga::app()->getModuleManager(); + $modules = array(); + foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $enabledModule) { + $docDir = $moduleManager->getModuleDir($enabledModule, '/doc'); + if (is_dir($docDir)) { + $modules[] = $enabledModule; + } } - $this->_helper->redirector->gotoSimpleAndExit('view', null, null, array('name' => $moduleName)); + $this->view->modules = $modules; + } + + /** + * Assert that the given module is enabled + * + * @param $moduleName + * + * @throws Zend_Controller_Action_Exception If the required parameter 'moduleName' is empty or either if the + * given module is neither installed nor enabled + */ + protected function assertModuleEnabled($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 + ); + } + } + + /** + * View the toc of a module's documentation + * + * @see assertModuleEnabled() + */ + public function tocAction() + { + $moduleName = $this->getParam('moduleName'); + $this->assertModuleEnabled($moduleName); + $moduleManager = Icinga::app()->getModuleManager(); + try { + $this->renderToc( + $moduleManager->getModuleDir($moduleName, '/doc'), + $moduleName, + 'doc/module/chapter', + array('moduleName' => $moduleName) + ); + } catch (DocException $e) { + throw new Zend_Controller_Action_Exception($e->getMessage(), 404); + } + $this->view->moduleName = $moduleName; + } + + /** + * 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() + */ + 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 + ); + } + $moduleManager = Icinga::app()->getModuleManager(); + try { + $this->renderChapter( + $moduleManager->getModuleDir($moduleName, '/doc'), + $chapterId, + $this->_helper->url->url(array('moduleName' => $moduleName), 'doc/module/toc'), + 'doc/module/chapter', + array('moduleName' => $moduleName) + ); + } catch (DocException $e) { + throw new Zend_Controller_Action_Exception($e->getMessage(), 404); + } + $this->view->moduleName = $moduleName; + } + + /** + * View a module's documentation as PDF + * + * @see assertModuleEnabled() + */ + public function pdfAction() + { + $moduleName = $this->getParam('moduleName'); + $this->assertModuleEnabled($moduleName); + $moduleManager = Icinga::app()->getModuleManager(); + $this->renderPdf( + $moduleManager->getModuleDir($moduleName, '/doc'), + $moduleName, + 'doc/module/chapter', + array('moduleName' => $moduleName) + ); } } diff --git a/modules/doc/application/views/scripts/chapter.phtml b/modules/doc/application/views/scripts/chapter.phtml new file mode 100644 index 000000000..7657d69fb --- /dev/null +++ b/modules/doc/application/views/scripts/chapter.phtml @@ -0,0 +1,3 @@ +
+ render($this, $this->getHelper('Url')); ?> +
diff --git a/modules/doc/application/views/scripts/index/index.phtml b/modules/doc/application/views/scripts/index/index.phtml index a178cc155..e4218bee2 100644 --- a/modules/doc/application/views/scripts/index/index.phtml +++ b/modules/doc/application/views/scripts/index/index.phtml @@ -1,5 +1,6 @@ -

Icinga 2 Documentation

-partial('module/view.phtml', 'doc', array( - 'toc' => $toc, - 'html' => $html -)); ?> \ No newline at end of file +
+

translate('Available documentations'); ?>

+ diff --git a/modules/doc/application/views/scripts/index/toc.phtml b/modules/doc/application/views/scripts/index/toc.phtml deleted file mode 100644 index 9188e21ff..000000000 --- a/modules/doc/application/views/scripts/index/toc.phtml +++ /dev/null @@ -1,14 +0,0 @@ -
-

Module documentations

-
-
-partial( - 'layout/menu.phtml', - 'default', - array( - 'items' => $toc->getChildren(), - 'sub' => false, - 'url' => '' - ) -) ?> -
diff --git a/modules/doc/application/views/scripts/module/index.phtml b/modules/doc/application/views/scripts/module/index.phtml index 36f11e15e..cc184016f 100644 --- a/modules/doc/application/views/scripts/module/index.phtml +++ b/modules/doc/application/views/scripts/module/index.phtml @@ -1,6 +1,10 @@ -

Module documentations

+

translate('Module documentations'); ?>

diff --git a/modules/doc/application/views/scripts/module/view.phtml b/modules/doc/application/views/scripts/module/view.phtml deleted file mode 100644 index 291947ad7..000000000 --- a/modules/doc/application/views/scripts/module/view.phtml +++ /dev/null @@ -1,7 +0,0 @@ - -

No documentation available.

- -
- -
- diff --git a/modules/doc/application/views/scripts/pdf.phtml b/modules/doc/application/views/scripts/pdf.phtml new file mode 100644 index 000000000..72d77f3c0 --- /dev/null +++ b/modules/doc/application/views/scripts/pdf.phtml @@ -0,0 +1,7 @@ +

translate('Documentation'); ?>

+
+ render($this, $this->getHelper('Url')); ?> +
+
+ render($this, $this->getHelper('Url')); ?> +
diff --git a/modules/doc/application/views/scripts/toc.phtml b/modules/doc/application/views/scripts/toc.phtml new file mode 100644 index 000000000..ca6283d67 --- /dev/null +++ b/modules/doc/application/views/scripts/toc.phtml @@ -0,0 +1,6 @@ +
+

+
+
+ render($this, $this->getHelper('Url')); ?> +
diff --git a/modules/doc/library/Doc/Controller.php b/modules/doc/library/Doc/Controller.php deleted file mode 100644 index 2c5a07d49..000000000 --- a/modules/doc/library/Doc/Controller.php +++ /dev/null @@ -1,23 +0,0 @@ -getDocumentation(); - $this->view->html = $html; - $this->view->toc = $toc; - } -} diff --git a/modules/doc/library/Doc/DocController.php b/modules/doc/library/Doc/DocController.php new file mode 100644 index 000000000..42f8dce9b --- /dev/null +++ b/modules/doc/library/Doc/DocController.php @@ -0,0 +1,76 @@ +view->sectionRenderer = new SectionRenderer( + $parser->getDocTree(), + SectionRenderer::decodeUrlParam($chapterId), + $tocUrl, + $url, + $urlParams + ); + $this->view->title = $chapterId; + $this->_helper->viewRenderer('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 + */ + protected function renderToc($path, $name, $url, array $urlParams = array()) + { + $parser = new DocParser($path); + $this->view->tocRenderer = new TocRenderer($parser->getDocTree(), $url, $urlParams); + $name = ucfirst($name); + $this->view->docName = $name; + $this->view->title = sprintf($this->translate('%s Documentation'), $name); + $this->_helper->viewRenderer('toc', null, true); + } + + /** + * Render a pdf + * + * @param string $path Path to the documentation + * @param string $name Name of the documentation + * @param string $url + * @param array $urlParams + */ + 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; + $this->_helper->viewRenderer('pdf', null, true); + $this->_request->setParam('format', 'pdf'); + } +} diff --git a/modules/doc/library/Doc/DocException.php b/modules/doc/library/Doc/DocException.php deleted file mode 100644 index cb7134045..000000000 --- a/modules/doc/library/Doc/DocException.php +++ /dev/null @@ -1,11 +0,0 @@ -fileInfo = $fileInfo; + } + + /** + * (non-PHPDoc) + * @see Countable::count() + */ + public function count() + { + return count($this->fileInfo); + } + + /** + * (non-PHPDoc) + * @see IteratorAggregate::getIterator() + */ + public function getIterator() + { + return new ArrayIterator($this->fileInfo); + } +} diff --git a/modules/doc/library/Doc/DocParser.php b/modules/doc/library/Doc/DocParser.php index d4e6875d1..c63532dc1 100644 --- a/modules/doc/library/Doc/DocParser.php +++ b/modules/doc/library/Doc/DocParser.php @@ -4,146 +4,65 @@ namespace Icinga\Module\Doc; -use RecursiveIteratorIterator; -use RecursiveDirectoryIterator; -use Parsedown; -use Icinga\Application\Icinga; -use Icinga\Web\Menu; -use Icinga\Web\Url; - -require_once 'IcingaVendor/Parsedown/Parsedown.php'; +use SplDoublyLinkedList; +use Icinga\Exception\NotReadableError; +use Icinga\Module\Doc\Exception\DocEmptyException; +use Icinga\Module\Doc\Exception\DocException; /** * Parser for documentation written in Markdown */ class DocParser { - protected $dir; - - protected $module; + /** + * Path to the documentation + * + * @var string + */ + protected $path; /** - * Create a new documentation parser for the given module or the application + * Iterator over documentation files * - * @param string $module - * - * @throws DocException + * @var DocIterator */ - public function __construct($module = null) - { - if ($module === null) { - $dir = Icinga::app()->getApplicationDir('/../doc'); - } else { - $mm = Icinga::app()->getModuleManager(); - if (!$mm->hasInstalled($module)) { - throw new DocException('Module is not installed'); - } - if (!$mm->hasEnabled($module)) { - throw new DocException('Module is not enabled'); - } - $dir = $mm->getModuleDir($module, '/doc'); - } - if (!is_dir($dir)) { - throw new DocException('Doc directory does not exist'); - } - $this->dir = $dir; - $this->module = $module; - } + protected $docIterator; /** - * Retrieve table of contents and HTML converted from markdown files sorted by filename + * Create a new documentation parser for the given path * - * @return array - * @throws DocException + * @param string $path Path to the documentation + * + * @throws DocException If the documentation directory does not exist + * @throws NotReadableError If the documentation directory is not readable + * @throws DocEmptyException If the documentation directory is empty */ - public function getDocumentation() + public function __construct($path) { - $iter = new RecursiveIteratorIterator( - new MarkdownFileIterator( - new RecursiveDirectoryIterator($this->dir) - ) - ); - $fileInfos = iterator_to_array($iter); - natcasesort($fileInfos); - $cat = array(); - $toc = array((object) array( - 'level' => 0, - 'item' => new Menu('doc') - )); - $itemPriority = 1; - foreach ($fileInfos as $fileInfo) { - try { - $fileObject = $fileInfo->openFile(); - } catch (RuntimeException $e) { - throw new DocException($e->getMessage()); - } - if ($fileObject->flock(LOCK_SH) === false) { - throw new DocException('Couldn\'t get the lock'); - } - $line = null; - while (!$fileObject->eof()) { - // Save last line for setext-style headers - $lastLine = $line; - $line = $fileObject->fgets(); - $header = $this->extractHeader($line, $lastLine); - if ($header !== null) { - list($header, $level) = $header; - $id = $this->extractHeaderId($header); - $attribs = array(); - $this->reduceToc($toc, $level); - if ($id === null) { - $path = array(); - foreach (array_slice($toc, 1) as $entry) { - $path[] = $entry->item->getTitle(); - } - $path[] = $header; - $id = implode('-', $path); - $attribs['rel'] = 'nofollow'; - } - $id = urlencode(str_replace('.', '.', strip_tags($id))); - $item = end($toc)->item->addChild( - $id, - array( - 'url' => Url::fromPath( - 'doc/module/view', - array( - 'name' => $this->module - ) - )->setAnchor($id)->getRelativeUrl(), - 'title' => htmlspecialchars($header), - 'priority' => $itemPriority++, - 'attribs' => $attribs - ) - ); - $toc[] = ((object) array( - 'level' => $level, - 'item' => $item - )); - $line = '' . PHP_EOL . $line; - } - $cat[] = $line; - } - $fileObject->flock(LOCK_UN); + if (! is_dir($path)) { + throw new DocException( + sprintf(mt('doc', 'Documentation directory \'%s\' does not exist'), $path) + ); } - $html = Parsedown::instance()->parse(implode('', $cat)); - $html = preg_replace_callback( - '#
(.*?)\
#s', - array($this, 'highlight'), - $html - ); - return array($html, $toc[0]->item); - } - - /** - * Syntax highlighting for PHP code - * - * @param $match - * - * @return string - */ - protected function highlight($match) - { - return highlight_string(htmlspecialchars_decode($match[1]), true); + if (! is_readable($path)) { + throw new DocException( + sprintf(mt('doc', 'Documentation directory \'%s\' is not readable'), $path) + ); + } + $docIterator = new DocIterator($path); + if ($docIterator->count() === 0) { + throw new DocEmptyException( + sprintf( + mt( + 'doc', + 'Documentation directory \'%s\' does not contain any non-empty Markdown file (\'.md\' suffix)' + ), + $path + ) + ); + } + $this->path = $path; + $this->docIterator = $docIterator; } /** @@ -156,28 +75,28 @@ class DocParser */ protected function extractHeader($line, $lastLine) { - if (!$line) { + if (! $line) { return null; } $header = null; - if ($line && - $line[0] === '#' && - preg_match('/^#+/', $line, $match) === 1 + if ($line + && $line[0] === '#' + && preg_match('/^#+/', $line, $match) === 1 ) { - // Atx-style + // Atx $level = strlen($match[0]); $header = trim(substr($line, $level)); - if (!$header) { + if (! $header) { return null; } } elseif ( - $line && - ($line[0] === '=' || $line[0] === '-') && - preg_match('/^[=-]+\s*$/', $line, $match) === 1 + $line + && ($line[0] === '=' || $line[0] === '-') + && preg_match('/^[=-]+\s*$/', $line, $match) === 1 ) { // Setext $header = trim($lastLine); - if (!$header) { + if (! $header) { return null; } if ($match[0][0] === '=') { @@ -189,36 +108,67 @@ class DocParser if ($header === null) { return null; } - return array($header, $level); - } - - /** - * Extract header id in an a or a span tag - * - * @param string &$header - * - * @return id|null - */ - protected function extractHeaderId(&$header) - { - if ($header[0] === '<' && - preg_match('#(?:<(?Pa|span) id="(?P.+)">)#u', $header, $match) + if ($header[0] === '<' + && preg_match('#(?:<(?Pa|span) (?:id|name)="(?P.+)">)\s*#u', $header, $match) ) { $header = str_replace($match[0], '', $header); - return $match['id']; + $id = $match['id']; + } else { + $id = null; } - return null; + return array($header, $id, $level); } /** - * Reduce the toc to the given level + * Get the documentation tree * - * @param array &$toc - * @param int $level + * @return DocTree */ - protected function reduceToc(array &$toc, $level) { - while (end($toc)->level >= $level) { - array_pop($toc); + public function getDocTree() + { + $tree = new DocTree(); + $stack = new SplDoublyLinkedList(); + foreach ($this->docIterator as $fileInfo) { + /* @var $file \SplFileInfo */ + $file = $fileInfo->openFile(); + /* @var $file \SplFileObject */ + $lastLine = null; + foreach ($file as $line) { + $header = $this->extractHeader($line, $lastLine); + if ($header !== null) { + list($title, $id, $level) = $header; + while (! $stack->isEmpty() && $stack->top()->getLevel() >= $level) { + $stack->pop(); + } + if ($id === null) { + $path = array(); + foreach ($stack as $section) { + /* @var $section Section */ + $path[] = $section->getTitle(); + } + $path[] = $title; + $id = implode('-', $path); + $noFollow = true; + } else { + $noFollow = false; + } + if ($stack->isEmpty()) { + $chapterId = $id; + $section = new Section($id, $title, $level, $noFollow, $chapterId); + $tree->addRoot($section); + } else { + $chapterId = $stack->bottom()->getId(); + $section = new Section($id, $title, $level, $noFollow, $chapterId); + $tree->addChild($section, $stack->top()); + } + $stack->push($section); + } else { + $stack->top()->appendContent($line); + } + // Save last line for setext-style headers + $lastLine = $line; + } } + return $tree; } } diff --git a/modules/doc/library/Doc/DocTree.php b/modules/doc/library/Doc/DocTree.php new file mode 100644 index 000000000..1b112649c --- /dev/null +++ b/modules/doc/library/Doc/DocTree.php @@ -0,0 +1,80 @@ +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 new file mode 100644 index 000000000..cd048a162 --- /dev/null +++ b/modules/doc/library/Doc/Exception/ChapterNotFoundException.php @@ -0,0 +1,10 @@ +getInnerIterator()->current(); - if (!$current->isFile()) { + /* @var $current \SplFileInfo */ + if (! $current->isFile()) { return false; } $filename = $current->getFilename(); diff --git a/modules/doc/library/Doc/NonEmptyFileIterator.php b/modules/doc/library/Doc/NonEmptyFileIterator.php new file mode 100644 index 000000000..71bf5acfa --- /dev/null +++ b/modules/doc/library/Doc/NonEmptyFileIterator.php @@ -0,0 +1,31 @@ +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 new file mode 100644 index 000000000..0aebb89b9 --- /dev/null +++ b/modules/doc/library/Doc/Renderer.php @@ -0,0 +1,75 @@ +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 new file mode 100644 index 000000000..e20d80359 --- /dev/null +++ b/modules/doc/library/Doc/SectionFilterIterator.php @@ -0,0 +1,68 @@ +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 new file mode 100644 index 000000000..938e5ed7b --- /dev/null +++ b/modules/doc/library/Doc/SectionRenderer.php @@ -0,0 +1,292 @@ +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 '>' + } + + /** + * 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 + ); + $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 new file mode 100644 index 000000000..4061e80e3 --- /dev/null +++ b/modules/doc/library/Doc/TocRenderer.php @@ -0,0 +1,109 @@ +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/public/css/module.less b/modules/doc/public/css/module.less new file mode 100644 index 000000000..d6d0d2a94 --- /dev/null +++ b/modules/doc/public/css/module.less @@ -0,0 +1,62 @@ +// W3C Recommendation (except h4) +h1 { font-size: 2em !important; } +h2 { font-size: 1.5em !important; } +h3 { font-size: 1.17em !important; } +h4 { font-size: 1em !important; } +h5 { font-size: .83em !important; } +h6 { font-size: .75em !important; } + +div.chapter { + padding-left: 5px; +} + +table th { + text-align: left; +} + +table th, +table td { + border: solid 1px lightgray; + padding-left: 5px; + padding-right: 5px; +} + +code { + width: 100%; + overflow-x: auto; + padding: 0.2em; + display: inline; +} + +pre > code { + display: inline-block; +} + +div.chapter > ul.navigation { + 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; + } + } +} diff --git a/modules/doc/run.php b/modules/doc/run.php new file mode 100644 index 000000000..7392e4c22 --- /dev/null +++ b/modules/doc/run.php @@ -0,0 +1,50 @@ +isCli()) { + return; +} + +$docModuleChapter = new Zend_Controller_Router_Route( + 'doc/module/:moduleName/chapter/:chapterId', + array( + 'controller' => 'module', + 'action' => 'chapter', + 'module' => 'doc' + ) +); + +$docIcingaWebChapter = new Zend_Controller_Router_Route( + 'doc/icingaweb/chapter/:chapterId', + array( + 'controller' => 'icingaweb', + 'action' => 'chapter', + 'module' => 'doc' + ) +); + +$docModuleToc = new Zend_Controller_Router_Route( + 'doc/module/:moduleName/toc', + array( + 'controller' => 'module', + 'action' => 'toc', + 'module' => 'doc' + ) +); + +$docModulePdf = new Zend_Controller_Router_Route( + 'doc/module/:moduleName/pdf', + array( + 'controller' => 'module', + 'action' => 'pdf', + 'module' => 'doc' + ) +); + +$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/ListCommand.php b/modules/monitoring/application/clicommands/ListCommand.php index 42a377763..e6b86f4ef 100644 --- a/modules/monitoring/application/clicommands/ListCommand.php +++ b/modules/monitoring/application/clicommands/ListCommand.php @@ -72,6 +72,7 @@ class ListCommand extends Command protected function showFormatted($query, $format, $columns) { + $query = $query->getQuery(); switch($format) { case 'json': echo json_encode($query->fetchAll()); @@ -155,7 +156,7 @@ class ListCommand extends Command 'service_perfdata', 'service_last_state_change' ); - $query = $this->getQuery('status', $columns) + $query = $this->getQuery('serviceStatus', $columns) ->order('host_name'); echo $this->renderStatusQuery($query); } @@ -167,6 +168,7 @@ class ListCommand extends Command $screen = $this->screen; $utils = new CliUtils($screen); $maxCols = $screen->getColumns(); + $query = $query->getQuery(); $rows = $query->fetchAll(); $count = $query->count(); $count = count($rows); diff --git a/modules/monitoring/application/controllers/ChartController.php b/modules/monitoring/application/controllers/ChartController.php index 4fee6e34f..19ee73a94 100644 --- a/modules/monitoring/application/controllers/ChartController.php +++ b/modules/monitoring/application/controllers/ChartController.php @@ -87,8 +87,8 @@ class Monitoring_ChartController extends Controller 'services_pending' ) )->getQuery()->fetchAll(); - $this->view->height = intval($this->getParam('height', 220)); - $this->view->width = intval($this->getParam('width', 520)); + $this->view->height = intval($this->getParam('height', 500)); + $this->view->width = intval($this->getParam('width', 500)); if (count($query) === 1) { $this->drawGroupPie($query[0]); } else { @@ -112,8 +112,8 @@ class Monitoring_ChartController extends Controller 'services_pending' ) )->getQuery()->fetchAll(); - $this->view->height = intval($this->getParam('height', 220)); - $this->view->width = intval($this->getParam('width', 520)); + $this->view->height = intval($this->getParam('height', 500)); + $this->view->width = intval($this->getParam('width', 500)); $this->drawServiceGroupChart($query); @@ -133,7 +133,8 @@ class Monitoring_ChartController extends Controller } $this->view->chart = new GridChart(); $this->view->chart->setAxisLabel('', t('Services')) - ->setXAxis(new StaticAxis()); + ->setXAxis(new StaticAxis()) + ->setAxisMin(null, 0); $this->view->chart->drawBars( array( @@ -183,7 +184,9 @@ class Monitoring_ChartController extends Controller ); } $this->view->chart = new GridChart(); - $this->view->chart->setAxisLabel('', t('Hosts'))->setXAxis(new StaticAxis()); + $this->view->chart->setAxisLabel('', t('Hosts')) + ->setXAxis(new StaticAxis()) + ->setAxisMin(null, 0); $this->view->chart->drawBars( array( 'label' => t('Up'), diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index f0cdb2767..3e02a4757 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -94,7 +94,6 @@ class Monitoring_ListController extends Controller 'host_last_check', 'host_last_state_change' => $stateChangeColumn, 'host_notifications_enabled', - // 'host_unhandled_service_count', 'host_unhandled_services', 'host_action_url', 'host_notes_url', @@ -222,6 +221,7 @@ class Monitoring_ListController extends Controller 'author' => 'downtime_author', 'start' => 'downtime_start', 'scheduled_start' => 'downtime_scheduled_start', + 'scheduled_end' => 'downtime_scheduled_end', 'end' => 'downtime_end', 'duration' => 'downtime_duration', 'is_flexible' => 'downtime_is_flexible', @@ -229,7 +229,9 @@ class Monitoring_ListController extends Controller 'is_in_effect' => 'downtime_is_in_effect', 'entry_time' => 'downtime_entry_time', 'host' => 'downtime_host', - 'service' => 'downtime_service' + 'service' => 'downtime_service', + 'host_state' => 'downtime_host_state', + 'service_state' => 'downtime_service_state' ))->order('downtime_is_in_effect', 'DESC') ->order('downtime_scheduled_start', 'DESC'); diff --git a/modules/monitoring/application/controllers/MonitoringCommands.php b/modules/monitoring/application/controllers/MonitoringCommands.php index 0a94c17b2..faa04114e 100644 --- a/modules/monitoring/application/controllers/MonitoringCommands.php +++ b/modules/monitoring/application/controllers/MonitoringCommands.php @@ -64,7 +64,7 @@ class Zend_View_Helper_MonitoringCommands extends Zend_View_Helper_Abstract $out .= '
    '; - $out .= '
    '; + $out .= '
    '; if ($type === Meta::TYPE_FULL) { return '
    '. $out. '
    '; diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index 37ea0e061..e8bc18761 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -21,7 +21,6 @@ class Monitoring_MultiController extends Controller array( 'host_name', 'host_in_downtime', - 'host_unhandled_service_count', 'host_passive_checks_enabled', 'host_obsessing', 'host_state', diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index d7d14ce15..d503ba512 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -103,6 +103,40 @@ class Monitoring_ShowController extends Controller )); } + public function contactAction() + { + $contact = $this->getParam('contact'); + if (! $contact) { + throw new Zend_Controller_Action_Exception( + $this->translate('The parameter `contact\' is required'), + 404 + ); + } + $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', + )); + $query->where('contact_name', $contact); + $this->view->contacts = $query->paginate(); + $this->view->contact_name = $contact; + } + /** * Creating tabs for this controller * @return Tabs diff --git a/modules/monitoring/application/views/scripts/chart/hostgroup.phtml b/modules/monitoring/application/views/scripts/chart/hostgroup.phtml index 4ad5f61dc..1f98bb8db 100644 --- a/modules/monitoring/application/views/scripts/chart/hostgroup.phtml +++ b/modules/monitoring/application/views/scripts/chart/hostgroup.phtml @@ -1,5 +1,5 @@ -
    +
    render(); ?> diff --git a/modules/monitoring/application/views/scripts/chart/servicegroup.phtml b/modules/monitoring/application/views/scripts/chart/servicegroup.phtml index ad8a21ab0..1f98bb8db 100644 --- a/modules/monitoring/application/views/scripts/chart/servicegroup.phtml +++ b/modules/monitoring/application/views/scripts/chart/servicegroup.phtml @@ -1,5 +1,5 @@ -
    +
    render(); ?> diff --git a/modules/monitoring/application/views/scripts/list/comments.phtml b/modules/monitoring/application/views/scripts/list/comments.phtml index 3ed671fbe..645c35fb6 100644 --- a/modules/monitoring/application/views/scripts/list/comments.phtml +++ b/modules/monitoring/application/views/scripts/list/comments.phtml @@ -1,98 +1,114 @@ +getHelper('CommandForm'); + +?> + +compact): ?>
    -tabs ?> -
    -sortControl->render($this); ?> -
    -paginationControl($comments, null, null, array('preserve' => $this->preserve)); ?> + tabs->render($this); ?> +
    + translate('Sort by'); ?> sortControl->render($this); ?> +
    + widget('limiter', array('url' => $this->url, 'max' => $comments->count())); ?> + paginationControl($comments, null, null, array('preserve' => $this->preserve)); ?>
    +
    - - - + translate('No comments matching the filter') ?> + + -$cf = $this->getHelper('CommandForm'); - -if (count($comments) === 0) { - echo t('No comments matching the filter'); -} - -foreach ($comments as $comment): - -?> - - + + + +
    + + + type) { + switch ($comment->type) { case 'flapping': - $icon = 'flapping'; - $tooltip = 'Comment was caused by a flapping host or service.'; - break; + $icon = 'flapping'; + $title = $this->translate('Flapping'); + $tooltip = $this->translate('Comment was caused by a flapping host or service.'); + break; case 'comment': - $icon = 'user'; - $tooltip = 'Comment was created by an user.'; - break; + $icon = 'user'; + $title = $this->translate('User Comment'); + $tooltip = $this->translate('Comment was created by an user.'); + break; case 'downtime': - $icon = 'down'; - $tooltip = 'Comment was caused by a downtime.'; + $icon = 'down'; + $title = $this->translate('Downtime'); + $tooltip = $this->translate('Comment was caused by a downtime.'); case 'ack': - $icon = 'acknowledgement'; - $tooltip = 'Comment was caused by an acknowledgement.'; - } + $icon = 'acknowledgement'; + $title = $this->translate('Acknowledgement'); + $tooltip = $this->translate('Comment was caused by an acknowledgement.'); + } ?> - icon($icon . '.png', $tooltip) ?>
    - timeSince($comment->timestamp) ?> - - + + + $comment->id, + 'host' => $comment->host + ); + if ($comment->objecttype === 'service') { + $data['service'] = $comment->service; + } ?> - - - - - -
    - objecttype === 'service'): ?>icon('service.png', 'Service comment') ?> qlink( - $comment->service, - 'monitoring/show/service', - array( +
    + icon($icon . '.png', $tooltip) ?> +
    + escape($title); ?> +
    + prefixedTimeSince($comment->timestamp); ?> +
    + objecttype === 'service'): ?> + icon('service.png'); ?> + service; ?> + + + translate('on') . ' ' . $comment->host; ?> + + + icon('host.png'); ?> + host; ?> + + +
    + icon('comment.png'); ?> author) + ? '[' . $comment->author . '] ' + : ''; + ?>escape($comment->comment); ?> +
    + persistent + ? $this->translate('This comment is persistent.') + : $this->translate('This comment is not persistent.'); + ?> +
    + 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.'); ?> +
    - $comment->id, - 'host' => $comment->host - ); - - if ($comment->objecttype === 'service') { - $data['service'] = $comment->service; - } - - // echo $cf->iconSubmitForm( - // 'img/icons/remove.png', - echo $cf->labelSubmitForm( - 'X', - 'Remove comment', - 'link-like', - 'removecomment', - $data - ); - -?> -
    - - +
    + labelSubmitForm( + 'X', + $this->translate('Remove Comment'), + 'link-like', + 'removecomment', + $data + ); ?> +
    +
    \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/list/contactgroups.phtml b/modules/monitoring/application/views/scripts/list/contactgroups.phtml index 98661f4e6..c110bf011 100644 --- a/modules/monitoring/application/views/scripts/list/contactgroups.phtml +++ b/modules/monitoring/application/views/scripts/list/contactgroups.phtml @@ -22,7 +22,7 @@ foreach ($groupData as $groupName => $groupInfo): ?>
    escape($c->contact_alias) ?> diff --git a/modules/monitoring/application/views/scripts/list/contacts.phtml b/modules/monitoring/application/views/scripts/list/contacts.phtml index 642fd7b57..9fc05de8e 100644 --- a/modules/monitoring/application/views/scripts/list/contacts.phtml +++ b/modules/monitoring/application/views/scripts/list/contacts.phtml @@ -14,31 +14,41 @@ $contactHelper = $this->getHelper('ContactFlags'); href('monitoring/show/contacts', array('contact' => $contact->contact_name)); ?> -
    - + foreach ($contacts as $contact): ?> +
    + contact_name ?> (contact_alias ?>) - %1$s' => $contact->contact_email, - 'Pager: %s' => $contact->contact_pager, - 'Service notification period: %s' => $contact->contact_notify_service_timeperiod, - 'Host notification period: %s' => $contact->contact_notify_host_timeperiod - ) as $format => $value): - if ($value): ?> -
    - -
    +
    %2$s', + t('Email'), + $this->escape($contact->contact_email) + ) ?>
    + contact_pager): ?> +
    + : + escape($contact->contact_pager) ?> +
    + +
    +
    +
    + : + escape($contact->contact_notify_service_timeperiod) ?> +
    +
    + : + escape($contact->contact_notify_host_timeperiod) ?> +
    +
    - + +
    +
    diff --git a/modules/monitoring/application/views/scripts/list/downtimes.phtml b/modules/monitoring/application/views/scripts/list/downtimes.phtml index 995a66001..babb76074 100644 --- a/modules/monitoring/application/views/scripts/list/downtimes.phtml +++ b/modules/monitoring/application/views/scripts/list/downtimes.phtml @@ -1,70 +1,121 @@ getHelper('CommandForm'); ?> -
    -tabs ?> -
    -sortControl->render($this); ?> -
    -paginationControl($downtimes, null, null, array('preserve' => $this->preserve)); ?> -
    -
    - - -downtimes as $downtime): ?> - - - - - - -
    - dateFormat()->formatDateTime($downtime->start); ?> - - dateFormat()->formatDateTime($downtime->end); ?> -
    - Duration: util()->showHourMin($downtime->duration); ?> -
    - The is_flexible): ?>flexiblefixed downtime is is_in_effect): ?>not in effect -
    - service)): ?> - service ?> - on host ?> - - host ?> - -
    - author ?>: comment ?> -
    - Entry Time: entry_time) ? $this->dateFormat()->formatDateTime((int) $downtime->entry_time) : ''; ?> - -
    - $downtime->id, - 'host' => $downtime->host - ); - if (isset($downtime->service)) { - $data['service'] = $downtime->service; - } - // echo $helper->iconSubmitForm( - // 'img/icons/remove.png', - echo $helper->labelSubmitForm( - 'X', - 'Remove Downtime', - 'link-like', - 'removedowntime', - $data - ); - ?> -
    +compact): ?> +
    + tabs->render($this); ?> +
    + translate('Sort by'); ?> sortControl->render($this); ?> +
    + widget('limiter', array('url' => $this->url, 'max' => $downtimes->count())); ?> + paginationControl($downtimes, null, null, array('preserve' => $this->preserve)); ?> +
    + + +
    + + translate('No downtimes matching the filter'); ?> +
    + + + + + + service)) { + $stateName = strtolower($this->util()->getServiceStateName($downtime->service_state)); + } else { + $stateName = strtolower($this->util()->getHostStateName($downtime->host_state)); + } + ?> + + + + $downtime->id, + 'host' => $downtime->host + ); + if (isset($downtime->service)) { + $data['service'] = $downtime->service; + } + ?> + + + + +
    + is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?> +
    + prefixedTimeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start); ?> +
    + service)): ?> + + service; ?> + + + translate('on'); ?> host; ?> + + + + host; ?> + + +
    + icon('comment.png'); ?> [author; ?>] 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) + ); ?> + + 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) + ); ?> + + + 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) + ); ?> + + 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) + ); ?> + + + +
    + labelSubmitForm( + 'X', + 'Remove Downtime', + 'link-like', + 'removedowntime', + $data + ); ?> +
    diff --git a/modules/monitoring/application/views/scripts/list/eventhistory.phtml b/modules/monitoring/application/views/scripts/list/eventhistory.phtml index 55257bae6..56c2198e9 100644 --- a/modules/monitoring/application/views/scripts/list/eventhistory.phtml +++ b/modules/monitoring/application/views/scripts/list/eventhistory.phtml @@ -1,122 +1,115 @@ - -compact): ?> -
    - tabs->render($this); ?> -
    - translate('Sort by') ?> sortControl->render($this); ?> -
    - paginationControl($history, null, null, array('preserve' => $this->preserve)); ?> -
    - +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)); ?> +
    +
    -translate('No entries found') ?> + translate('No history events matching the filter') ?>
    - - - - - type) { - case 'notify': - $icon = 'notification'; - $title = 'Notification'; - $msg = $event->output; - break; - case 'comment': - $icon = 'comment'; - $title = 'Comment'; - $msg = $event->output; - break; - case 'ack': - $icon = 'acknowledgement'; - $title = 'Acknowledgement'; - $msg = $event->output; - break; - case 'dt_comment': - $icon = 'in-downtime'; - $title = 'In Downtime'; - $msg = $event->output; - break; - case 'flapping': - $icon = 'flapping'; - $title = 'Flapping'; - $msg = $event->output; - break; - case 'hard_state': - $icon = '{{HARDSTATE_ICON}}'; - $title = 'Hard State'; - $msg = $event->output . '
    Attempt ' . $event->attempt . '/' . $event->max_attempts . ' (Hard)'; - $class = 'border-status-' . ( - $isService ? - strtolower($this->util()->getServiceStateName($event->state)) : - strtolower($this->util()->getHostStateName($event->state)) - ); - break; - case 'soft_state': - $icon = '{{SOFTSTATE_ICON}}'; - $title = 'Soft State'; - $msg = $event->output . '
    Attempt ' . $event->attempt . '/' . $event->max_attempts . ' (Soft)'; - $class = 'border-status-' . ( - $isService ? - strtolower($this->util()->getServiceStateName($event->state)) : - strtolower($this->util()->getHostStateName($event->state)) - ); - break; - case 'dt_start': - $icon = 'downtime-start'; - $title = 'Downtime Start'; - $msg = $event->output; - break; - case 'dt_end': - $icon = 'downtime-end'; - $title = 'Downtime End'; - $msg = $event->output; - break; - } - ?> - - - +
    "> - timestamp); ?> - - service)): ?> - - service ?> - - - on host ?> - - - - - host ?> - - -
    -
    - - -
    -
    + + + service); + switch ($event->type) { + case 'notify': + $icon = 'notification'; + $title = $this->translate('Notification'); + $msg = $event->output; + break; + case 'comment': + $icon = 'comment'; + $title = $this->translate('Comment'); + $msg = $event->output; + break; + case 'ack': + $icon = 'acknowledgement'; + $title = $this->translate('Acknowledgement'); + $msg = $event->output; + break; + case 'dt_comment': + $icon = 'in_downtime'; + $title = $this->translate('In Downtime'); + $msg = $event->output; + break; + case 'flapping': + $icon = 'flapping'; + $title = $this->translate('Flapping'); + $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! + break; + case 'soft_state': + $icon = 'softstate'; + $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! + break; + case 'dt_start': + $icon = 'downtime_start'; + $title = $this->translate('Downtime Start'); + $msg = $event->output; + break; + case 'dt_end': + $icon = 'downtime_end'; + $title = $this->translate('Downtime End'); + $msg = $event->output; + break; + } + ?> + + + - - -
    + escape($title); ?> +
    + timestamp); ?> +
    + + + service; ?> + + + translate('on') . ' ' . $event->host; ?> + + + + host; ?> + + +
    +
    + icon($icon . '.png', $title); ?> +
    +
    - + + +
    - diff --git a/modules/monitoring/application/views/scripts/process/info.phtml b/modules/monitoring/application/views/scripts/process/info.phtml index 350966c49..8fb9bd0e3 100644 --- a/modules/monitoring/application/views/scripts/process/info.phtml +++ b/modules/monitoring/application/views/scripts/process/info.phtml @@ -10,28 +10,33 @@ $cf = $this->getHelper('CommandForm');

    Backend backendName; ?> -is_currently_running === '1' ? sprintf('has been running with PID %d ', $ps->process_id) . $this->prefixedTimeSince($ps->program_start_time) : 'is not running'; ?>. +is_currently_running === '1' + ? sprintf( + $this->translate('has been running with PID %d '), + $ps->process_id + ) . $this->prefixedTimeSince($ps->program_start_time) + : $this->translate('is not running'); ?>. - + - + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - +
    Last status updatetranslate('Last status update'); ?> timeSince($ps->status_update_time) ?> ago
    Last check commandtranslate('Last check command'); ?> timeSince($ps->last_command_check) ?> ago
    Global host event handlerglobal_host_event_handler ? $ps->global_host_event_handler : 'Not set' ?>translate('Global host event handler'); ?>global_host_event_handler ? $ps->global_host_event_handler : $this->translate('Not set'); ?>
    Global service event handlerglobal_service_event_handler ? $ps->global_service_event_handler : 'Not set' ?>translate('Global service event handler'); ?>global_service_event_handler ? $ps->global_service_event_handler : $this->translate('Not set'); ?>
    Notifications enabledtranslate('Notifications enabled'); ?> toggleSubmitForm( '', $ps->notifications_enabled, @@ -41,15 +46,18 @@ $cf = $this->getHelper('CommandForm'); array('global' => '1') ) ?> notifications_enabled === '1'): ?> - Temporarily disable + + translate('Temporarily disable'); ?> + disable_notif_expire_time): ?> -Will be re-enabled in timeUntil($ps->disable_notif_expire_time) ?> +translate('Will be re-enabled in %s'), '' . $this->timeUntil($ps->disable_notif_expire_time) . ''); ?>
    Execute active service checkstranslate('Execute active service checks'); ?> toggleSubmitForm( '', $ps->active_service_checks_enabled, @@ -60,7 +68,7 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Accept passive service checkstranslate('Accept passive service checks'); ?> toggleSubmitForm( '', $ps->passive_service_checks_enabled, @@ -71,7 +79,7 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Execute active host checkstranslate('Execute active host checks'); ?> toggleSubmitForm( '', $ps->active_host_checks_enabled, @@ -82,7 +90,7 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Accept passive host checkstranslate('Accept passive host checks'); ?> toggleSubmitForm( '', $ps->passive_host_checks_enabled, @@ -93,7 +101,7 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Eventhandlers enabledtranslate('Eventhandlers enabled'); ?> toggleSubmitForm( '', $ps->event_handlers_enabled, @@ -104,7 +112,7 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Obsessing over host checkstranslate('Obsessing over host checks'); ?> toggleSubmitForm( '', $ps->obsess_over_hosts, @@ -115,7 +123,7 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Obsessing over service checkstranslate('Obsessing over service checks'); ?> toggleSubmitForm( '', $ps->obsess_over_services, @@ -126,7 +134,7 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Flap detection enabledtranslate('Flap detection enabled'); ?> toggleSubmitForm( '', $ps->flap_detection_enabled, @@ -137,7 +145,7 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Process performance datatranslate('Process performance data'); ?> toggleSubmitForm( '', $ps->process_performance_data, @@ -148,17 +156,17 @@ Will be re-enabled in timeUntil($ps->disable_notif_expire_tim ) ?>
    Monitoring Processtranslate('Monitoring Process'); ?> labelSubmitForm( - 'Restart', - 'Restart the monitoring process', + $this->translate('Restart'), + $this->translate('Restart the monitoring process'), '', 'restartprocess' ) ?>