From 2b25757e20294f911b3fc2a8afd7a681bb779a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20Mo=C3=9Fhammer?= Date: Wed, 7 Aug 2013 17:44:18 +0200 Subject: [PATCH] Add documentation, add errorhandling and sizing The optional width/height parameter can be set in the ini to change the dashboard components size, documentation has been added and in the error case a message with the configuration is shown refs #4192 --- .../controllers/ConfigurationController.php | 1 - .../controllers/DashboardController.php | 67 ++++-- .../dashboard/show-configuration.phtml | 29 +++ config/dashboard/dashboard.ini | 11 - doc/dashboard.md | 43 ++++ doc/res/Dashboard.png | Bin 0 -> 9260 bytes library/Icinga/Util/Dimension.php | 29 ++- library/Icinga/Web/Widget/Dashboard.php | 197 +++++++++++++----- .../Icinga/Web/Widget/Dashboard/Component.php | 113 +++++++--- library/Icinga/Web/Widget/Dashboard/Pane.php | 111 +++++++++- .../controllers/ListController.php | 2 + 11 files changed, 473 insertions(+), 130 deletions(-) create mode 100644 application/views/scripts/dashboard/show-configuration.phtml create mode 100644 doc/dashboard.md create mode 100644 doc/res/Dashboard.png diff --git a/application/controllers/ConfigurationController.php b/application/controllers/ConfigurationController.php index e48932558..334d0a3e8 100644 --- a/application/controllers/ConfigurationController.php +++ b/application/controllers/ConfigurationController.php @@ -52,7 +52,6 @@ class ConfigurationController extends ActionController ); $tabBuilder->build(); - $this->view->tabs = $tabBuilder->getTabs(); } } diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php index b8ce87acd..315ef6f4f 100644 --- a/application/controllers/DashboardController.php +++ b/application/controllers/DashboardController.php @@ -6,11 +6,25 @@ use Icinga\Web\Url; use Icinga\Application\Icinga; use Icinga\Web\Widget\Dashboard; use Icinga\Application\Config as IcingaConfig; -use Icinga\Exception\ConfigurationError; use Icinga\Form\Dashboard\AddUrlForm; +use Icinga\Exception\ConfigurationError; + +/** + * Handle creation, removal and displaying of dashboards, panes and components + * + * @see Icinga\Web\Widget\Dashboard for more information about dashboards + */ class DashboardController extends ActionController { + + /** + * Retrieve a dashboard from the provided config + * + * @param string $config The config to read the dashboard from, or 'dashboard/dashboard' if none is given + * + * @return Dashboard + */ private function getDashboard($config = 'dashboard/dashboard') { $dashboard = new Dashboard(); @@ -18,6 +32,10 @@ class DashboardController extends ActionController return $dashboard; } + /** + * Remove a component from the pane identified by the 'pane' parameter + * + */ public function removecomponentAction() { $pane = $this->_getParam('pane'); @@ -34,14 +52,18 @@ class DashboardController extends ActionController } $this->redirectNow(Url::fromPath('dashboard', array('pane' => $pane))); } - + + + /** + * Display the form for adding new components or add the new component if submitted + * + */ public function addurlAction() { $form = new AddUrlForm(); $form->setRequest($this->_request); $this->view->form = $form; - if ($form->isSubmittedAndValid()) { $dashboard = $this->getDashboard(); $dashboard->setComponentUrl( @@ -49,23 +71,26 @@ class DashboardController extends ActionController $form->getValue('component'), ltrim($form->getValue('url'), '/') ); - $this->persistDashboard($dashboard); - $this->redirectNow( - Url::fromPath( - 'dashboard', - array( - 'pane' => $form->getValue('pane') - ) - ) - ); + try { + $dashboard->store(); + $this->redirectNow( + Url::fromPath('dashboard',array('pane' => $form->getValue('pane'))) + ); + } catch (ConfigurationError $exc) { + $this->_helper->viewRenderer('show_configuration'); + $this->view->exceptionMessage = $exc->getMessage(); + $this->view->iniConfigurationString = $dashboard->toIni(); + } } } - private function persistDashboard(Dashboard $dashboard) - { - $dashboard->store(); - } - + /** + * Display the dashboard with the pane set in the 'pane' request parameter + * + * If no pane is submitted or the submitted one doesn't exist, the default pane is + * displayed (normally the first one) + * + */ public function indexAction() { $dashboard = $this->getDashboard(); @@ -75,10 +100,10 @@ class DashboardController extends ActionController $dashboard->activate($dashboardName); } $this->view->tabs = $dashboard->getTabs(); - $this->view->tabs->add("Add", array( - "title" => "Add Url", - "iconCls" => "plus", - "url" => Url::fromPath("dashboard/addurl") + $this->view->tabs->add('Add', array( + 'title' => 'Add Url', + 'iconCls' => 'plus', + 'url' => Url::fromPath('dashboard/addurl') )); $this->view->dashboard = $dashboard; } diff --git a/application/views/scripts/dashboard/show-configuration.phtml b/application/views/scripts/dashboard/show-configuration.phtml new file mode 100644 index 000000000..4cec816bf --- /dev/null +++ b/application/views/scripts/dashboard/show-configuration.phtml @@ -0,0 +1,29 @@ +
+
+

Saving dashboard failed

+
+

+ Your dashboard couldn't be stored (error: "exceptionMessage ?>"). This could have one or more + of the following reasons: +

+ +
+ +

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

+

+

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

\ No newline at end of file diff --git a/config/dashboard/dashboard.ini b/config/dashboard/dashboard.ini index 15f8994be..e69de29bb 100644 --- a/config/dashboard/dashboard.ini +++ b/config/dashboard/dashboard.ini @@ -1,11 +0,0 @@ -[test] -title = "test" -[test.test] -url = "test" - -[test.test2] -url = "test2" - -[test.dsgdgs] -url = "dsdsgdsg" - diff --git a/doc/dashboard.md b/doc/dashboard.md new file mode 100644 index 000000000..61a57986d --- /dev/null +++ b/doc/dashboard.md @@ -0,0 +1,43 @@ +# The dashboard + +The icingaweb dashboard allows you to display different views on one page. You can create customized overviews over +the objects you're interested in and can add and remove elements. + +## Dashboard, Panes and Components + +![Dashboard structure][dashboards1] + +* The building blocks of dashboards are components - those represent a single URL and display it's content (often in + a more condensed layout) +* Different components can be added to a pane and will be shown their. All panes are shown as tabs on top of the dashboard, + whereas the title is used for the text in the tab +* The dashboard itself is just the view containing the panes + + +## Configuration files + +By default, the config/dashboard/dashboard.ini is used for storing dashboards in the following format: + + [PaneName] ; Define a new Pane + title = "PaneTitle" ; The title of the pane as displayed in the tabls + + [PaneName.Component1] ; Define a new component 'Component 1' underneat the pane + url = "/url/for/component1" ; the url that will be displayed, with view=compact as URL parameter appended + height = "500px" ; optional height setting + width = "400px" ; optional width setting + + [test.My hosts] ; Another component, here with host + url = "monitoring/list/hosts" ; the url of the component + ; Notice the missing height/width definition + + [test.My services] ; And another pane + url = "monitoring/list/services" ; With service url + + [test2] ; Define a second pane + title = "test2" ; with the title + + [test2.test] ; Add a component to the second pane + url = "/monitoring/show/host/host1" ; ...and define it's url + + +[dashboards1]: res/Dashboard.png \ No newline at end of file diff --git a/doc/res/Dashboard.png b/doc/res/Dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..e78d4513237222e344e8026e008065138f1a32bf GIT binary patch literal 9260 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 literal 0 HcmV?d00001 diff --git a/library/Icinga/Util/Dimension.php b/library/Icinga/Util/Dimension.php index f1371e46e..a08e68c32 100644 --- a/library/Icinga/Util/Dimension.php +++ b/library/Icinga/Util/Dimension.php @@ -48,7 +48,7 @@ class Dimension private $unit = self::UNIT_PX; /** - * Creates a new Dimension object with the given size and unit + * Create a new Dimension object with the given size and unit * * @param int $value The new value * @param string $unit The unit to use (default: px) @@ -71,7 +71,7 @@ class Dimension } /** - * Returns true when the value is > 0 + * Return true when the value is > 0 * * @return bool */ @@ -81,7 +81,7 @@ class Dimension } /** - * Returns the underlying value without unit information + * Return the underlying value without unit information * * @return int */ @@ -91,7 +91,17 @@ class Dimension } /** - * Returns this value with it's according unit as a string + * Return the unit used for the value + * + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * Return this value with it's according unit as a string * * @return string */ @@ -103,12 +113,19 @@ class Dimension return $this->value.$this->unit; } + /** + * Create a new Dimension object from a string containing the numeric value and the dimension (e.g. 200px, 20%) + * + * @param $string The string to parse + * + * @return Dimension + */ public static function fromString($string) { $matches = array(); if (!preg_match_all('/^ *([0-9]+)(px|pt|em|\%) */i', $string, $matches)) { - throw new InvalidArgumentException($string.' is not a valid dimension'); + return new Dimension(0); } - return new Dimension(intval($matches[1]), $matches[2]); + return new Dimension(intval($matches[1][0]), $matches[2][0]); } } \ No newline at end of file diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index 3217194fd..fee762c22 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -12,80 +12,121 @@ use Icinga\Web\Widget\Dashboard\Component as DashboardComponent; use Icinga\Web\Url; +/** + * Dashboards display multiple views on a single page + * + * The terminology is as follows: + * - Component: A single view showing a specific url + * - Pane: Aggregates one or more components on one page, displays it's title as a tab + * - Dashboard: Shows all panes + * + */ class Dashboard implements Widget { /** + * The configuration containing information about this dashboard + * * @var IcingaConfig; */ private $config; - private $configfile; + + /** + * An array containing all panes of this dashboard + * + * @var array + */ private $panes = array(); + + /** + * The @see Icinga\Web\Widget\Tabs object for displaying displayable panes + * + * @var Tabs + */ private $tabs; - private $url = null; + /** + * The parameter that will be added to identify panes + * + * @var string + */ private $tabParam = 'pane'; - - public function __construct() - { - if ($this->url === null) { - $this->url = Url::fromRequest()->getUrlWithout($this->tabParam); - } - } - + /** + * Set the given tab name as active. + * + * @param string $name The tab name to activate + * + */ public function activate($name) { $this->getTabs()->activate($name); } - + + /** + * Return the tab object used to navigate through this dashboard + * + * @return Tabs + */ public function getTabs() { + $url = Url::fromRequest()->getUrlWithout($this->tabParam); if ($this->tabs === null) { $this->tabs = new Tabs(); foreach ($this->panes as $key => $pane) { $this->tabs->add($key, array( 'title' => $pane->getTitle(), - 'url' => clone($this->url), + 'url' => clone($url), 'urlParams' => array($this->tabParam => $key) )); } } - return $this->tabs; } - public function isWritable() - { - return is_writable($this->configfile); - } - + /** + * Store the current dashboard with all it's panes and components to the given file (or the default one if none is + * given) + * + * + * @param string $file The filename to store this dashboard as an ini + * + * @return $this + * @throws \Icinga\Exception\ConfigurationError If persisting fails, details are written to the log + * + */ public function store($file = null) { if ($file === null) { $file = IcingaConfig::app('dashboard/dashboard')->getConfigFile(); } - $this->configfile = $file; - if (!$this->isWritable()) { - Logger::error("Tried to persist dashboard to %s, but path is not writeable", $this->configfile); + + if (!is_writable($file)) { + Logger::error('Tried to persist dashboard to %s, but path is not writeable', $file); throw new ConfigurationError('Can\'t persist dashboard'); } - if (! @file_put_contents($this->configfile, $this->toIni())) { + if (! @file_put_contents($file, $this->toIni())) { $error = error_get_last(); if ($error == NULL) { - $error = "Unknown error"; + $error = 'Unknown error'; } else { - $error = $error["message"]; + $error = $error['message']; } - Logger::error("Tried to persist dashboard to %s, but got error: %s", $this->configfile, $error); + Logger::error('Tried to persist dashboard to %s, but got error: %s', $file, $error); throw new ConfigurationError('Can\'t persist dashboard'); } else { return $this; } - } + /** + * Populate this dashboard via the given configuration file + * + * @param IcingaConfig $config The configuration file to populate this dashboard with + * + * @return $this + */ public function readConfig(IcingaConfig $config) { $this->config = $config; @@ -94,6 +135,11 @@ class Dashboard implements Widget return $this; } + /** + * Creates a new empty pane with the given title + * + * @param $title + */ public function createPane($title) { $pane = new Pane($title); @@ -102,12 +148,22 @@ class Dashboard implements Widget } + /** + * Update or adds a new component with the given url to a pane + * + * @TODO: Should only allow component objects to be added directly as soon as we store more information + * + * @param string $pane The pane to add the component to + * @param Component|string $component The component to add or the title of the newly created component + * @param $url The url to use for the component + * + * @return $this + */ public function setComponentUrl($pane, $component, $url) { if ($component === null && strpos($pane, '.')) { list($pane, $component) = preg_split('~\.~', $pane, 2); } - if (!isset($this->panes[$pane])) { $this->createPane($pane); } @@ -120,6 +176,13 @@ class Dashboard implements Widget return $this; } + /** + * Return true if a pane doesn't exist or doesn't have any components in it + * + * @param string $pane The name of the pane to check for emptyness + * + * @return bool + */ public function isEmptyPane($pane) { $paneObj = $this->getPane($pane); @@ -130,6 +193,15 @@ class Dashboard implements Widget return !empty($cmps); } + + /** + * Remove a component $component from the given pane + * + * @param string $pane The pane to remove the component from + * @param Component|string $component The component to remove or it's name + * + * @return $this + */ public function removeComponent($pane, $component) { if ($component === null && strpos($pane, '.')) { @@ -143,6 +215,11 @@ class Dashboard implements Widget return $this; } + /** + * Return an array with pane name=>title format used for comboboxes + * + * @return array + */ public function getPaneKeyTitleArray() { $list = array(); @@ -152,40 +229,49 @@ class Dashboard implements Widget return $list; } - public function getComponentEnum() - { - $list = array(); - foreach ($this->panes as $name => $pane) { - foreach ($pane->getComponents() as $component) { - $list[$name . '.' . $component->getTitle()] = - $pane->getTitle() . ': ' . $component->getTitle(); - } - } - return $list; - } - + /** + * Add a pane object to this dashboard + * + * @param Pane $pane The pane to add + * + * @return $this + */ public function addPane(Pane $pane) { $this->panes[$pane->getName()] = $pane; return $this; } + /** + * Return the pane with the provided name or null if it doesn't exit + * + * @param string $name The name of the pane to return + * + * @return null|Pane The pane or null if no pane with the given name exists + */ public function getPane($name) { if (!isset($this->panes[$name])) return null; return $this->panes[$name]; } - + + /** + * @see Icinga\Web\Widget::render + */ public function render(\Zend_View_Abstract $view) { if (empty($this->panes)) { return ''; } - - return $this->getActivePane()->render($view); + return $this->determineActivePane()->render($view); } + /** + * Activates the default pane of this dashboard and returns it's name + * + * @return mixed + */ private function setDefaultPane() { reset($this->panes); @@ -194,7 +280,12 @@ class Dashboard implements Widget return $active; } - public function getActivePane() + /** + * Determine the active pane either by the selected tab or the current request + * + * @return Pane The currently active pane + */ + public function determineActivePane() { $active = $this->getTabs()->getActiveName(); if (! $active) { @@ -211,6 +302,11 @@ class Dashboard implements Widget return $this->panes[$active]; } + /** + * Return the ini string describing this dashboard + * + * @return string + */ public function toIni() { $ini = ''; @@ -219,23 +315,24 @@ class Dashboard implements Widget } return $ini; } - - protected function loadConfigPanes() + + /** + * Load all config panes from @see Dashboard::$config + * + */ + private function loadConfigPanes() { $items = $this->config; - $app = Icinga::app(); foreach ($items->keys() as $key) { $item = $this->config->get($key, false); if (false === strstr($key, '.')) { $this->addPane(Pane::fromIni($key, $item)); - } else { - list($paneName, $title) = explode('.', $key , 2); + list($paneName, $title) = explode('.', $key, 2); $pane = $this->getPane($paneName); $pane->addComponent(DashboardComponent::fromIni($title, $item, $pane)); } } - - } } + diff --git a/library/Icinga/Web/Widget/Dashboard/Component.php b/library/Icinga/Web/Widget/Dashboard/Component.php index 3690e29f7..22aa99d14 100644 --- a/library/Icinga/Web/Widget/Dashboard/Component.php +++ b/library/Icinga/Web/Widget/Dashboard/Component.php @@ -10,21 +10,49 @@ use Zend_Config; /** * A dashboard pane component * - * Needs a title and an URL + * This is the element displaying a specific view in icinga2web * */ class Component implements Widget { + /** + * The url of this Component + * + * @var \Icinga\Web\Url + */ private $url; - private $title; - private $width; - private $height; /** + * The title being displayed on top of the component + * @var + */ + private $title; + + /** + * The width of the component, if set + * + * @var Dimension|null + */ + private $width = null; + + /** + * The height of the component, if set + * + * @var Dimension|null + */ + private $height = null; + + /** + * The pane containing this component, needed for the 'remove button' * @var Pane */ private $pane; + /** + * The template string used for rendering this widget + * + * @var string + */ private $template =<<<'EOD'

@@ -39,7 +67,13 @@ class Component implements Widget
EOD; - + /** + * Create a new component displaying the given url in the provided pane + * + * @param string $title The title to use for this component + * @param Url|string $url The url this component uses for displaying information + * @param Pane $pane The pane this Component will be added to + */ public function __construct($title, $url, Pane $pane) { $this->title = $title; @@ -51,18 +85,28 @@ EOD; } } + /** + * Set the with for this component or use the default width if null is provided + * + * @param Dimension|null $width The width to use or null to use the default width + */ public function setWidth(Dimension $width = null) { $this->width = $width; } + /** + * Set the with for this component or use the default height if null is provided + * + * @param Dimension|null $height The height to use or null to use the default height + */ public function setHeight(Dimension $height = null) { $this->height = $height; } /** - * Retrieve this components title + * Retrieve the components title * * @return string */ @@ -72,7 +116,7 @@ EOD; } /** - * Retrieve my url + * Retrieve the components url * * @return Url */ @@ -82,10 +126,11 @@ EOD; } /** - * Set this components URL + * Set the components URL * - * @param string|Url $url Component URL - * @return self + * @param string|Url $url The url to use, either as an Url object or as a path + * + * @return $this */ public function setUrl($url) { @@ -97,35 +142,29 @@ EOD; return $this; } - protected function iniPair($key, $val) - { - return sprintf( - "%s = %s\n", - $key, - $this->quoteIni($val) - ); - } - - protected function quoteIni($str) - { - return '"' . $str . '"'; - } - + /** + * Return this component in a suitable format and encoding for ini files + * + * @return string + */ public function toIni() { - $ini = $this->iniPair('url', $this->url->getRelativeUrl()); + $ini = 'url = "'.$this->url->getRelativeUrl().'"'.PHP_EOL; foreach ($this->url->getParams() as $key => $val) { - $ini .= $this->iniPair($key, $val); + $ini .= $key.' = "'.$val.'"'.PHP_EOL; } if ($this->height !== null) { - $ini .= 'height: '.((string) $this->height).'\n'; + $ini .= 'height = "'.((string) $this->height).'"'.PHP_EOL; } if ($this->width !== null) { - $ini .= 'width: '.((string) $this->width).'\n'; + $ini .= 'width = "'.((string) $this->width).'"'.PHP_EOL; } return $ini; } + /** + * @see Widget::render() + */ public function render(\Zend_View_Abstract $view) { $url = clone($this->url); @@ -141,14 +180,18 @@ EOD; ) ); - $html = str_replace("{URL}", $url->getAbsoluteUrl(), $this->template); $html = str_replace("{REMOVE_URL}", $removeUrl, $html); - $html = str_replace("{STYLE}", $this->getBoxSizeAsCSS(), $html); + $html = str_replace("{DIMENSION}", $this->getBoxSizeAsCSS(), $html); $html = str_replace("{TITLE}", $view->escape($this->getTitle()), $html); return $html; } + /** + * Return the height and width deifnition (if given) in CSS format + * + * @return string + */ private function getBoxSizeAsCSS() { $style = ""; @@ -158,8 +201,18 @@ EOD; if ($this->width) { $style .= 'width:'.(string) $this->width.';'; } + return $style; } + /** + * Create a @see Component instance from the given Zend config, using the provided title + * + * @param $title The title for this component + * @param Zend_Config $config The configuration defining url, parameters, height, width, etc. + * @param Pane $pane The pane this component belongs to + * + * @return Component A newly created Component for use in the Dashboard + */ public static function fromIni($title, Zend_Config $config, Pane $pane) { $height = null; diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php index 6749da12a..43087cdc5 100644 --- a/library/Icinga/Web/Widget/Dashboard/Pane.php +++ b/library/Icinga/Web/Widget/Dashboard/Pane.php @@ -7,39 +7,97 @@ use Icinga\Exception\ConfigurationError; use Icinga\Web\Widget\Widget; use Zend_Config; +/** + * A pane, displaying different Dashboard components + * + */ class Pane implements Widget { - protected $name; - protected $title; - protected $components = array(); + /** + * The name of this pane, as defined in the ini file + * + * @var string + */ + private $name; + /** + * The title of this pane, as displayed in the dashboard tabs + * @TODO: Currently the same as $name, evaluate if distinguishing is needed + * + * @var string + */ + private $title; + + /** + * An array of @see Components that are displayed in this pane + * + * @var array + */ + private $components = array(); + + /** + * Create a new pane + * + * @param $name The pane to create + */ public function __construct($name) { $this->name = $name; $this->title = $name; } + /** + * Returns the name of this pane + * + * @return string + */ public function getName() { return $this->name; } + /** + * Returns the title of this pane + * + * @return string + */ public function getTitle() { return $this->title; } + /** + * Overwrite the title of this pane + * + * @param string $title The new title to use for this pane + * @return Pane $this + */ public function setTitle($title) { $this->title = $title; return $this; } + /** + * Return true if a component with the given title exists in this pane + * + * @param string $title The title of the component to check for existence + * + * @return bool + */ public function hasComponent($title) { return array_key_exists($title, $this->components); } + /** + * Return a component with the given name if existing + * + * @param string $title The title of the component to return + * + * @return Component The component with the given title + * @throws ProgrammingError If the component doesn't exist + */ public function getComponent($title) { if ($this->hasComponent($title)) { @@ -51,6 +109,12 @@ class Pane implements Widget )); } + /** + * Removes the component with the given title if it exists in this pane + * + * @param string $title The pane + * @return Pane $this + */ public function removeComponent($title) { if ($this->hasComponent($title)) { @@ -59,11 +123,19 @@ class Pane implements Widget return $this; } + /** + * Return all components added at this pane + * + * @return array + */ public function getComponents() { return $this->components; } + /** + * @see Widget::render + */ public function render(\Zend_View_Abstract $view) { $html = PHP_EOL; @@ -73,6 +145,15 @@ class Pane implements Widget return $html; } + /** + * Add a component to this pane, optionally creating it if $component is a string + * + * @param string|Component $component The component object or title (if a new component will be created) + * @param string|null $url An Url to be used when component is a string + * + * @return Pane $this + * @throws \Icinga\Exception\ConfigurationError + */ public function addComponent($component, $url = null) { if ($component instanceof Component) { @@ -80,24 +161,24 @@ class Pane implements Widget } elseif (is_string($component) && $url !== null) { $this->components[$component] = new Component($component, $url, $this); } else{ - throw new ConfigurationError('You messed up your dashboard'); + throw new ConfigurationError('Invalid component added: '.$component); } return $this; } - protected function quoteIni($str) - { - return '"' . $str . '"'; - } - + /** + * Return the ini representation of this pane as a string + * + * @return string + */ public function toIni() { if (empty($this->components)) { - return ""; + return ''; } $ini = '['.$this->getName().']'.PHP_EOL. - 'title = '.$this->quoteIni($this->getTitle()).PHP_EOL; + 'title = "'.$this->getTitle().'"'.PHP_EOL; foreach ($this->components as $title => $component) { // component header @@ -108,6 +189,14 @@ class Pane implements Widget return $ini; } + /** + * Create a new pane with the title $title from the given configuration + * + * @param $title The title for this pane + * @param Zend_Config $config The configuration to use for setup + * + * @return Pane + */ public static function fromIni($title, Zend_Config $config) { $pane = new Pane($title); diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index 46d5cd6ac..38d53bf3f 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -60,6 +60,8 @@ class Monitoring_ListController extends ModuleActionController $state_column = 'service_hard_state'; $state_change_column = 'service_last_hard_state_change'; } + $this->compactView = "services-compact"; + $this->view->services = $this->query('status', array( 'host_name',