diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php index e9aac4a95..ff2580c1b 100644 --- a/application/controllers/DashboardController.php +++ b/application/controllers/DashboardController.php @@ -106,29 +106,22 @@ class DashboardController extends ActionController $action = $this; $form->setOnSuccess(function (Form $form) use ($dashboard, $action) { try { - $pane = $dashboard->getPane($form->getValue('pane')); + $pane = $dashboard->getPane($form->getValue('org_pane')); + $pane->setTitle($form->getValue('pane')); } catch (ProgrammingError $e) { $pane = new Dashboard\Pane($form->getValue('pane')); $pane->setUserWidget(); $dashboard->addPane($pane); } try { - $dashlet = $pane->getDashlet($form->getValue('dashlet')); + $dashlet = $pane->getDashlet($form->getValue('org_dashlet')); + $dashlet->setTitle($form->getValue('dashlet')); $dashlet->setUrl($form->getValue('url')); } catch (ProgrammingError $e) { $dashlet = new Dashboard\Dashlet($form->getValue('dashlet'), $form->getValue('url'), $pane); $pane->addDashlet($dashlet); } $dashlet->setUserWidget(); - // Rename dashlet - if ($form->getValue('org_dashlet') && $form->getValue('org_dashlet') !== $dashlet->getTitle()) { - $pane->removeDashlet($form->getValue('org_dashlet')); - } - // Move - if ($form->getValue('org_pane') && $form->getValue('org_pane') !== $pane->getTitle()) { - $oldPane = $dashboard->getPane($form->getValue('org_pane')); - $oldPane->removeDashlet($dashlet->getTitle()); - } $dashboardConfig = $dashboard->getConfig(); try { $dashboardConfig->saveIni(); @@ -269,7 +262,7 @@ class DashboardController extends ActionController $action = $this; $form->setOnSuccess(function (Form $form) use ($dashboard, $pane, $action) { $pane = $dashboard->getPane($pane); - $dashboard->removePane($pane->getTitle()); + $dashboard->removePane($pane->getName()); $dashboardConfig = $dashboard->getConfig(); try { $dashboardConfig->saveIni(); diff --git a/application/forms/Dashboard/DashletForm.php b/application/forms/Dashboard/DashletForm.php index 22f1f6c5a..1af65a954 100644 --- a/application/forms/Dashboard/DashletForm.php +++ b/application/forms/Dashboard/DashletForm.php @@ -161,10 +161,10 @@ class DashletForm extends Form public function load(Dashlet $dashlet) { $this->populate(array( - 'pane' => $dashlet->getPane()->getName(), + 'pane' => $dashlet->getPane()->getTitle(), 'org_pane' => $dashlet->getPane()->getName(), 'dashlet' => $dashlet->getTitle(), - 'org_dashlet' => $dashlet->getTitle(), + 'org_dashlet' => $dashlet->getName(), 'url' => $dashlet->getUrl()->getRelativeUrl() )); } diff --git a/application/views/scripts/dashboard/settings.phtml b/application/views/scripts/dashboard/settings.phtml index ffa48b78a..ba672988e 100644 --- a/application/views/scripts/dashboard/settings.phtml +++ b/application/views/scripts/dashboard/settings.phtml @@ -59,7 +59,7 @@ qlink( $dashlet->getTitle(), 'dashboard/update-dashlet', - array('pane' => $pane->getName(), 'dashlet' => $dashlet->getTitle()), + array('pane' => $pane->getName(), 'dashlet' => $dashlet->getName()), array('title' => sprintf($this->translate('Edit dashlet %s'), $dashlet->getTitle())) ); ?> @@ -75,10 +75,10 @@ qlink( '', 'dashboard/remove-dashlet', - array('pane' => $pane->getName(), 'dashlet' => $dashlet->getTitle()), + array('pane' => $pane->getName(), 'dashlet' => $dashlet->getName()), array( 'icon' => 'trash', - 'title' => sprintf($this->translate('Remove dashlet %s from pane %s'), $dashlet->getTitle(), $pane->getName()) + 'title' => sprintf($this->translate('Remove dashlet %s from pane %s'), $dashlet->getTitle(), $pane->getTitle()) ) ); ?> diff --git a/doc/80-Upgrading.md b/doc/80-Upgrading.md index ddc8f146b..e621a7439 100644 --- a/doc/80-Upgrading.md +++ b/doc/80-Upgrading.md @@ -14,6 +14,9 @@ v2.3 to v2.5 requires to follow the instructions for v2.4 too. for log messages emitted by jquery-migrate. (https://github.com/jquery/jquery-migrate) Your javascript code will still work, though jquery-migrate will notify you if you're utilizing deprecated/removed functions. jquery-migrate will be removed with Icinga Web v2.8 and code not adjusted accordingly will stop working. +* If you're using a language other than english and you've adjusted or disabled module dashboards, you'll need to + update all of your `dashboard.ini` files. A CLI command exists to assist you with this task. Enable the `migrate` + module and run the following on the host where these files exist: `icingacli migrate dashboard sections --verbose` ## Upgrading to Icinga Web 2 2.6.x diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 450de7ded..735738089 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -314,9 +314,12 @@ class Module $navigation = new Navigation(); foreach ($panes as $pane) { /** @var DashboardContainer $pane */ - $dashlets = array(); + $dashlets = []; foreach ($pane->getDashlets() as $dashletName => $dashletUrl) { - $dashlets[$this->translate($dashletName)] = $dashletUrl; + $dashlets[$dashletName] = [ + 'label' => $this->translate($dashletName), + 'url' => $dashletUrl + ]; } $navigation->addItem( @@ -326,7 +329,7 @@ class Module array( 'label' => $this->translate($pane->getName()), 'type' => 'dashboard-pane', - 'dashlets' => $dashlets + 'children' => $dashlets ) ) ); diff --git a/library/Icinga/Legacy/DashboardConfig.php b/library/Icinga/Legacy/DashboardConfig.php index 54cd3b78f..3fb5c2fc2 100644 --- a/library/Icinga/Legacy/DashboardConfig.php +++ b/library/Icinga/Legacy/DashboardConfig.php @@ -5,6 +5,9 @@ namespace Icinga\Legacy; use Icinga\Application\Config; use Icinga\User; +use Icinga\Web\Navigation\DashboardPane; +use Icinga\Web\Navigation\Navigation; +use Icinga\Web\Navigation\NavigationItem; /** * Legacy dashboard config class for case insensitive interpretation of dashboard config files @@ -76,6 +79,51 @@ class DashboardConfig extends Config */ public function saveIni($filePath = null, $fileMode = 0660) { + // Preprocessing start, ensures that the non-translated names are used to save module dashboard changes + // TODO: This MUST NOT survive the new dashboard implementation (yes, it's still a thing..) + $dashboardNavigation = new Navigation(); + $dashboardNavigation->load('dashboard-pane'); + $getDashboardPane = function ($label) use ($dashboardNavigation) { + foreach ($dashboardNavigation as $dashboardPane) { + /** @var DashboardPane $dashboardPane */ + if ($dashboardPane->getLabel() === $label) { + return $dashboardPane; + } + + foreach ($dashboardPane->getChildren() as $dashlet) { + /** @var NavigationItem $dashlet */ + if ($dashlet->getLabel() === $label) { + return $dashlet; + } + } + } + }; + + foreach (clone $this->config as $name => $options) { + if (strpos($name, '.') !== false) { + list($dashboardLabel, $dashletLabel) = explode('.', $name, 2); + } else { + $dashboardLabel = $name; + $dashletLabel = null; + } + + $dashboardPane = $getDashboardPane($dashboardLabel); + if ($dashboardPane !== null) { + $dashboardLabel = $dashboardPane->getName(); + } + + if ($dashletLabel !== null) { + $dashletItem = $getDashboardPane($dashletLabel); + if ($dashletItem !== null) { + $dashletLabel = $dashletItem->getName(); + } + } + + unset($this->config[$name]); + $this->config[$dashboardLabel . ($dashletLabel ? '.' . $dashletLabel : '')] = $options; + } + // Preprocessing end + parent::saveIni($filePath, $fileMode); if ($filePath === null) { $filePath = $this->configFile; diff --git a/library/Icinga/Web/Navigation/DashboardPane.php b/library/Icinga/Web/Navigation/DashboardPane.php index f87259b03..71b3215cf 100644 --- a/library/Icinga/Web/Navigation/DashboardPane.php +++ b/library/Icinga/Web/Navigation/DashboardPane.php @@ -62,19 +62,6 @@ class DashboardPane extends NavigationItem $this->setUrl(Url::fromPath('dashboard', array('pane' => $this->getName()))); } - /** - * {@inheritdoc} - */ - public function merge(NavigationItem $item) - { - parent::merge($item); - - $this->setDashlets(array_merge( - $this->getDashlets(false), - $item->getDashlets(false) - )); - } - /** * Set disabled state for pane * diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index 83b0be654..5a8796d27 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -77,15 +77,15 @@ class Dashboard extends AbstractWidget foreach ($navigation as $dashboardPane) { /** @var DashboardPane $dashboardPane */ $pane = new Pane($dashboardPane->getLabel()); - foreach ($dashboardPane->getDashlets(false) as $title => $url) { - $pane->addDashlet($title, $url); + foreach ($dashboardPane->getChildren() as $dashlet) { + $pane->addDashlet($dashlet->getLabel(), $dashlet->getUrl()); } $panes[] = $pane; } $this->mergePanes($panes); - $this->loadUserDashboards(); + $this->loadUserDashboards($navigation); return $this; } @@ -103,7 +103,7 @@ class Dashboard extends AbstractWidget } foreach ($pane->getDashlets() as $dashlet) { if ($dashlet->isUserWidget()) { - $output[$pane->getName() . '.' . $dashlet->getTitle()] = $dashlet->toArray(); + $output[$pane->getName() . '.' . $dashlet->getName()] = $dashlet->toArray(); } } } @@ -114,10 +114,10 @@ class Dashboard extends AbstractWidget /** * Load user dashboards from all config files that match the username */ - protected function loadUserDashboards() + protected function loadUserDashboards(Navigation $navigation) { foreach (DashboardConfig::listConfigFilesForUser($this->user) as $file) { - $this->loadUserDashboardsFromFile($file); + $this->loadUserDashboardsFromFile($file, $navigation); } } @@ -128,7 +128,7 @@ class Dashboard extends AbstractWidget * * @return bool */ - protected function loadUserDashboardsFromFile($file) + protected function loadUserDashboardsFromFile($file, Navigation $dashboardNavigation) { try { $config = Config::fromIni($file); @@ -143,8 +143,12 @@ class Dashboard extends AbstractWidget $dashlets = array(); foreach ($config as $key => $part) { if (strpos($key, '.') === false) { - if ($this->hasPane($part->title)) { - $panes[$key] = $this->getPane($part->title); + $dashboardPane = $dashboardNavigation->getItem($key); + if ($dashboardPane !== null) { + $key = $dashboardPane->getLabel(); + } + if ($this->hasPane($key)) { + $panes[$key] = $this->getPane($key); } else { $panes[$key] = new Pane($key); $panes[$key]->setTitle($part->title); @@ -155,6 +159,14 @@ class Dashboard extends AbstractWidget } } else { list($paneName, $dashletName) = explode('.', $key, 2); + $dashboardPane = $dashboardNavigation->getItem($paneName); + if ($dashboardPane !== null) { + $paneName = $dashboardPane->getLabel(); + $dashletItem = $dashboardPane->getChildren()->getItem($dashletName); + if ($dashletItem !== null) { + $dashletName = $dashletItem->getLabel(); + } + } $part->pane = $paneName; $part->dashlet = $dashletName; $dashlets[] = $part; @@ -175,6 +187,7 @@ class Dashboard extends AbstractWidget $dashletData->url, $pane ); + $dashlet->setName($dashletData->dashlet); if ((bool) $dashletData->get('disabled', false) === true) { $dashlet->setDisabled(true); @@ -200,7 +213,7 @@ class Dashboard extends AbstractWidget { /** @var $pane Pane */ foreach ($panes as $pane) { - if ($this->hasPane($pane->getTitle()) === true) { + if ($this->hasPane($pane->getName()) === true) { /** @var $current Pane */ $current = $this->panes[$pane->getName()]; $current->addDashlets($pane->getDashlets()); diff --git a/library/Icinga/Web/Widget/Dashboard/Dashlet.php b/library/Icinga/Web/Widget/Dashboard/Dashlet.php index 17f971f0a..b9b2eb0d1 100644 --- a/library/Icinga/Web/Widget/Dashboard/Dashlet.php +++ b/library/Icinga/Web/Widget/Dashboard/Dashlet.php @@ -22,6 +22,8 @@ class Dashlet extends UserWidget */ private $url; + private $name; + /** * The title being displayed on top of the dashlet * @var @@ -91,11 +93,23 @@ EOD; */ public function __construct($title, $url, Pane $pane) { + $this->name = $title; $this->title = $title; $this->pane = $pane; $this->url = $url; } + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + /** * Retrieve the dashlets title * diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php index 967ba48e9..c8b14c5a1 100644 --- a/library/Icinga/Web/Widget/Dashboard/Pane.php +++ b/library/Icinga/Web/Widget/Dashboard/Pane.php @@ -22,7 +22,6 @@ class Pane extends UserWidget /** * The title of this pane, as displayed in the dashboard tabs - * @TODO: Currently the same as $name, evaluate if distinguishing is needed * * @var string */ @@ -229,7 +228,7 @@ class Pane extends UserWidget public function addDashlet($dashlet, $url = null) { if ($dashlet instanceof Dashlet) { - $this->dashlets[$dashlet->getTitle()] = $dashlet; + $this->dashlets[$dashlet->getName()] = $dashlet; } elseif (is_string($dashlet) && $url !== null) { $this->createDashlet($dashlet, $url); } else { @@ -248,15 +247,15 @@ class Pane extends UserWidget { /* @var $dashlet Dashlet */ foreach ($dashlets as $dashlet) { - if (array_key_exists($dashlet->getTitle(), $this->dashlets)) { - if (preg_match('/_(\d+)$/', $dashlet->getTitle(), $m)) { - $name = preg_replace('/_\d+$/', $m[1]++, $dashlet->getTitle()); + if (array_key_exists($dashlet->getName(), $this->dashlets)) { + if (preg_match('/_(\d+)$/', $dashlet->getName(), $m)) { + $name = preg_replace('/_\d+$/', $m[1]++, $dashlet->getName()); } else { - $name = $dashlet->getTitle() . '_2'; + $name = $dashlet->getName() . '_2'; } $this->dashlets[$name] = $dashlet; } else { - $this->dashlets[$dashlet->getTitle()] = $dashlet; + $this->dashlets[$dashlet->getName()] = $dashlet; } } diff --git a/modules/migrate/application/clicommands/DashboardCommand.php b/modules/migrate/application/clicommands/DashboardCommand.php new file mode 100644 index 000000000..4cb89a003 --- /dev/null +++ b/modules/migrate/application/clicommands/DashboardCommand.php @@ -0,0 +1,122 @@ +getMethod('launchConfigScript'); + $launchConfigScriptMethod->setAccessible(true); + // Calling getDashboard() results in Url::fromPath() getting called as well == the CLI's death + $paneItemsProperty = $moduleReflection->getProperty('paneItems'); + $paneItemsProperty->setAccessible(true); + // Again, no direct way to access this nor to let the module setup its own translation domain + $localeDirProperty = $moduleReflection->getProperty('localedir'); + $localeDirProperty->setAccessible(true); + + $locales = Translator::getAvailableLocaleCodes(); + $modules = Icinga::app()->getModuleManager()->loadEnabledModules()->getLoadedModules(); + foreach ($this->listDashboardConfigs() as $path) { + Logger::info('Migrating dashboard config: %s', $path); + + $config = Config::fromIni($path); + foreach ($modules as $module) { + $localePath = $localeDirProperty->getValue($module); + if (! is_dir($localePath)) { + // Modules without any translations are not affected + continue; + } + + $launchConfigScriptMethod->invoke($module); + Translator::registerDomain($module->getName(), $localePath); + + foreach ($locales as $locale) { + if ($locale === 'en_US') { + continue; + } + + Translator::setupLocale($locale); + + foreach ($paneItemsProperty->getValue($module) as $paneName => $container) { + /** @var DashboardContainer $container */ + foreach ($config->toArray() as $section => $options) { + if (strpos($section, '.') !== false) { + list($paneTitle, $dashletTitle) = explode('.', $section, 2); + } else { + $paneTitle = $section; + $dashletTitle = null; + } + + if (isset($options['disabled']) && mt($module->getName(), $paneName) !== $paneTitle) { + // `disabled` is checked because if it's a module's pane that's the only reason + // why it's in there. If a user utilized the same label though for a custom pane, + // it remains as is. + continue; + } + + $dashletName = null; + if ($dashletTitle !== null) { + foreach ($container->getDashlets() as $name => $url) { + if (mt($module->getName(), $name) === $dashletTitle) { + $dashletName = $name; + break; + } + } + } + + $newSection = $paneName . ($dashletName ? '.' . $dashletName : ''); + $config->removeSection($section); + $config->setSection($newSection, $options); + + Logger::info('Migrated section "%s" to "%s"', $section, $newSection); + } + } + } + } + + $config->saveIni(); + } + } + + protected function listDashboardConfigs() + { + $dashboardConfigPath = Config::resolvePath('dashboards'); + + try { + $dashboardConfigDir = opendir($dashboardConfigPath); + } catch (Exception $e) { + Logger::error('Cannot access dashboard configuration: %s', $e); + exit(1); + } + + while ($entry = readdir($dashboardConfigDir)) { + $userDashboardPath = join(DIRECTORY_SEPARATOR, [$dashboardConfigPath, $entry, 'dashboard.ini']); + if (is_file($userDashboardPath)) { + yield $userDashboardPath; + } + } + } +}