diff --git a/application/controllers/AuthenticationController.php b/application/controllers/AuthenticationController.php index e0a3e6b8d..d500f23e6 100644 --- a/application/controllers/AuthenticationController.php +++ b/application/controllers/AuthenticationController.php @@ -84,6 +84,8 @@ class AuthenticationController extends ActionController } } + + /** * Action handle logout */ diff --git a/application/controllers/ConfigurationController.php b/application/controllers/ConfigurationController.php index ae895695c..334d0a3e8 100644 --- a/application/controllers/ConfigurationController.php +++ b/application/controllers/ConfigurationController.php @@ -28,7 +28,7 @@ use Icinga\Application\Benchmark; use Icinga\Authentication\Manager; use Icinga\Web\ActionController; -use Icinga\Web\Hook\Configuration\ConfigurationTab; +use Icinga\Web\Widget\Tabs; use Icinga\Web\Hook\Configuration\ConfigurationTabBuilder; /** @@ -48,11 +48,10 @@ class ConfigurationController extends ActionController public function indexAction() { $tabBuilder = new ConfigurationTabBuilder( - $this->widget('tabs') + new Tabs() ); $tabBuilder->build(); - $this->view->tabs = $tabBuilder->getTabs(); } } diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php new file mode 100644 index 000000000..2182fb793 --- /dev/null +++ b/application/controllers/DashboardController.php @@ -0,0 +1,141 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + */ +// {{{ICINGA_LICENSE_HEADER}}} + +use Icinga\Web\ActionController; +use Icinga\Web\Url; +use Icinga\Application\Icinga; +use Icinga\Web\Widget\Dashboard; +use Icinga\Config\Config as IcingaConfig; +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(); + $dashboard->readConfig(IcingaConfig::app($config)); + return $dashboard; + } + + /** + * Remove a component from the pane identified by the 'pane' parameter + * + */ + public function removecomponentAction() + { + $pane = $this->_getParam('pane'); + $dashboard = $this->getDashboard(); + try { + $dashboard->removeComponent( + $pane, + $this->_getParam('component') + )->store(); + $this->redirectNow(Url::fromPath('dashboard', array('pane' => $pane))); + } catch (ConfigurationError $exc ) { + + $this->_helper->viewRenderer('show_configuration'); + $this->view->exceptionMessage = $exc->getMessage(); + $this->view->iniConfigurationString = $dashboard->toIni(); + } + } + + + /** + * 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( + $form->getValue('pane'), + $form->getValue('component'), + ltrim($form->getValue('url'), '/') + ); + 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(); + } + } + } + + /** + * 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(); + + if ($this->_getParam('pane')) { + $pane = $this->_getParam('pane'); + $dashboard->activate($pane); + } + $this->view->tabs = $dashboard->getTabs(); + $this->view->tabs->add( + 'Add', + array( + 'title' => 'Add Url', + 'iconCls' => 'plus', + 'url' => Url::fromPath('dashboard/addurl') + ) + ); + $this->view->dashboard = $dashboard; + } +} + +// @codingStandardsIgnoreEnd \ No newline at end of file diff --git a/application/controllers/ModulesController.php b/application/controllers/ModulesController.php index 9fb215978..fc5fdfdaf 100644 --- a/application/controllers/ModulesController.php +++ b/application/controllers/ModulesController.php @@ -33,8 +33,7 @@ use Icinga\Web\ActionController; use Icinga\Application\Icinga; use Icinga\Web\Hook\Configuration\ConfigurationTabBuilder; -use Icinga\Application\Modules\Manager as ModuleManager; -use Zend_Controller_Action as ZfActionController; +use Icinga\Web\Widget\Tabs; /** * Handle module depending frontend actions @@ -60,9 +59,7 @@ class ModulesController extends ActionController */ public function indexAction() { - $tabBuilder = new ConfigurationTabBuilder( - $this->widget('tabs') - ); + $tabBuilder = new ConfigurationTabBuilder(new Tabs()); $tabBuilder->build(); diff --git a/application/controllers/StaticController.php b/application/controllers/StaticController.php index 7e1ebb5ae..bb86e60ef 100644 --- a/application/controllers/StaticController.php +++ b/application/controllers/StaticController.php @@ -72,8 +72,9 @@ class StaticController extends ActionController public function imgAction() { - $module = $this->_getParam('moduleName'); + $module = $this->_getParam('module_name'); $file = $this->_getParam('file'); + $basedir = Icinga::app()->getModuleManager()->getModule($module)->getBaseDir(); $filePath = $basedir . '/public/img/' . $file; @@ -102,7 +103,7 @@ class StaticController extends ActionController ) . ' GMT'); readfile($filePath); - $this->_viewRenderer->setNoRender(); + return; } public function javascriptAction() @@ -110,17 +111,16 @@ class StaticController extends ActionController $module = $this->_getParam('module_name'); $file = $this->_getParam('file'); + if (!Icinga::app()->getModuleManager()->hasEnabled($module)) { + echo "/** Module not enabled **/"; + return; + } $basedir = Icinga::app()->getModuleManager()->getModule($module)->getBaseDir(); $filePath = $basedir . '/public/js/' . $file; if (!file_exists($filePath)) { - throw new ActionException( - sprintf( - '%s does not exist', - $filePath - ), - 404 - ); + echo "/** Module has no js files **/"; + return; } $hash = md5_file($filePath); $response = $this->getResponse(); @@ -143,7 +143,8 @@ class StaticController extends ActionController } else { readfile($filePath); } - $this->_viewRenderer->setNoRender(); + + return; } } diff --git a/application/forms/Dashboard/AddUrlForm.php b/application/forms/Dashboard/AddUrlForm.php new file mode 100644 index 000000000..571891ac7 --- /dev/null +++ b/application/forms/Dashboard/AddUrlForm.php @@ -0,0 +1,136 @@ + 'Dashboard', + 'required' => true, + 'style' => 'display:inline-block', + 'multiOptions' => $dashboard->getPaneKeyTitleArray() + ) + ); + + $newDashboardBtn = new Zend_Form_Element_Submit( + 'create_new_pane', + array( + 'label' => '+', + 'required' => false, + 'style' => 'display:inline-block' + ) + ); + + $newDashboardBtn->removeDecorator('DtDdWrapper'); + $selectPane->removeDecorator('DtDdWrapper'); + $selectPane->removeDecorator('htmlTag'); + + + $this->addElement($selectPane); + $this->addElement($newDashboardBtn); + $this->enableAutoSubmit(array('create_new_pane')); + } + + /** + * Add a textfield for creating a new pane to this form + * + */ + private function addNewPaneTextField() + { + $txtCreatePane = new Zend_Form_Element_Text( + 'pane', + array( + 'label' => 'New dashboard title', + 'required' => true, + 'style' => 'display:inline-block' + ) + ); + + // Marks this field as a new pane (and prevents the checkbox being displayed when validation errors occur) + $markAsNewPane = new Zend_Form_Element_Hidden( + 'create_new_pane', + array( + 'required' => true, + 'value' => 1 + ) + ); + + $cancelDashboardBtn = new Zend_Form_Element_Submit( + 'use_existing_dashboard', + array( + 'label' => 'X', + 'required' => false, + 'style' => 'display:inline-block' + ) + ); + + $cancelDashboardBtn->removeDecorator('DtDdWrapper'); + $txtCreatePane->removeDecorator('DtDdWrapper'); + $txtCreatePane->removeDecorator('htmlTag'); + + $this->addElement($txtCreatePane); + $this->addElement($cancelDashboardBtn); + $this->addElement($markAsNewPane); + } + + /** + * Add elements to this form (used by extending classes) + * + */ + protected function create() + { + $dashboard = new Dashboard(); + $dashboard->readConfig(IcingaConfig::app('dashboard/dashboard')); + $this->addElement( + 'text', + 'url', + array( + 'label' => 'Url', + 'required' => true, + ) + ); + $elems = $dashboard->getPaneKeyTitleArray(); + + if (empty($elems) || // show textfield instead of combobox when no pane is available + ($this->getRequest()->getPost('create_new_pane', '0') && // or when a new pane should be created (+ button) + !$this->getRequest()->getPost('use_existing_dashboard', '0')) // and the user didn't click the 'use existing' button + ) { + $this->addNewPaneTextField(); + } else { + $this->addPaneSelectionBox($dashboard); + } + + $this->addElement( + 'text', + 'component', + array( + 'label' => 'Title', + 'required' => true, + ) + ); + $this->setSubmitLabel("Add to dashboard"); + } +} diff --git a/application/views/scripts/configuration/index.phtml b/application/views/scripts/configuration/index.phtml index 75703c7c9..e6445410a 100644 --- a/application/views/scripts/configuration/index.phtml +++ b/application/views/scripts/configuration/index.phtml @@ -1,4 +1,5 @@ -tabs; ?> +tabs->render($this); ?> +

Configuration

diff --git a/application/views/scripts/dashboard/addurl.phtml b/application/views/scripts/dashboard/addurl.phtml new file mode 100644 index 000000000..c8653ae32 --- /dev/null +++ b/application/views/scripts/dashboard/addurl.phtml @@ -0,0 +1,3 @@ +

Add dashboard URL

+ +form->render($this); ?> \ No newline at end of file diff --git a/application/views/scripts/dashboard/index.phtml b/application/views/scripts/dashboard/index.phtml new file mode 100644 index 000000000..6b1e5c9bb --- /dev/null +++ b/application/views/scripts/dashboard/index.phtml @@ -0,0 +1,3 @@ +tabs->render($this); ?> + +dashboard->render($this); ?> \ No newline at end of file diff --git a/application/views/scripts/dashboard/show-configuration.phtml b/application/views/scripts/dashboard/show-configuration.phtml new file mode 100644 index 000000000..d7421e5d1 --- /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/application/views/scripts/modules/overview.phtml b/application/views/scripts/modules/overview.phtml index 29e80a94f..5978530e5 100644 --- a/application/views/scripts/modules/overview.phtml +++ b/application/views/scripts/modules/overview.phtml @@ -3,7 +3,7 @@ $this->modules->limit(10); $modules = $this->modules->paginate(); ?> -tabs; ?> +tabs->render($this); ?>

Installed Modules

paginationControl($modules, null, null, array( 'preserve' => $this->preserve diff --git a/config/dashboard/dashboard.ini b/config/dashboard/dashboard.ini new file mode 100644 index 000000000..e69de29bb diff --git a/config/menu.ini b/config/menu.ini index 21e230bcc..b4e42e7b5 100755 --- a/config/menu.ini +++ b/config/menu.ini @@ -1,3 +1,4 @@ [menu] +Dashboard = "/dashboard/index" Configuration = "/configuration/index" diff --git a/doc/dashboard.md b/doc/dashboard.md new file mode 100644 index 000000000..e62bdce37 --- /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 there. 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/form.md b/doc/form.md index 0150ad431..befbdf0d7 100644 --- a/doc/form.md +++ b/doc/form.md @@ -41,6 +41,10 @@ some fields in the form) the form is repopulated but not validated at this time. in this case, but no errors are added to the created form. +In order to be able to use isSubmittedAndValid, you have to define a submitbutton in the form. +This is done with the *setSubmitLabel(string)* function, with the first parameter being the +label set to the submit button. + #### Pre validation To handle dependend fields you can just override *preValid()* or *postValid()* diff --git a/doc/res/Dashboard.graphml b/doc/res/Dashboard.graphml new file mode 100644 index 000000000..eed85e46d --- /dev/null +++ b/doc/res/Dashboard.graphml @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Panes + + + + + + + + + + + + + + + + + + Dashboard + + + + + + + + + + + + + + + + + + Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/res/Dashboard.png b/doc/res/Dashboard.png new file mode 100644 index 000000000..e78d45132 Binary files /dev/null and b/doc/res/Dashboard.png differ diff --git a/library/Icinga/Config/Config.php b/library/Icinga/Config/Config.php index 74dc64be9..a9f80c12d 100755 --- a/library/Icinga/Config/Config.php +++ b/library/Icinga/Config/Config.php @@ -45,7 +45,7 @@ class Config extends Zend_Config_Ini * The INI file this configuration has been loaded from * @var string */ - protected $configFile; + private $configFile; /** * Application config instances per file @@ -135,4 +135,11 @@ class Config extends Zend_Config_Ini return array_keys($this->$name->toArray()); } } + + public function getConfigFile() + { + return $this->configFile; + } + + } diff --git a/library/Icinga/Util/Dimension.php b/library/Icinga/Util/Dimension.php new file mode 100644 index 000000000..1ca0a5150 --- /dev/null +++ b/library/Icinga/Util/Dimension.php @@ -0,0 +1,148 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + */ +// {{{ICINGA_LICENSE_HEADER}}} + +namespace Icinga\Util; + +class Dimension +{ + /** + * Defines this dimension as nr of pixels + */ + const UNIT_PX = "px"; + + /** + * Defines this dimension as width of 'M' in current font + */ + const UNIT_EM = "em"; + + /** + * Defines this dimension as a percentage value + */ + const UNIT_PERCENT = "%"; + + /** + * Defines this dimension in points + */ + const UNIT_PT = "pt"; + + /** + * The current set value for this dimension + * + * @var int + */ + private $value = 0; + + /** + * The unit to interpret the value with + * + * @var string + */ + private $unit = self::UNIT_PX; + + /** + * 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) + */ + public function __construct($value, $unit = self::UNIT_PX) + { + $this->setValue($value, $unit); + } + + /** + * Change the value and unit of this dimension + * + * @param int $value The new value + * @param string $unit The unit to use (default: px) + */ + public function setValue($value, $unit = self::UNIT_PX) + { + $this->value = intval($value); + $this->unit = $unit; + } + + /** + * Return true when the value is > 0 + * + * @return bool + */ + public function isDefined() + { + return $this->value > 0; + } + + /** + * Return the underlying value without unit information + * + * @return int + */ + public function getValue() + { + return $this->value; + } + + /** + * 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 + */ + public function __toString() + { + if (!$this->isDefined()) { + return ""; + } + 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)) { + return new Dimension(0); + } + return new Dimension(intval($matches[1][0]), $matches[2][0]); + } +} diff --git a/library/Icinga/Web/ActionController.php b/library/Icinga/Web/ActionController.php index 7d989373e..0b24384da 100755 --- a/library/Icinga/Web/ActionController.php +++ b/library/Icinga/Web/ActionController.php @@ -153,19 +153,6 @@ class ActionController extends ZfController return t($string); } - /** - * Helper function creating a new widget - * - * @param string $name The widget name - * @param string $properties Optional widget properties - * - * @return Widget\AbstractWidget - */ - public function widget($name, $properties = array()) - { - return Widget::create($name, $properties); - } - /** * Whether the current user has the given permission * @@ -250,7 +237,7 @@ class ActionController extends ZfController public function redirectNow($url, array $params = array()) { if ($url instanceof Url) { - $url = $url->getRelative(); + $url = $url->getRelativeUrl(); } $this->_helper->Redirector->gotoUrlAndExit($url); } diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 901910352..253bde3a7 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -121,6 +121,16 @@ class Url return $this; } + /** + * Return the baseUrl set for this Url + * + * @return string + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + /** * Set the relative path of this url, without query parameters * @@ -302,6 +312,8 @@ class Url return $url; } + + /** * Alias for @see Url::getAbsoluteUrl() * @return mixed diff --git a/library/Icinga/Web/Widget.php b/library/Icinga/Web/Widget.php deleted file mode 100644 index 0947b1643..000000000 --- a/library/Icinga/Web/Widget.php +++ /dev/null @@ -1,73 +0,0 @@ - - * @author Icinga Development Team - */ -// {{{ICINGA_LICENSE_HEADER}}} - -namespace Icinga\Web; - -use Icinga\Exception\ProgrammingError; - -/** - * Web widgets make things easier for you! - * - * This class provides nothing but a static factory method for widget creation. - * Usually it will not be used directly as there are widget()-helpers available - * in your action controllers and view scripts. - * - * Usage example: - * - * $tabs = Widget::create('tabs'); - * - * - * @copyright Copyright (c) 2013 Icinga-Web Team - * @author Icinga-Web Team - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License - */ -class Widget -{ - - /** - * Create a new widget - * - * @param string $name Widget name - * @param array $options Widget constructor options - * - * @return Icinga\Web\Widget\AbstractWidget - */ - public static function create($name, $options = array(), $module_name = null) - { - $class = 'Icinga\\Web\\Widget\\' . ucfirst($name); - - if (! class_exists($class)) { - throw new ProgrammingError( - sprintf( - 'There is no such widget: %s', - $name - ) - ); - } - - $widget = new $class($options, $module_name); - return $widget; - } -} diff --git a/library/Icinga/Web/Widget/AbstractWidget.php b/library/Icinga/Web/Widget/AbstractWidget.php deleted file mode 100644 index 214032413..000000000 --- a/library/Icinga/Web/Widget/AbstractWidget.php +++ /dev/null @@ -1,167 +0,0 @@ - - * @author Icinga-Web Team - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License - */ -abstract class AbstractWidget -{ - /** - * If you are going to access the current view with the view() function, - * it's instance is stored here for performance reasons. - * - * @var Zend_View_Abstract - */ - protected static $view; - - protected $module_name; - - /** - * Fill $properties with default values for all your valid widget properties - * - * @var array - */ - protected $properties = array(); - - /** - * You MUST extend this function. This is where all your HTML voodoo happens - * - * @return string - */ - abstract public function renderAsHtml(); - - /** - * You are not allowed to override the constructor, but you can put - * initialization stuff in your init() function - * - * @return void - */ - protected function init() - { - } - - /** - * We are not allowing you to override the constructor unless someone - * presents a very good reason for doing so - * - * @param array $properties An optional properties array - */ - final public function __construct($properties = array(), $module_name = null) - { - if ($module_name !== null) { - $this->module_name = $module_name; - } - foreach ($properties as $key => $val) { - $this->$key = $val; - } - $this->init(); - } - - /** - * Getter for widget properties - * - * @param string $key The option you're interested in - * - * @throws ProgrammingError for unknown property name - * - * @return mixed - */ - public function __get($key) - { - if (array_key_exists($key, $this->properties)) { - return $this->properties[$key]; - } - - throw new ProgrammingError( - sprintf( - 'Trying to get invalid "%s" property for %s', - $key, - get_class($this) - ) - ); - } - - /** - * Setter for widget properties - * - * @param string $key The option you want to set - * @param string $val The new value going to be assigned to this option - * - * @throws ProgrammingError for unknown property name - * - * @return mixed - */ - public function __set($key, $val) - { - if (array_key_exists($key, $this->properties)) { - $this->properties[$key] = $val; - return; - } - - throw new ProgrammingError( - sprintf( - 'Trying to set invalid "%s" property in %s. Allowed are: %s', - $key, - get_class($this), - empty($this->properties) - ? 'none' - : implode(', ', array_keys($this->properties)) - ) - ); - } - - /** - * Access the current view - * - * Will instantiate a new one if none exists - * // TODO: App->getView - * - * @return Zend_View_Abstract - */ - protected function view() - { - if (self::$view === null) { - - $renderer = ZfActionHelper::getStaticHelper( - 'viewRenderer' - ); - - if (null === $renderer->view) { - $renderer->initView(); - } - - self::$view = $renderer->view; - } - - return self::$view; - } - - /** - * Cast this widget to a string. Will call your renderAsHtml() function - * - * @return string - */ - public function __toString() - { - return $this->renderAsHtml(); - } -} diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index 91da715e5..2681c93e8 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -1,83 +1,209 @@ + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + */ +// {{{ICINGA_LICENSE_HEADER}}} namespace Icinga\Web\Widget; use Icinga\Application\Icinga; -use Icinga\Config\Config; -use Icinga\Web\Widget; +use Icinga\Config\Config as IcingaConfig; +use Icinga\Application\Logger; +use Icinga\Exception\ConfigurationError; +use Icinga\Web\Widget\Widget; use Icinga\Web\Widget\Dashboard\Pane; +use Icinga\Web\Widget\Dashboard\Component as DashboardComponent; + use Icinga\Web\Url; -use Zend_Config as ZfConfig; +use Zend_View_Abstract; -class Dashboard extends AbstractWidget +/** + * 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 { - protected $config; - protected $configfile; - protected $panes = array(); - protected $tabs; + /** + * The configuration containing information about this dashboard + * + * @var IcingaConfig; + */ + private $config; - protected $properties = array( - 'url' => null, - 'tabParam' => 'pane' - ); + /** + * An array containing all panes of this dashboard + * + * @var array + */ + private $panes = array(); - protected function init() - { - if ($this->url === null) { - $this->url = Url::fromRequest()->without($this->tabParam); - } - } + /** + * The @see Icinga\Web\Widget\Tabs object for displaying displayable panes + * + * @var Tabs + */ + private $tabs; + /** + * The parameter that will be added to identify panes + * + * @var string + */ + private $tabParam = 'pane'; + + /** + * Set the given tab name as active. + * + * @param string $name The tab name to activate + * + */ public function activate($name) { - $this->tabs()->activate($name); + $this->getTabs()->activate($name); } - - public function tabs() + + /** + * 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 = Widget::create('tabs'); + $this->tabs = new Tabs(); + foreach ($this->panes as $key => $pane) { - $this->tabs->add($key, array( - 'title' => $pane->getTitle(), - 'url' => clone($this->url), - 'urlParams' => array($this->tabParam => $key) - )); + $this->tabs->add( + $key, + array( + 'title' => $pane->getTitle(), + 'url' => clone($url), + 'urlParams' => array($this->tabParam => $key) + ) + ); } } - return $this->tabs; } - public function isWritable() + /** + * 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) { - return is_writable($this->configfile); - } + if ($file === null) { + $file = IcingaConfig::app('dashboard/dashboard')->getConfigFile(); + } - public function store() - { - if (! @file_put_contents($this->configfile, $this->toIni())) { - return false; + 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'); + } + // make sure empty dashboards don't cause errors + $iniString = trim($this->toIni()); + if (!$iniString) { + $iniString = ' '; + } + if (!@file_put_contents($file, $iniString)) { + $error = error_get_last(); + if ($error == null) { + $error = 'Unknown error'; + } else { + $error = $error['message']; + } + Logger::error('Tried to persist dashboard to %s, but got error: %s', $file, $error); + throw new ConfigurationError('Can\'t persist dashboard'); } else { return $this; } } - public function readConfig(ZfConfig $config) + /** + * Populate this dashboard via the given configuration file + * + * @param IcingaConfig $config The configuration file to populate this dashboard with + * + * @return self + */ + public function readConfig(IcingaConfig $config) { - $this->configfile = Config::getInstance()->getConfigDir() - . '/dashboard.ini'; $this->config = $config; $this->panes = array(); $this->loadConfigPanes(); return $this; } + /** + * Creates a new empty pane with the given title + * + * @param string $title + * + * @return self + */ + public function createPane($title) + { + $pane = new Pane($title); + $pane->setTitle($title); + $this->addPane($pane); + + return $this; + } + + /** + * 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 self + */ 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); + } $pane = $this->getPane($pane); if ($pane->hasComponent($component)) { $pane->getComponent($component)->setUrl($url); @@ -87,16 +213,51 @@ class Dashboard extends AbstractWidget 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); + if ($paneObj === null) { + return true; + } + $cmps = $paneObj->getComponents(); + 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 self + */ public function removeComponent($pane, $component) { if ($component === null && strpos($pane, '.')) { list($pane, $component) = preg_split('~\.~', $pane, 2); } - $this->getPane($pane)->removeComponent($component); + $pane = $this->getPane($pane); + if ($pane !== null) { + $pane->removeComponent($component); + } + return $this; } - public function paneEnum() + /** + * Return an array with pane name=>title format used for comboboxes + * + * @return array + */ + public function getPaneKeyTitleArray() { $list = array(); foreach ($this->panes as $name => $pane) { @@ -105,54 +266,85 @@ class Dashboard extends AbstractWidget 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 self + */ 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]; } - - public function renderAsHtml() + + /** + * @see Icinga\Web\Widget::render + */ + public function render(Zend_View_Abstract $view) { if (empty($this->panes)) { return ''; } - - return $this->tabs() . $this->getActivePane(); + return $this->determineActivePane()->render($view); } - public function getActivePane() + /** + * Activates the default pane of this dashboard and returns it's name + * + * @return mixed + */ + private function setDefaultPane() { - $active = $this->tabs()->getActiveName(); - if (! $active) { + reset($this->panes); + $active = key($this->panes); + $this->activate($active); + return $active; + } + + /** + * 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) { if ($active = Url::fromRequest()->getParam($this->tabParam)) { - $this->activate($active); + if ($this->isEmptyPane($active)) { + $active = $this->setDefaultPane(); + } else { + $this->activate($active); + } } else { - reset($this->panes); - $active = key($this->panes); - $this->activate($active); + $active = $this->setDefaultPane(); } } - return $this->panes[$active]; } + /** + * Return the ini string describing this dashboard + * + * @return string + */ public function toIni() { $ini = ''; @@ -161,33 +353,22 @@ class Dashboard extends AbstractWidget } return $ini; } - - protected function loadConfigPanes() + + /** + * Load all config panes from @see Dashboard::$config + * + */ + private function loadConfigPanes() { - $items = $this->config->dashboard->toArray(); - $app = Icinga::app(); - foreach ($items as $key => $item) { + $items = $this->config; + foreach ($items->keys() as $key) { + $item = $this->config->get($key, false); if (false === strstr($key, '.')) { - $pane = new Pane($key); - if (isset($item['title'])) { - $pane->setTitle($item['title']); - } - $this->addPane($pane); + $this->addPane(Pane::fromIni($key, $item)); } else { - list($dashboard, $title) = preg_split('~\.~', $key, 2); - $base_url = $item['base_url']; - - $module = substr($base_url, 0, strpos($base_url, '/')); - $whitelist = array(); - if (! $app->hasModule($module)) { - continue; - } - - unset($item['base_url']); - $this->getPane($dashboard)->addComponent( - $title, - Url::fromPath($base_url, $item) - ); + 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 9c43f36c9..e47c3e90b 100644 --- a/library/Icinga/Web/Widget/Dashboard/Component.php +++ b/library/Icinga/Web/Widget/Dashboard/Component.php @@ -1,24 +1,109 @@ + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + */ +// {{{ICINGA_LICENSE_HEADER}}} namespace Icinga\Web\Widget\Dashboard; +use Icinga\Util\Dimension; use Icinga\Web\Url; +use Icinga\Web\Widget\Widget; +use Zend_Config; /** * A dashboard pane component * - * Needs a title and an URL - * // TODO: Rename to "Dashboardlet" + * This is the element displaying a specific view in icinga2web * */ -class Component +class Component implements Widget { - protected $url; - protected $title; + /** + * The url of this Component + * + * @var \Icinga\Web\Url + */ + private $url; - public function __construct($title, $url) + /** + * 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' + +
+ {TITLE} + X + +
+ +
+
+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; + $this->pane = $pane; if ($url instanceof Url) { $this->url = $url; } else { @@ -27,7 +112,27 @@ class Component } /** - * Retrieve this components title + * 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 the components title * * @return string */ @@ -37,7 +142,7 @@ class Component } /** - * Retrieve my url + * Retrieve the components url * * @return Url */ @@ -47,9 +152,10 @@ class Component } /** - * Set this components URL + * Set the components URL + * + * @param string|Url $url The url to use, either as an Url object or as a path * - * @param string|Url $url Component URL * @return self */ public function setUrl($url) @@ -62,55 +168,96 @@ class Component 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('base_url', $this->url->getScript()); + $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) . '"' . PHP_EOL; + } + if ($this->width !== null) { + $ini .= 'width = "' . ((string) $this->width) . '"' . PHP_EOL; } return $ini; } /** - * Render this components HTML + * @see Widget::render() */ - public function __toString() + public function render(\Zend_View_Abstract $view) { - $url = clone($this->url); + $url = clone($this->url); $url->addParams(array('view' => 'compact')); - if (isset($_GET['layout'])) { - $url->addParams(array('layout' => $_GET['layout'])); + + $removeUrl = Url::fromPath( + '/dashboard/removecomponent', + array( + 'pane' => $this->pane->getName(), + 'component' => $this->getTitle() + ) + ); + + $html = str_replace('{URL}', $url->getAbsoluteUrl(), $this->template); + $html = str_replace('{REMOVE_URL}', $removeUrl, $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 = ''; + if ($this->height) { + $style .= 'height:' . (string) $this->height . ';'; + } + 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; + $width = null; + $url = $config->get('url'); + $parameters = $config->toArray(); + unset($parameters['url']); // otherwise there's an url = parameter in the Url + + if (isset($parameters['height'])) { + $height = Dimension::fromString($parameters['height']); + unset($parameters['height']); } - $htm = '
' - . "\n" - . '

' - . htmlspecialchars($this->title) - . "

\n" - . '' - . "\n
\n"; - return $htm; + if (isset($parameters['width'])) { + $width = Dimension::fromString($parameters['width']); + unset($parameters['width']); + } + + $cmp = new Component($title, Url::fromPath($url, $parameters), $pane); + $cmp->setHeight($height); + $cmp->setWidth($width); + return $cmp; } } diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php index c57aaf5d2..a5ada7056 100644 --- a/library/Icinga/Web/Widget/Dashboard/Pane.php +++ b/library/Icinga/Web/Widget/Dashboard/Pane.php @@ -1,54 +1,145 @@ + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + */ +// {{{ICINGA_LICENSE_HEADER}}} namespace Icinga\Web\Widget\Dashboard; -use Icinga\Web\Url; use Icinga\Exception\ConfigurationError; +use Icinga\Exception\ProgrammingError; +use Icinga\Web\Widget\Widget; +use Zend_Config; +use Zend_View_Abstract; -class Pane +/** + * 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 self + */ 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)) { return $this->components[$title]; } - throw new ProgrammingError(sprintf( - 'Trying to access invalid component: %s', - $title - )); + throw new ProgrammingError(sprintf('Trying to access invalid component: %s', $title)); } + /** + * 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)) { @@ -57,48 +148,86 @@ class Pane 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; + foreach ($this->components as $component) { + $html .= PHP_EOL.$component->render($view); + } + 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 self + * @throws \Icinga\Exception\ConfigurationError + */ public function addComponent($component, $url = null) { if ($component instanceof Component) { - $this->components[$component->title] = $component; + $this->components[$component->getTitle()] = $component; } elseif (is_string($component) && $url !== null) { - $this->components[$component] = new Component($component, $url); - } else{ - throw new ConfigurationError('You messed up your dashboard'); + $this->components[$component] = new Component($component, $url, $this); + } else { + 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() { - $ini = sprintf( - "[%s]\ntitle = %s\n", - $this->getName(), - $this->quoteIni($this->getTitle()) - ) . "\n"; + if (empty($this->components)) { + return ''; + } + $ini = '[' . $this->getName() . ']' . PHP_EOL. + 'title = "' . $this->getTitle() . '"' . PHP_EOL; foreach ($this->components as $title => $component) { - $ini .= sprintf( - "[%s.%s]\n", - $this->getName(), - $title - ) . $component->toIni() . "\n"; + // component header + $ini .= '[' . $this->getName() . '.' . $title . ']' . PHP_EOL; + // component content + $ini .= $component->toIni() . PHP_EOL; } return $ini; } - public function __toString() + /** + * 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) { - return implode('', $this->components); + $pane = new Pane($title); + if ($config->get('title', false)) { + $pane->setTitle($config->get('title')); + } + return $pane; } } diff --git a/library/Icinga/Web/Widget/Form.php b/library/Icinga/Web/Widget/Form.php deleted file mode 100644 index 50525b21c..000000000 --- a/library/Icinga/Web/Widget/Form.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @author Icinga-Web Team - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License - */ -class Form extends AbstractWidget -{ - protected $form; - protected $properties = array( - 'name' => null, - 'options' => null - ); - - public function __call($func, $args) - { - return call_user_func_array(array($this->form, $func), $args); - } - - protected function init() - { - // Load form by name given in props: - $file = null; - $fparts = array(); - $cparts = array(); - foreach (preg_split('~/~', $this->name, -1, PREG_SPLIT_NO_EMPTY) as $part) { - $fparts[] = $part; - $cparts[] = ucfirst($part); - } - array_push($fparts, ucfirst(array_pop($fparts))); - - $app = Icinga::app(); - $module_name = $this->view()->module_name; - if ($module_name === 'default') { - $module_name = null; - } - if ($module_name !== null) { - $fname = $app->getModuleManager()->getModule($module_name)->getBaseDir() - . '/application/forms/' - . implode('/', $fparts) - . 'Form.php'; - if (file_exists($fname)) { - $file = $fname; - array_unshift($cparts, ucfirst($module_name)); - } - } - - if ($file === null) { - $fname = $app->getApplicationDir('forms/') - . implode('/', $fparts) - . 'Form.php'; - if (file_exists($fname)) { - $file = $fname; - } else { - throw new ProgrammingError(sprintf( - 'Unable to load your form: %s', - $this->name - )); - } - } - $class = 'Icinga\\Web\\Form\\' . implode('_', $cparts) . 'Form'; - require_once($file); - $this->form = new $class($this->options); - } - - public function renderAsHtml() - { - return (string) $this->form; - } -} diff --git a/library/Icinga/Web/Widget/Tab.php b/library/Icinga/Web/Widget/Tab.php index a8ea47f68..7393ea2ff 100644 --- a/library/Icinga/Web/Widget/Tab.php +++ b/library/Icinga/Web/Widget/Tab.php @@ -1,11 +1,35 @@ + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team */ +// {{{ICINGA_LICENSE_HEADER}}} + namespace Icinga\Web\Widget; use Icinga\Exception\ProgrammingError; +use Zend_View_Abstract; /** * A single tab, usually used through the tabs widget @@ -24,41 +48,141 @@ use Icinga\Exception\ProgrammingError; * @author Icinga-Web Team * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License */ -class Tab extends AbstractWidget +class Tab implements Widget { /** * Whether this tab is currently active * * @var bool */ - protected $active = false; + private $active = false; /** * Default values for widget properties * * @var array */ - protected $properties = array( - 'name' => null, - 'title' => '', - 'url' => null, - 'urlParams' => array(), - 'icon' => null, - ); + private $name = null; /** - * Health check at initialization time + * The title displayed for this tab * - * @throws Icinga\Exception\ProgrammingError if tab name is missing - * - * @return void + * @var string */ - protected function init() + private $title = ''; + + /** + * The Url this tab points to + * + * @var string|null + */ + private $url = null; + + /** + * The parameters for this tab's Url + * + * @var array + */ + private $urlParams = array(); + + /** + * The icon image to use for this tab or null if none + * + * @var string|null + */ + private $icon = null; + + /** + * The icon class to use if $icon is null + * + * @var string|null + */ + private $iconCls = null; + + + /** + * Sets an icon image for this tab + * + * @param string $icon The url of the image to use + */ + public function setIcon($icon) { + $this->icon = $icon; + } + + /** + * Set's an icon class that will be used in an tag if no icon image is set + * + * @param string $iconCls The CSS class of the icon to use + */ + public function setIconCls($iconCls) + { + $this->iconCls = $iconCls; + } + + + /** + * @param mixed $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * @param mixed $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * Set the Url this tab points to + * + * @param string $url The Url to use for this tab + */ + public function setUrl($url) + { + $this->url = $url; + } + + + /** + * Set the parameters to be set for this tabs Url + * + * @param array $url The Url parameters to set + */ + public function setUrlParams(array $urlParams) + { + $this->urlParams = $urlParams; + } + + /** + * Create a new Tab with the given properties + * + * Allowed properties are all properties for which a setter exists + * + * @param array $properties An array of properties + */ + public function __construct(array $properties = array()) + { + foreach ($properties as $name => $value) { + $setter = 'set' . ucfirst($name); + if (method_exists($this, $setter)) { + $this->$setter($value); + } + } if ($this->name === null) { - throw new ProgrammingError( - 'Cannot create a nameless tab' - ); + throw new ProgrammingError('Cannot create a nameless tab'); } } @@ -78,31 +202,24 @@ class Tab extends AbstractWidget return $this; } - /** - * Whether this tab is currently active - * - * @return bool - */ - public function isActive() - { - return $this->active; - } /** - * This is where the list item HTML is created - * - * @return string + * @see Widget::render() */ - public function renderAsHtml() + public function render(Zend_View_Abstract $view) { - $view = $this->view(); - $class = $this->isActive() ? ' class="active"' : ''; + $class = $this->active ? ' class="active"' : ''; $caption = $this->title; if ($this->icon !== null) { - $caption = $view->img($this->icon, array( - 'width' => 16, - 'height' => 16 - )) . ' ' . $caption; + $caption = $view->img( + $this->icon, + array( + 'width' => 16, + 'height' => 16 + ) + ) . ' ' . $caption; + } elseif ($this->iconCls !== null) { + $caption = ' ' . $caption; } if ($this->url !== null) { $tab = $view->qlink( @@ -114,6 +231,7 @@ class Tab extends AbstractWidget } else { $tab = $caption; } - return "
  • $tab
  • \n"; + + return '
  • ' . $tab . '
  • ' . PHP_EOL; } } diff --git a/library/Icinga/Web/Widget/Tabs.php b/library/Icinga/Web/Widget/Tabs.php index 8e9c3483d..9ddc6cd13 100644 --- a/library/Icinga/Web/Widget/Tabs.php +++ b/library/Icinga/Web/Widget/Tabs.php @@ -1,8 +1,31 @@ + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team */ +// {{{ICINGA_LICENSE_HEADER}}} + namespace Icinga\Web\Widget; use Icinga\Exception\ProgrammingError; @@ -13,36 +36,37 @@ use Countable; /** * Navigation tab widget * - * Useful if you want to create navigation tabs - * - * @copyright Copyright (c) 2013 Icinga-Web Team - * @author Icinga-Web Team - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License */ -class Tabs extends AbstractWidget implements Countable +class Tabs implements Countable, Widget { /** * This is where single tabs added to this container will be stored * * @var array */ - protected $tabs = array(); + private $tabs = array(); /** * The name of the currently activated tab * * @var string */ - protected $active; + private $active; /** * Class name(s) going to be assigned to the <ul> element * * @var string */ - protected $tab_class = 'nav-tabs'; + private $tab_class = 'nav-tabs'; - protected $specialActions = false; + /** + * Array when special actions (dropdown) are enabled + * @TODO: Remove special part from tabs (Bug #4512) + * + * @var bool|array + */ + private $specialActions = false; /** * Activate the tab with the given name @@ -77,6 +101,11 @@ class Tabs extends AbstractWidget implements Countable ); } + /** + * Return the name of the active tab + * + * @return string + */ public function getActiveName() { return $this->active; @@ -118,7 +147,7 @@ class Tabs extends AbstractWidget implements Countable */ public function get($name) { - if (! $this->has($name)) { + if (!$this->has($name)) { throw new ProgrammingError( sprintf( 'There is no such tab: %s', @@ -177,6 +206,13 @@ class Tabs extends AbstractWidget implements Countable return $this; } + /** + * Enable special actions (dropdown with format, basket and dashboard) + * + * @TODO: Remove special part from tabs (Bug #4512) + * + * @return $this + */ public function enableSpecialActions() { $this->specialActions = true; @@ -184,52 +220,49 @@ class Tabs extends AbstractWidget implements Countable } /** - * This is where the tabs are going to be rendered - * - * @return string + * @see Widget::render */ - public function renderAsHtml() + public function render(\Zend_View_Abstract $view) { - $view = $this->view(); - if (empty($this->tabs)) { return ''; } - $html = '