= $this->escape($this->translate('This feature is deactivated at the moment.')); ?>
-
- =
- $this->escape($this->translate('Please have a little patience, we are hard working on it, take a look at icingaweb2 issues.'));
- ?>
-
+
= t('Add URL to Dashboard'); ?>
+ = $this->form; ?>
\ No newline at end of file
diff --git a/application/views/scripts/dashboard/error.phtml b/application/views/scripts/dashboard/error.phtml
new file mode 100644
index 000000000..e5a0f3939
--- /dev/null
+++ b/application/views/scripts/dashboard/error.phtml
@@ -0,0 +1,13 @@
+
+
= t('Could not persist dashboard'); ?>
+
+ = t('Please copy the following dashboard snippet to '); ?>
+ = $this->config->getFilename(); ?>;.
+
+ = t('Make sure that the webserver can write to this file.'); ?>
+
= $this->escape($this->translate('Welcome to Icinga Web!')) ?>
-
= sprintf(
- $this->escape($this->translate('Currently there is no dashlet available. This might change once you enabled some of the available %s.')),
- $this->qlink($this->translate('modules'), 'config/modules')
- ) ?>
+
+ = sprintf(
+ $this->escape($this->translate('Currently there is no dashlet available. This might change once you enabled some of the available %s.')),
+ $this->qlink($this->translate('modules'), 'config/modules')
+ ) ?>
+
\ No newline at end of file
diff --git a/application/views/scripts/dashboard/new-component.phtml b/application/views/scripts/dashboard/new-component.phtml
new file mode 100644
index 000000000..46c8b2255
--- /dev/null
+++ b/application/views/scripts/dashboard/new-component.phtml
@@ -0,0 +1,7 @@
+
+ = $this->tabs ?>
+
+
+
= t('Add Component To Dashboard'); ?>
+ = $this->form; ?>
+
\ No newline at end of file
diff --git a/application/views/scripts/dashboard/remove-component.phtml b/application/views/scripts/dashboard/remove-component.phtml
new file mode 100644
index 000000000..c39472771
--- /dev/null
+++ b/application/views/scripts/dashboard/remove-component.phtml
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/application/views/scripts/dashboard/remove-pane.phtml b/application/views/scripts/dashboard/remove-pane.phtml
new file mode 100644
index 000000000..637774cab
--- /dev/null
+++ b/application/views/scripts/dashboard/remove-pane.phtml
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/application/views/scripts/dashboard/settings.phtml b/application/views/scripts/dashboard/settings.phtml
new file mode 100644
index 000000000..83fba6588
--- /dev/null
+++ b/application/views/scripts/dashboard/settings.phtml
@@ -0,0 +1,62 @@
+
+
\ No newline at end of file
diff --git a/application/views/scripts/dashboard/show-configuration.phtml b/application/views/scripts/dashboard/show-configuration.phtml
deleted file mode 100644
index 7cdce496c..000000000
--- a/application/views/scripts/dashboard/show-configuration.phtml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
{{WARNING_ICON}}Saving Dashboard Failed
-
-
- Your dashboard couldn't be stored (error: "= $this->exceptionMessage; ?>"). This could have one or more
- of the following reasons:
-
-
-
You don't have permissions to write to the dashboard file
-
Something went wrong while writing the file
-
There's an application error preventing you from persisting the configuration
-
-
-
-
- Details can be seen in your application log (if you don't have access to this file, call your administrator in this case).
-
- In case you can access the configuration file (config/dashboard/dashboard.ini) by yourself, you can open it and
- insert the config manually:
-
-
\ No newline at end of file
diff --git a/application/views/scripts/dashboard/update-component.phtml b/application/views/scripts/dashboard/update-component.phtml
new file mode 100644
index 000000000..0493f5613
--- /dev/null
+++ b/application/views/scripts/dashboard/update-component.phtml
@@ -0,0 +1,8 @@
+
+ = $this->tabs ?>
+
+
+
+
= t('Edit Component'); ?>
+ = $this->form; ?>
+
\ No newline at end of file
diff --git a/library/Icinga/File/Ini/IniWriter.php b/library/Icinga/File/Ini/IniWriter.php
index cf9355b56..e3b757b78 100644
--- a/library/Icinga/File/Ini/IniWriter.php
+++ b/library/Icinga/File/Ini/IniWriter.php
@@ -229,4 +229,14 @@ class IniWriter extends Zend_Config_Writer_FileAbstract
return $combinations;
}
+
+ /**
+ * Getter for filename
+ *
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->_filename;
+ }
}
diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php
index 8d75a9df3..863d4f1c1 100644
--- a/library/Icinga/Web/Form.php
+++ b/library/Icinga/Web/Form.php
@@ -163,6 +163,26 @@ class Form extends Zend_Form
parent::__construct($options);
}
+ /**
+ * Set a callback that is called instead of this form's onSuccess method
+ *
+ * It is called using the following signature: (Request $request, Form $form).
+ *
+ * @param callable $onSuccess Callback
+ *
+ * @return $this
+ *
+ * @throws LogicException If the callback is not callable
+ */
+ public function setOnSuccess($onSuccess)
+ {
+ if (! is_callable($onSuccess)) {
+ throw new LogicException('The option `onSuccess\' is not callable');
+ }
+ $this->onSuccess = $onSuccess;
+ return $this;
+ }
+
/**
* Set the label to use for the standard submit button
*
diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php
index f7daa5d02..c97bdf578 100644
--- a/library/Icinga/Web/Widget/Dashboard.php
+++ b/library/Icinga/Web/Widget/Dashboard.php
@@ -6,8 +6,13 @@ namespace Icinga\Web\Widget;
use Icinga\Application\Icinga;
use Icinga\Application\Config;
+use Icinga\Data\ConfigObject;
use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotReadableError;
use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\SystemPermissionException;
+use Icinga\File\Ini\IniWriter;
+use Icinga\User;
use Icinga\Web\Widget\Dashboard\Pane;
use Icinga\Web\Widget\Dashboard\Component as DashboardComponent;
use Icinga\Web\Url;
@@ -23,13 +28,6 @@ use Icinga\Web\Url;
*/
class Dashboard extends AbstractWidget
{
- /**
- * The configuration containing information about this dashboard
- *
- * @var Config;
- */
- private $config;
-
/**
* An array containing all panes of this dashboard
*
@@ -51,6 +49,11 @@ class Dashboard extends AbstractWidget
*/
private $tabParam = 'pane';
+ /**
+ * @var User
+ */
+ private $user;
+
/**
* Set the given tab name as active.
*
@@ -67,30 +70,136 @@ class Dashboard extends AbstractWidget
*
* @return self
*/
- public static function load()
+ public function load()
{
- /** @var $dashboard Dashboard */
- $dashboard = new static('dashboard');
$manager = Icinga::app()->getModuleManager();
foreach ($manager->getLoadedModules() as $module) {
/** @var $module \Icinga\Application\Modules\Module */
- $dashboard->mergePanes($module->getPaneItems());
+ $this->mergePanes($module->getPaneItems());
}
- return $dashboard;
+ if ($this->user !== null) {
+ $this->loadUserDashboards();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create a writer object
+ *
+ * @return IniWriter
+ */
+ public function createWriter()
+ {
+ $configFile = $this->getConfigFile();
+ $output = array();
+ foreach ($this->panes as $pane) {
+ if ($pane->isUserWidget() === true) {
+ $output[$pane->getName()] = $pane->toArray();
+ }
+ foreach ($pane->getComponents() as $component) {
+ if ($component->isUserWidget() === true) {
+ $output[$pane->getName() . '.' . $component->getTitle()] = $component->toArray();
+ }
+ }
+ }
+
+ $co = new ConfigObject($output);
+ $config = new Config($co);
+ return new IniWriter(array('config' => $config, 'filename' => $configFile));
+ }
+
+ /**
+ * Write user specific dashboards to disk
+ */
+ public function write()
+ {
+ $this->createWriter()->write();
+ }
+
+ /**
+ * @return bool
+ */
+ private function loadUserDashboards()
+ {
+ try {
+ $config = Config::fromIni($this->getConfigFile());
+ } catch (NotReadableError $e) {
+ return;
+ }
+ if (! count($config)) {
+ return false;
+ }
+ $panes = array();
+ $components = array();
+ foreach ($config as $key => $part) {
+ if (strpos($key, '.') === false) {
+ if ($this->hasPane($part->title)) {
+ $panes[$key] = $this->getPane($part->title);
+ } else {
+ $panes[$key] = new Pane($key);
+ $panes[$key]->setTitle($part->title);
+ }
+ $panes[$key]->setUserWidget();
+ if ((bool) $part->get('disabled', false) === true) {
+ $panes[$key]->setDisabled();
+ }
+
+ } else {
+ list($paneName, $componentName) = explode('.', $key, 2);
+ $part->pane = $paneName;
+ $part->component = $componentName;
+ $components[] = $part;
+ }
+ }
+ foreach ($components as $componentData) {
+ $pane = null;
+
+ if (array_key_exists($componentData->pane, $panes) === true) {
+ $pane = $panes[$componentData->pane];
+ } elseif (array_key_exists($componentData->pane, $this->panes) === true) {
+ $pane = $this->panes[$componentData->pane];
+ } else {
+ continue;
+ }
+ $component = new DashboardComponent(
+ $componentData->title,
+ $componentData->url,
+ $pane
+ );
+
+ if ((bool) $componentData->get('disabled', false) === true) {
+ $component->setDisabled(true);
+ }
+
+ $component->setUserWidget();
+ $pane->addComponent($component);
+ }
+
+ $this->mergePanes($panes);
+
+ return true;
}
/**
* Merge panes with existing panes
*
- * @param array $panes
- * @return $this
+ * @param array $panes
+ *
+ * @return $this
*/
public function mergePanes(array $panes)
{
/** @var $pane Pane */
foreach ($panes as $pane) {
- if (array_key_exists($pane->getName(), $this->panes)) {
+ if ($pane->getDisabled()) {
+ if ($this->hasPane($pane->getTitle()) === true) {
+ $this->removePane($pane->getTitle());
+ }
+ continue;
+ }
+ if ($this->hasPane($pane->getTitle()) === true) {
/** @var $current Pane */
$current = $this->panes[$pane->getName()];
$current->addComponents($pane->getComponents());
@@ -109,7 +218,7 @@ class Dashboard extends AbstractWidget
*/
public function getTabs()
{
- $url = Url::fromRequest()->getUrlWithout($this->tabParam);
+ $url = Url::fromPath('dashboard')->getUrlWithout($this->tabParam);
if ($this->tabs === null) {
$this->tabs = new Tabs();
@@ -137,20 +246,6 @@ class Dashboard extends AbstractWidget
return $this->panes;
}
- /**
- * Populate this dashboard via the given configuration file
- *
- * @param Config $config The configuration file to populate this dashboard with
- *
- * @return self
- */
- public function readConfig(Config $config)
- {
- $this->config = $config;
- $this->panes = array();
- $this->loadConfigPanes();
- return $this;
- }
/**
* Creates a new empty pane with the given title
@@ -168,34 +263,6 @@ class Dashboard extends AbstractWidget
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 string|null $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);
- } else {
- $pane->addComponent($component, $url);
- }
- return $this;
- }
-
/**
* Checks if the current dashboard has any panes
*
@@ -207,49 +274,14 @@ class Dashboard extends AbstractWidget
}
/**
- * Check if this dashboard has a specific pane
+ * Check if a panel exist
*
- * @param $pane string The name of the pane
- * @return bool
+ * @param string $pane
+ * @return bool
*/
public function hasPane($pane)
{
- return array_key_exists($pane, $this->panes);
- }
-
- /**
- * 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);
- }
- $pane = $this->getPane($pane);
- if ($pane !== null) {
- $pane->removeComponent($component);
- }
-
- return $this;
- }
-
- /**
- * Return an array with pane name=>title format used for comboboxes
- *
- * @return array
- */
- public function getPaneKeyTitleArray()
- {
- $list = array();
- foreach ($this->panes as $name => $pane) {
- $list[$name] = $pane->getTitle();
- }
- return $list;
+ return $pane && array_key_exists($pane, $this->panes);
}
/**
@@ -265,6 +297,21 @@ class Dashboard extends AbstractWidget
return $this;
}
+ public function removePane($title)
+ {
+ if ($this->hasPane($title) === true) {
+ $pane = $this->getPane($title);
+ if ($pane->isUserWidget() === true) {
+ unset($this->panes[$pane->getName()]);
+ } else {
+ $pane->setDisabled();
+ $pane->setUserWidget();
+ }
+ } else {
+ throw new ProgrammingError('Pane not found: ' . $title);
+ }
+ }
+
/**
* Return the pane with the provided name
*
@@ -284,6 +331,20 @@ class Dashboard extends AbstractWidget
return $this->panes[$name];
}
+ /**
+ * Return an array with pane name=>title format used for comboboxes
+ *
+ * @return array
+ */
+ public function getPaneKeyTitleArray()
+ {
+ $list = array();
+ foreach ($this->panes as $name => $pane) {
+ $list[$name] = $pane->getTitle();
+ }
+ return $list;
+ }
+
/**
* @see Icinga\Web\Widget::render
*/
@@ -292,6 +353,7 @@ class Dashboard extends AbstractWidget
if (empty($this->panes)) {
return '';
}
+
return $this->determineActivePane()->render();
}
@@ -319,7 +381,10 @@ class Dashboard extends AbstractWidget
/**
* Determine the active pane either by the selected tab or the current request
*
- * @return Pane The currently active pane
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\ProgrammingError
+ *
+ * @return Pane The currently active pane
*/
public function determineActivePane()
{
@@ -346,34 +411,55 @@ class Dashboard extends AbstractWidget
}
/**
- * Return this dashboard's structure as array
+ * Setter for user object
*
- * @return array
+ * @param User $user
*/
- public function toArray()
+ public function setUser(User $user)
{
- $array = array();
- foreach ($this->panes as $pane) {
- $array += $pane->toArray();
- }
-
- return $array;
+ $this->user = $user;
}
/**
- * Load all config panes from @see Dashboard::$config
+ * Getter for user object
*
+ * @return User
*/
- private function loadConfigPanes()
+ public function getUser()
{
- foreach ($this->config as $key => $item) {
- if (false === strstr($key, '.')) {
- $this->addPane(Pane::fromIni($key, $item));
- } else {
- list($paneName, $title) = explode('.', $key, 2);
- $pane = $this->getPane($paneName);
- $pane->addComponent(DashboardComponent::fromIni($title, $item, $pane));
+ return $this->user;
+ }
+
+ /**
+ * Get config file
+ *
+ * @return string
+ */
+ public function getConfigFile()
+ {
+ if ($this->user === null) {
+ return '';
+ }
+
+ $baseDir = '/var/lib/icingaweb';
+
+ if (! file_exists($baseDir)) {
+ throw new NotReadableError('Could not read: ' . $baseDir);
+ }
+
+ $userDir = $baseDir . '/' . $this->user->getUsername();
+
+ if (! file_exists($userDir)) {
+ $success = @mkdir($userDir);
+ if (!$success) {
+ throw new SystemPermissionException('Could not create: ' . $userDir);
}
}
+
+ if (! file_exists($userDir)) {
+ throw new NotReadableError('Could not read: ' . $userDir);
+ }
+
+ return $userDir . '/dashboard.ini';
}
}
diff --git a/library/Icinga/Web/Widget/Dashboard/Component.php b/library/Icinga/Web/Widget/Dashboard/Component.php
index e0ac4b3d7..1ab0ff60d 100644
--- a/library/Icinga/Web/Widget/Dashboard/Component.php
+++ b/library/Icinga/Web/Widget/Dashboard/Component.php
@@ -17,7 +17,7 @@ use Icinga\Exception\IcingaException;
* This is the element displaying a specific view in icinga2web
*
*/
-class Component extends AbstractWidget
+class Component extends UserWidget
{
/**
* The url of this Component
@@ -53,7 +53,7 @@ class Component extends AbstractWidget
private $template =<<<'EOD'