diff --git a/application/layouts/scripts/parts/menu.phtml b/application/layouts/scripts/parts/menu.phtml
new file mode 100644
index 000000000..14b5bc0e7
--- /dev/null
+++ b/application/layouts/scripts/parts/menu.phtml
@@ -0,0 +1,16 @@
+sub) ? '
' : '' ?>
+ items as $item): ?>
+
+
+
\ No newline at end of file
diff --git a/application/layouts/scripts/parts/navigation.phtml b/application/layouts/scripts/parts/navigation.phtml
index 025587b2c..f64fb92ed 100755
--- a/application/layouts/scripts/parts/navigation.phtml
+++ b/application/layouts/scripts/parts/navigation.phtml
@@ -1,51 +1,17 @@
getRequest()->getRequestUri();
-$currentKey = isset($this->navkey) ? $this->navkey : $url;
+$url = Icinga\Web\Url::fromRequest()->getRelativeUrl();
+$menu = Icinga\Web\Menu::fromConfig();
-$item = $this->navigation->keys("menu");
-?>
-auth()->isAuthenticated()): ?>
-
-
-
- auth()->isAuthenticated()):
+ echo $this->partial(
+ 'parts/menu.phtml',
+ array(
+ 'items' => $menu->getChildren(),
+ 'sub' => false,
+ 'url' => $url
+ )
+ );
+endif;
- $item = $this->navigation->menu->$itemName;
- $active = false;
- $url = "";
- $iconClass = '';
-
- if (is_string($item)) {
- $active = !$activeSet && $this->baseUrl($item) == $currentKey;
- $url = $this->baseUrl($item);
- } else {
- $url = $this->baseUrl(isset($item->route) ? $item->route : "");
- $itemName = isset($item->title) ? $item->title : $itemName;
- $active = !$activeSet && (isset($item->key) ? $item->key : $url) === $currentKey;
- if (isset($item->iconClass)) {
- $iconClass = $item->iconClass;
- }
- }
- $activeSet = $activeSet || $active;
- ?>
- - ">
-
-
-
-
- = $itemName ?>
-
-
-
-
- endif ?>
+?>
\ No newline at end of file
diff --git a/config/menu.ini b/config/menu.ini
index ffb8fffba..e2c505e73 100755
--- a/config/menu.ini
+++ b/config/menu.ini
@@ -1,9 +1,22 @@
-[menu]
-Dashboard.title = "Dashboard"
-Dashboard.route = "/dashboard/index"
-Dashboard.iconClass = "icinga-icon-dashboard-petrol"
-
-Configuration.title = "Configuration"
-Configuration.route = "/config/index"
-Configuration.iconClass = "icinga-icon-configuration-petrol"
+;[menu]
+;Dashboard.title = "Dashboard"
+;Dashboard.route = "/dashboard/index"
+;Dashboard.iconClass = "icinga-icon-dashboard-petrol"
+;
+;Configuration.title = "Configuration"
+;Configuration.route = "/config/index"
+;Configuration.iconClass = "icinga-icon-configuration-petrol"
+[Home]
+title = "Home"
+url = "/index/welcome"
+priority = "1"
+icon = "/img/icons/down.png"
+
+[Dashboard]
+title = "Dashboard"
+url = "/dashboard/index"
+priority = "5"
+icon = "/img/icons/askgoogle.jpg"
+icon_class = "testclass"
+
\ No newline at end of file
diff --git a/config/modules/monitoring/menu.ini b/config/modules/monitoring/menu.ini
index e54c3c819..55813a4b0 100755
--- a/config/modules/monitoring/menu.ini
+++ b/config/modules/monitoring/menu.ini
@@ -1,49 +1,52 @@
-[menu]
-;Remove component as of #4583 since it's not working
-;Issues.title = "Issues" ; Extended version
-;Issues.route = "/monitoring/list/services?problems=1&sort=severity" ; Explicit route
-;Issues.key = "issues" ; When this key is set in the controller, the item is active
+; title
+; url
+; priority
+; icon
+; icon class
-;Remove component as of #4583 since it's not working
-;Changes.title = "Recent Changes"
-;Changes.route = "/monitoring/list/services?sort=service_last_state_change"
-;_1 = 1 ;Spacer after this section
+[Hosts]
+title = "Hosts"
+url = "/monitoring/index"
+priority = "2"
+icon = ""
-Hosts.title = "Hosts"
-Hosts.route = "/monitoring/list/hosts"
-Hosts.iconClass = "icinga-icon-host-petrol"
+[Hosts.Problems]
+url = "/monitoring/index?grp=probs"
+priority = "1"
+icon = ""
-Services.title = "Services"
-Services.route = "/monitoring/list/services"
-Services.iconClass = "icinga-icon-service-petrol"
+[Hosts.Mail]
+priority = "2"
+icon = ""
-Downtimes.title = "Downtimes"
-Downtimes.route = "/monitoring/list/downtimes"
-Downtimes.iconClass = "icinga-icon-down-petrol"
+[Hosts.Exchange]
+title = "Hosts Exchange"
+url = "/monitoring/index?grp=exchange"
+priority = "5"
+icon = ""
-Notifications.title = "Notifications"
-Notifications.route = "/monitoring/list/notifications"
-Notifications.iconClass = "icinga-icon-notification-petrol"
+[Cluster]
+title = "Cluster"
+url = "/monitoring/index?grp=cluster"
+priority = "10"
+icon = ""
-Comments.title = "Comments"
-Comments.route = "/monitoring/list/comments"
-Comments.iconClass = "icinga-icon-comment-petrol"
+[Cluster.Cluster1]
+title = "Cluster Cluster1"
+url = "#"
+priority = "1"
+icon = ""
-;Contacts = "/monitoring/list/contacts"
+[Cluster.Cluster2]
+title = "Cluster 2"
+url = "#"
+priority = "2"
+icon = ""
-;Contact Groups = "/monitoring/list/contactgroups"
-Servicegroups.title = "Servicegroups"
-Servicegroups.route = "/monitoring/list/servicegroups"
-Servicegroups.iconClass = "icinga-icon-servicegroup-petrol"
+[Hosts.Exchange.Ex1]
+title = "Hosts Exchange Ex1"
+url = "#"
+priority = "2"
+icon = ""
-Hostgroups.title = "Hostgroups"
-Hostgroups.route = "/monitoring/list/hostgroups"
-Hostgroups.iconClass = "icinga-icon-hostgroup-petrol"
-
-History.title = "History"
-History.route = "/monitoring/list/eventhistory"
-History.iconClass = "icinga-icon-history-petrol"
-
-Performance.title = "Performance"
-Performance.route ="/monitoring/process/performance"
diff --git a/config/modules/monitoring/menu.ini.in b/config/modules/monitoring/menu.ini.in
new file mode 100644
index 000000000..ef874f6a7
--- /dev/null
+++ b/config/modules/monitoring/menu.ini.in
@@ -0,0 +1,69 @@
+[menu]
+;Remove component as of #4583 since it's not working
+;Issues.title = "Issues" ; Extended version
+;Issues.route = "/monitoring/list/services?problems=1&sort=severity" ; Explicit route
+;Issues.key = "issues" ; When this key is set in the controller, the item is active
+
+;Remove component as of #4583 since it's not working
+;Changes.title = "Recent Changes"
+;Changes.route = "/monitoring/list/services?sort=service_last_state_change"
+;_1 = 1 ;Spacer after this section
+
+Hosts.title = "Hosts"
+Hosts.route = "/monitoring/list/hosts"
+Hosts.iconClass = "icinga-icon-host-petrol"
+
+Services.title = "Services"
+Services.route = "/monitoring/list/services"
+Services.iconClass = "icinga-icon-service-petrol"
+
+Downtimes.title = "Downtimes"
+Downtimes.route = "/monitoring/list/downtimes"
+Downtimes.iconClass = "icinga-icon-down-petrol"
+
+Notifications.title = "Notifications"
+Notifications.route = "/monitoring/list/notifications"
+Notifications.iconClass = "icinga-icon-notification-petrol"
+
+Comments.title = "Comments"
+Comments.route = "/monitoring/list/comments"
+Comments.iconClass = "icinga-icon-comment-petrol"
+
+;Contacts = "/monitoring/list/contacts"
+
+;Contact Groups = "/monitoring/list/contactgroups"
+
+Servicegroups.title = "Servicegroups"
+Servicegroups.route = "/monitoring/list/servicegroups"
+Servicegroups.iconClass = "icinga-icon-servicegroup-petrol"
+
+Hostgroups.title = "Hostgroups"
+Hostgroups.route = "/monitoring/list/hostgroups"
+Hostgroups.iconClass = "icinga-icon-hostgroup-petrol"
+
+History.title = "History"
+History.route = "/monitoring/list/eventhistory"
+History.iconClass = "icinga-icon-history-petrol"
+
+Performance.title = "Performance"
+Performance.route ="/monitoring/process/performance"
+
+
+[Hosts]
+; New section
+; Title property unset means title is "Hosts"
+url="#"
+iconClass="icon-hosts"
+priority=1
+
+[Hosts.Problems]
+; New section beneath section hosts
+title="Problem Hosts"
+url="/monitoring/list/hosts?problem=1"
+priority=2
+
+[Hosts.A Link]
+title="Wiki"
+url="https://wiki.somewhere.com"
+priority=1
+icon="https://wiki.somewhere.com/icon.png"
\ No newline at end of file
diff --git a/library/Icinga/Web/Menu.php b/library/Icinga/Web/Menu.php
new file mode 100644
index 000000000..e7727e1fe
--- /dev/null
+++ b/library/Icinga/Web/Menu.php
@@ -0,0 +1,383 @@
+
+ * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
+ * @author Icinga Development Team
+ *
+ */
+// {{{ICINGA_LICENSE_HEADER}}}
+
+namespace Icinga\Web;
+
+use Icinga\Application\Config;
+use Icinga\Application\Modules\Manager as ModulManager;
+use Icinga\Application\Icinga;
+
+class MenuItem
+{
+ /**
+ * MenuItem name
+ *
+ * @type string
+ */
+ private $name;
+
+ /**
+ * MenuItem titel
+ *
+ * @type string
+ */
+ private $title;
+
+ /**
+ * MenuItem priority
+ *
+ * @type int
+ */
+ private $priority;
+
+ /**
+ * MenuItem url
+ *
+ * @type string
+ */
+ private $url;
+
+ /**
+ * MenuItem icon path
+ *
+ * @type string
+ */
+ private $icon;
+
+ /**
+ * MenuItem icon class
+ *
+ * @type string
+ */
+ private $icon_class;
+
+ /**
+ * MenuItem children array
+ *
+ * @type array
+ */
+ private $children = array();
+
+
+ /**
+ * Create a new MenuItem
+ */
+ public function __construct($name='', $title='')
+ {
+ ($name != '') ? $this->name = $name: $this->name = $title;
+ $this->title = $title;
+ $this->priority = 100;
+ }
+
+ /**
+ * Set name for MenuItems
+ *
+ * @param string name
+ *
+ * @return self
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Get name from MenuItem
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set title for MenuItems
+ *
+ * @param string title
+ *
+ * @return self
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+
+ /**
+ * Get title from MenuItem
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title ? $this->title : $this->name;
+ }
+
+ /**
+ * Set priority for MenuItems
+ *
+ * @param int priority
+ *
+ * @return self
+ */
+ public function setPriority($priority)
+ {
+ $this->priority = $priority;
+ return $this;
+ }
+
+ /**
+ * Get priority from MenuItem
+ *
+ * @return int
+ */
+ public function getPriority()
+ {
+ return $this->priority;
+ }
+
+ /**
+ * Set url for MenuItems
+ *
+ * @param string url
+ *
+ * @return self
+ */
+ public function setUrl($url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ /**
+ * Get url from MenuItem
+ *
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set icon for MenuItems
+ *
+ * @param string icon
+ *
+ * @return self
+ */
+ public function setIcon($icon)
+ {
+ $this->icon = $icon;
+ return $this;
+ }
+
+ /**
+ * Get icon from MenuItem
+ *
+ * @return string
+ */
+ public function getIcon()
+ {
+ return $this->icon;
+ }
+
+ /**
+ * Set icon class for MenuItems
+ *
+ * @param string icon class
+ *
+ * @return self
+ */
+ public function setIconClass($iconClass)
+ {
+ $this->icon_class = $iconClass;
+ return $this;
+ }
+
+ /**
+ * Get icon class from MenuItem
+ *
+ * @return string
+ */
+ public function getIconClass()
+ {
+ return $this->icon_class;
+ }
+
+ /**
+ * Add a child to MenuItem
+ *
+ * @param string identifier
+ * @param array props
+ *
+ * @return self
+ */
+ public function addChild($identifier, $props)
+ {
+ if (false === ($pos = strpos($identifier, '.'))) {
+ $menuItem = new MenuItem($identifier);
+ $menuItem
+ ->setTitle($props->title)
+ ->setPriority($props->priority)
+ ->setUrl($props->url)
+ ->setIcon($props->icon)
+ ->setIconClass($props->icon_class);
+ $this->children[$identifier] = $menuItem;
+ } else {
+ $parent = substr($identifier, 0, $pos);
+ $identifier = substr($identifier, $pos + 1);
+ $this->getChild($parent)->addChild($identifier, $props);
+ }
+ return $this;
+ }
+
+ /**
+ * Check if MenuItem has Childs
+ *
+ * @return bool
+ */
+ public function hasChildren()
+ {
+ return ! empty($this->children);
+ }
+
+ /**
+ * Get children from MenuItem
+ *
+ * return all children proper sortet of the current MenuItem
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ usort($this->children, array($this, 'cmpChildren'));
+ return $this->children;
+ }
+
+ /**
+ * Get child from MenuItem
+ *
+ * @param string identifier
+ *
+ * @return MenuItem
+ */
+ public function getChild($identifier)
+ {
+ return $this->children[$identifier];
+ }
+
+
+ /**
+ * Compare children
+ *
+ * compare two children against each other based on priority and name
+ *
+ * @return array
+ */
+ protected function cmpChildren($a, $b)
+ {
+ if ($a->priority == $b->priority) {
+ return ($a->getTitle() > $b->getTitle()) ? +1 : -1;
+ }
+ return ($a->priority > $b->priority) ? +1 : -1;
+ }
+}
+
+
+class Menu extends MenuItem
+{
+ /**
+ * ZendConfig cfg
+ *
+ * @type Config
+ */
+ private $cfg;
+
+ /**
+ * Config
+ *
+ * @type config
+ */
+ private $config;
+
+ /**
+ * Load MenuItems from all Configs
+ *
+ * @return Menu
+ */
+ public static function fromConfig(){
+ $menu = new Menu();
+ $manager = Icinga::app()->getModuleManager();
+ $menu->cfg[] = Config::app('menu');
+ foreach ($manager->listEnabledModules() as $moduleName)
+ {
+ $menu->cfg[] = Config::module($moduleName, 'menu');
+ }
+ $menu->mergeConfigs();
+ $menu->loadMenuItems();
+ return $menu;
+ }
+
+ /**
+ * Merge all configs
+ *
+ * merge all configs and set suffix if duplicate entry exist
+ *
+ */
+ private function mergeConfigs()
+ {
+ $this->config = array();
+ foreach ($this->cfg as $config){
+ foreach ($config as $identifier => $keys){
+ while (array_key_exists($identifier, $this->config)) {
+ $identifier .= '_dup';
+ }
+ $this->config[$identifier] = $keys;
+ }
+ }
+ ksort($this->config);
+ }
+
+ /**
+ * Load menu items
+ *
+ * load MenuItems based on the merged config
+ */
+ private function loadMenuItems()
+ {
+ foreach ($this->config as $id => $props) {
+ $this->addChild($id, $props);
+ }
+ }
+
+}
diff --git a/public/css/icinga/navigation.less b/public/css/icinga/navigation.less
index b6b2f4ce7..4f2ff7a34 100644
--- a/public/css/icinga/navigation.less
+++ b/public/css/icinga/navigation.less
@@ -76,6 +76,24 @@
.nav-stacked > li:first-child {
}
+.submenu {
+ background-image: none;
+ padding-left: 15px;
+}
+
+.submenu > a {
+ color: red;
+}
+
+.testclass > a{
+ color: green;
+}
+
+.active > a{
+ font-weight: bold;
+ text-decoration: underline;
+}
+
.icinga-subnavigation > li {
padding-top: 8px;
padding-bottom: 8px;
@@ -185,3 +203,8 @@ ul.icinga-subnavigation {
padding-left: 15px;
margin-top: 12px;
}
+
+.nav > .subclass {
+ text-decoration: underline;
+ font-size: 16px;
+}
\ No newline at end of file