Project

General

Profile

« Previous | Next » 

Revision 611fd588

Added by Amos Benari over 10 years ago

fixes #3510 - plugin interface for registering a plugin, updated menu system

View differences:

app/assets/javascripts/topbar.js
'position': 'static'
});
$("[class^='menu_tab_']").removeClass('active');
$('.menu_tab_settings').addClass($('#current_tab').attr('data-settings'));
$('.menu_tab_'+$('#current_tab').attr('data-controller')).addClass('active').next('.dropdown').addClass('active');
mark_active_menu();
$('.dropdown-toggle').dropdown();
if(!is_mobile()){
......
}
})
//open main menu on hover
$(document).on('mouseenter', '.dropdown.menu_tab_dropdown', function(){
if(!$(this).hasClass('open')){
$(this).find('.dropdown-toggle:first').click();
}
});
function mark_active_menu() {
$("[class^='menu_tab_']").removeClass('active');
// if there is no menu for controller_action mark controller_index as active menu
var active_menu = $('.menu_tab_'+$('#current_tab').data('controller')+'_'+$('#current_tab').data('action'))
if (!active_menu.exists()){
active_menu = $('.menu_tab_'+$('#current_tab').data('controller')+'_index')
}
active_menu.addClass('active');
$('.menu_tab_dropdown').each(function(){
if ($(this).find('.active').length >0) {$(this).addClass('active')}
})
}
function is_mobile() {
var check = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
app/controllers/about_controller.rb
def index
@proxies = SmartProxy.my_proxies.includes(:features)
@compute_resources = ComputeResource.my_compute_resources
@plugins = Foreman::Plugin.all
end
end
app/controllers/api/v2/plugins_controller.rb
module Api
module V2
class PluginsController < BaseController
api :GET, '/plugins', 'List of installed plugins'
def index
@plugins = Foreman::Plugin.all
end
end
end
end
app/helpers/home_helper.rb
module HomeHelper
def class_for_setting_page
setting_options.flatten.include?(controller_name.to_sym) ? "active" : ""
end
def setting_options
authorized_menu_actions(settings_menu_items)+[[_('About'), :about]]
end
def settings_menu_items
configuration_group =
[[_('Environments'), :environments],
[_('Global Parameters'), :common_parameters],
[_('Host Groups'), :hostgroups],
[_('Puppet Classes'), :puppetclasses],
[_('Smart Variables'), :lookup_keys],
[_('Smart Proxies'), :smart_proxies]]
menu_items = [ [:group, _("Configuration"), configuration_group]]
if SETTINGS[:unattended]
provisioning_group =
[[_('Architectures'), :architectures],
[_('Compute Resources'), :compute_resources],
[_('Domains'), :domains],
[_('Hardware Models'), :models],
[_('Installation Media'), :media],
[_('Operating Systems'), :operatingsystems],
[_('Partition Tables'), :ptables],
[_('Provisioning Templates'), :config_templates],
[_('Subnets'), :subnets]]
menu_items += [[:divider], [:group, _("Provisioning"), provisioning_group]]
end
if (SETTINGS[:organizations_enabled] or SETTINGS[:locations_enabled])
menu_items += [[:divider]]
menu_items += [ [_('Locations'), :locations] ] if SETTINGS[:locations_enabled]
menu_items += [ [_('Organizations'), :organizations] ] if SETTINGS[:organizations_enabled]
end
users_group =
[[_('LDAP Authentication'), :auth_source_ldaps],
[_('Users'), :users],
[_('User Groups'), :usergroups]]
users_group += [[_('Roles'), :roles]] if User.current && User.current.admin?
menu_items += [[:divider], [:group, _("Users"), users_group] ] if SETTINGS[:login]
menu_items += [
[:divider],
[_('Bookmarks'), :bookmarks],
[_('Settings'), :settings]
]
menu_items
def render_menu menu_name
authorized_menu_actions(Menu::Manager.items(menu_name).children).map do |menu|
items = authorized_menu_actions(menu.children)
render "home/submenu", :menu_items => items, :menu_title => _(menu.caption) if items.any?
end.join(' ').html_safe
end
def authorized_menu_actions(choices)
last_item = nil
choices = choices.map do |item|
#prevent adjacent dividers
if item == [:divider]
if last_item
last_item = nil
item
end
elsif item.size == 2 && authorized_for(item[1], :index)
last_item = item
item
elsif item.size == 3
item[2] = item[2].map do |sub_item|
sub_item if authorized_for(sub_item[1], :index)
end.compact
if item[2].size > 0
last_item = item
item
end
end
last_item = Menu::Divider.new(:first_div)
choices = choices.map do |item|
last_item = case item
when Menu::Divider
item unless last_item.is_a?(Menu::Divider) #prevent adjacent dividers
when Menu::Item
item if item.authorized?
when Menu::Toggle
item if item.authorized_children.size > 0
end
end.compact
choices.pop if (choices.last == [:divider])
choices.shift if choices.first.is_a?(Menu::Divider)
choices.pop if choices.last.is_a?(Menu::Divider)
choices
end
def menu(tab, label, path = nil)
path ||= send("hash_for_#{tab}_path")
return '' unless authorized_for(path[:controller], path[:action] )
content_tag(:li, :class => "menu_tab_#{tab} ") do
link_to_if_authorized(label, path)
end
def menu_item_tag item
content_tag(:li,
link_to(_(item.caption), item.url_hash, item.html_options.merge(:id => "menu_item_#{item.name}")),
:class => "menu_tab_#{item.url_hash[:controller]}_#{item.url_hash[:action]}")
end
def org_switcher_title
......
title
end
# filters out any non allowed actions from the setting menu.
def allowed_choices choices, action = "index"
choices.map do |opt|
name, kontroller = opt
url = send("#{kontroller}_url")
authorized_for(kontroller, action) ? [name, url] : nil
end.compact.sort
end
def user_header
summary = gravatar_image_tag(User.current.mail, :class=>'gravatar small', :alt=>_('Change your avatar at gravatar.com')) +
"#{User.current.to_label} " + content_tag(:span, "", :class=>'caret')
app/services/foreman/access_control.rb
def permission(name, hash, options={})
@permissions ||= []
options.merge!(:security_block => @security_block)
options.merge!(:security_block => @security_block) if @security_block
@permissions << Permission.new(name, hash, options)
end
app/services/foreman/access_permissions.rb
map.security_block :tasks do |map|
map.permission :view_tasks, {:trends => [:show]}
end
map.security_block :plugins do |map|
map.permission :view_plugins, {:plugins => [:index],
:"api/v2/plugins" => [:index]
}
end
end
app/services/foreman/plugin.rb
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module Foreman #:nodoc:
class PluginNotFound < Foreman::Exception; end
class PluginRequirementError < Foreman::Exception; end
# Base class for Foreman plugins.
# Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
#
# Foreman::Plugin.register :example do
# name 'Example plugin'
# author 'John Smith'
# description 'This is an example plugin for Foreman'
# version '0.0.1'
# end
#
class Plugin
@registered_plugins = {}
class << self
attr_reader :registered_plugins
private :new
def def_field(*names)
class_eval do
names.each do |name|
define_method(name) do |*args|
args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
end
end
end
end
# Plugin constructor
def register(id, &block)
plugin = new(id)
if (gem = Gem.loaded_specs[id.to_s])
plugin.name gem.name
plugin.author gem.authors.join(',')
plugin.description gem.description
plugin.url gem.homepage
plugin.version gem.version.to_s
end
plugin.instance_eval(&block)
registered_plugins[id] = plugin
end
# Clears the registered plugins hash
# It doesn't unload installed plugins
def clear
@registered_plugins = {}
end
# Returns an array of all registered plugins
def all
registered_plugins.values.sort
end
# Finds a plugin by its id
def find(id)
registered_plugins[id.to_sym]
end
# Checks if a plugin is installed
#
# @param [String] id name of the plugin
def installed?(id)
registered_plugins[id.to_sym].present?
end
end
def_field :name, :description, :url, :author, :author_url, :version
attr_reader :id
def initialize(id)
@id = id.to_sym
end
def <=>(plugin)
self.id.to_s <=> plugin.id.to_s
end
def to_s
"Foreman plugin: #{id}, #{version}, #{author}, #{description}"
end
# Sets a requirement on Foreman version
# Raises a PluginRequirementError exception if the requirement is not met
# matcher format is gem dependency format
def requires_foreman(matcher)
current = SETTINGS[:version].notag
unless Gem::Dependency.new('', matcher).match?('', current)
raise PluginRequirementError.new(N_("%{id} plugin requires Foreman %{matcher} but current is %{current}" % {:id=>id, :matcher => matcher, :current=>current}))
end
true
end
# Sets a requirement on a Foreman plugin version
# Raises a PluginRequirementError exception if the requirement is not met
# matcher format is gem dependency format
def requires_foreman_plugin(plugin_name, matcher)
plugin = Plugin.find(plugin_name)
raise PluginNotFound.new(N_("%{id} plugin requires the %{plugin_name} plugin, not found") % {:id =>id, :plugin_name=>plugin_name}) unless plugin
unless Gem::Dependency.new('', matcher).match?('', plugin.version)
raise PluginRequirementError.new(N_("%{id} plugin requires the %{plugin_name} plugin %{matcher} but current is %{plugin_version}" % {:id=>id, :plugin_name=>plugin_name,:matcher=> matcher,:plugin_version=>plugin.version}))
end
true
end
# Adds an item to the given menu
# The id parameter is automatically added to the url.
# menu :menu_name, :plugin_example, 'menu text', { :controller => :example, :action => :index }
#
# name parameter can be: :top_menu or :admin_menu
#
def menu(menu, name, options={})
options.merge!(:parent => @parent) if @parent
Menu::Manager.map(menu).item(name, options)
end
alias :add_menu_item :menu
def sub_menu(menu, name, options={}, &block)
options.merge!(:parent => @parent) if @parent
Menu::Manager.map(menu).sub_menu(name, options)
current = @parent
@parent = name
self.instance_eval(&block)
@parent = current
end
# Removes item from the given menu
def delete_menu_item(menu, item)
Menu::Manager.map(menu).delete(item)
end
def security_block(name, &block)
@security_block = name
self.instance_eval(&block)
@security_block = nil
end
# Defines a permission called name for the given controller=>actions
def permission(name, hash, options={})
options.merge!(:security_block => @security_block)
Foreman::AccessControl.map do |map|
map.permission name, hash, options
end
end
# Add a new role if it doesn't exist
def role(name, permissions)
Role.transaction do
role = Role.find_or_create_by_name(name)
role.update_attribute :permissions, permissions if role.permissions.empty?
end
end
end
end
app/services/foreman/version.rb
# Simple struct for manipulation and comparing versions
class Version
attr_reader :version, :major, :minor, :build, :tag, :short
attr_reader :version, :major, :minor, :build, :tag, :short, :notag
alias :full :version
def initialize givenversion=nil
......
@version = File.read(root + "/VERSION").chomp # or fail if not found
end
@major, @minor, @build = @version.scan(/\d+/)
@tag = @version.include?('-') ? @version.split('-').last : "" rescue ""
@short = "#{@major}.#{@minor}"
if @version =~ /\A(.*)-([^-]+)\z/
@notag = $1
@tag = $2
else
@notag = @version
@tag = ""
end
end
def to_s
app/services/menu/divider.rb
module Menu
class Divider < Node
def initialize(name, options={})
@caption = options[:caption]
super name
end
def authorized?
true
end
end
end
app/services/menu/item.rb
module Menu
class Item < Node
include Rails.application.routes.url_helpers
attr_reader :name, :condition, :parent, :child_menus, :last, :html_options
def initialize(name, options)
raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym
raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call)
@name = name
@url_hash = options[:url_hash]
@condition = options[:if]
@caption = options[:caption]
@html_options = options[:html] || {}
@parent = options[:parent]
@child_menus = options[:children]
@last = options[:last] || false
super @name.to_sym
end
def url_hash
@url_hash ||= send("hash_for_#{name}_path")
@url_hash.inject({}) do |h,(key,value)|
h[key] = (value.respond_to?(:call) ? value.call : value)
h
end
end
def authorized?
User.current.allowed_to?({
:controller => url_hash[:controller].to_s.gsub(/::/, "_").underscore,
:action => url_hash[:action]
})
rescue false
end
end
end
app/services/menu/loader.rb
module Menu
class Loader
def self.load
Manager.map :user_menu do |menu|
menu.item :logout, :caption => N_('Sign out'),
:url_hash => {:controller => 'users', :action => 'logout'}
menu.item :my_account, :caption => N_('My account'),
:url_hash => {:controller => 'users', :action => 'edit', :id => Proc.new { User.current }}
end
Manager.map :admin_menu do |menu|
menu.sub_menu :administer_menu, :caption => N_('Administer') do
menu.item :locations, :caption => N_('Locations') if SETTINGS[:locations_enabled]
menu.item :organizations, :caption => N_('Organizations') if SETTINGS[:organizations_enabled]
menu.divider
if SETTINGS[:login]
menu.item :auth_source_ldaps,:caption => N_('LDAP authentication')
menu.item :users, :caption => N_('Users')
menu.item :usergroups, :caption => N_('User groups')
menu.item :roles, :caption => N_('Roles')
end
menu.divider
menu.item :bookmarks, :caption => N_('Bookmarks')
menu.item :settings, :caption => N_('Settings')
menu.item :about_index, :caption => N_('About')
end
end
Manager.map :top_menu do |menu|
menu.sub_menu :monitor_menu, :caption => N_('Monitor') do
menu.item :dashboard, :caption => N_('Dashboard')
menu.item :reports, :caption => N_('Reports'),
:url_hash => {:controller => 'reports', :action => 'index', :search => 'eventful = true'}
menu.item :fact_values, :caption => N_('Facts')
menu.item :statistics, :caption => N_('Statistics')
menu.item :trends, :caption => N_('Trends')
menu.item :audits, :caption => N_('Audits')
end
menu.sub_menu :hosts_menu, :caption => N_('Hosts') do
menu.item :hosts, :caption => N_('All hosts')
if SETTINGS[:unattended]
menu.divider :caption => N_('Provisioning Setup')
menu.item :operatingsystems,:caption => N_('Operating systems')
menu.item :config_templates,:caption => N_('Provisioning templates')
menu.item :ptables, :caption => N_('Partition tables')
menu.item :media, :caption => N_('Installation media')
menu.item :models, :caption => N_('Hardware models')
menu.item :architectures, :caption => N_('Architectures')
end
end
menu.sub_menu :configure_menu, :caption => N_('Configure') do
menu.item :hostgroups, :caption => N_('Host groups')
menu.item :common_parameters, :caption => N_('Global parameters')
menu.divider :caption => N_('Puppet')
menu.item :environments, :caption => N_('Environments')
menu.item :puppetclasses, :caption => N_('Puppet classes')
menu.item :lookup_keys, :caption => N_('Smart variables')
end
menu.sub_menu :infrastructure_menu, :caption => N_('Infrastructure') do
menu.item :smart_proxies, :caption => N_('Smart proxies')
if SETTINGS[:unattended]
menu.item :compute_resources, :caption => N_('Compute resources')
menu.item :subnets, :caption => N_('Subnets')
menu.item :domains, :caption => N_('Domains')
end
end
end
end
end
end
app/services/menu/manager.rb
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module Menu
module Manager
class << self
def map(menu_name)
@items ||= {}
mapper = Mapper.new(menu_name.to_sym, @items)
if block_given?
yield mapper
else
mapper
end
end
def items(menu_name)
# force menu reload in development when auto loading modified files
@items || Menu::Loader.load
@items[menu_name.to_sym] || Node.new(:root)
end
end
class Mapper
attr_reader :menu, :menu_items
def initialize(menu, items)
items[menu] ||= Node.new(:root)
@menu = menu
@menu_items = items[menu]
end
# Adds an item at the end of the menu. Available options:
# * param: the parameter name that is used for the project id (default is :id)
# * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
# * caption that can be:
# * a localized string Symbol
# * a String
# * a Proc that can take the project as argument
# * before, after: specify where the menu item should be inserted (eg. :after => :activity)
# * parent: menu item will be added as a child of another named menu (eg. :parent => :issues)
# * children: a Proc that is called before rendering the item. The Proc should return an array of MenuItems, which will be added as children to this item.
# eg. :children => Proc.new {|project| [Foreman::Manager::MenuItem.new(...)] }
# * last: menu item will stay at the end (eg. :last => true)
# * html_options: a hash of html options that are passed to link_to
def push(obj, options={})
parent = options[:parent] || @parent
target_root = (parent && subtree = self.find(parent)) ? subtree : @menu_items.root
# menu item position
if options[:first]
target_root.prepend(obj)
elsif (before = options[:before]) && exists?(before)
target_root.add_at(obj, position_of(before))
elsif (after = options[:after]) && exists?(after)
target_root.add_at(obj, position_of(after) + 1)
elsif options[:last]
target_root.add_last(obj)
else
target_root.add(obj)
end
end
def item(name, options={})
push(Item.new(name, options), options)
end
def sub_menu name, options={}, &block
push(Toggle.new(name, options[:caption]), options)
current = @parent
@parent = name
self.instance_eval(&block) if block_given?
@parent = current
end
def divider options={}
push(Divider.new(:divider, options), options)
end
# Removes a menu item
def delete(name)
if found = self.find(name)
@menu_items.remove!(found)
end
end
# Checks if a menu item exists
def exists?(name)
@menu_items.any? {|node| node.name == name}
end
def find(name)
@menu_items.find {|node| node.name == name}
end
def position_of(name)
@menu_items.each do |node|
if node.name == name
return node.position
end
end
end
end
end
end
app/services/menu/node.rb
module Menu
class Node
include Enumerable
attr_accessor :parent
attr_reader :name
def initialize(name, children = [])
@name = name
@children = children
@last_items_count = children.size
end
def caption
case @caption
when String
@caption
when Proc
@caption.call().to_s
when Symbol
@caption.to_s.humanize
else
@name.to_s.humanize unless @name == :divider
end
end
def authorized_children
children.map{|sub_item| sub_item if sub_item.authorized?}.compact
end
def children
if block_given?
@children.each {|child| yield child}
else
@children
end
end
# Returns the number of descendants + 1
def size
@children.inject(1) {|sum, node| sum + node.size}
end
def each &block
yield self
children { |child| child.each(&block) }
end
# Adds a child at first position
def prepend(child)
add_at(child, 0)
end
# Adds a child at given position
def add_at(child, position)
if child.is_a?(Menu::Item) && find {|node| node.name == child.name}
Rails.logger.error "Child already exists #{child.name}"
return
end
@children = @children.insert(position, child)
child.parent = self
child
end
# Adds a child as last child
def add_last(child)
add_at(child, -1)
@last_items_count += 1
child
end
# Adds a child
def add(child)
position = @children.size - @last_items_count
add_at(child, position)
end
alias :<< :add
# Removes a child
def remove!(child)
@children.delete(child)
@last_items_count -= +1 if child && child.last
child.parent = nil
child
end
# Returns the position for this node in it's parent
def position
self.parent.children.index(self)
end
# Returns the root for this node
def root
root = self
root = root.parent while root.parent
root
end
end
end
app/services/menu/toggle.rb
module Menu
class Toggle < Node
def initialize(name, caption)
@caption = caption
super name.to_sym
end
def authorized?
true
end
end
end
app/views/about/index.html.erb
<div class="stats-well">
<h4><%=_("System Status")%></h4>
<ul class="nav nav-tabs" data-tabs="tabs">
<li class="active"><a href="#available_providers" data-toggle="tab"><%= _('Available Providers') %></a></li>
<% if @proxies.any? %>
<li><a href="#smart_proxies" data-toggle="tab"><%= _('Smart Proxies') %></a></li>
<% end %>
<% if @compute_resources.any? %>
<li class="active"><a href="#smart_proxies" data-toggle="tab"><%= _('Smart Proxies') %></a></li>
<% if SETTINGS[:unattended] %>
<li><a href="#available_providers" data-toggle="tab"><%= _('Available Providers') %></a></li>
<li><a href="#compute_resources" data-toggle="tab"><%= _('Compute Resources') %></a></li>
<% end %>
<% end %>
<li><a href="#plugins" data-toggle="tab"><%= _('Plugins') %></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="available_providers">
<table class="table table-striped">
<tr>
<th><%= _("Provider") %></th>
<th><%= _("Status") %></th>
</tr>
<% ComputeResource::SUPPORTED_PROVIDERS.each do |provider| %>
<tr>
<td><%= provider %></td>
<% if ComputeResource::PROVIDERS.include?(provider) %>
<td><div class="badge badge-success"><%= _('Installed') %></div></td>
<% else %>
<td><div class="badge badge-inverse"><%= _('Not Installed') %></div></td>
<% end %>
</tr>
<% end %>
</table>
<%= _('To enable a provider, either install the OS package (e.g. foreman-libvirt) or enable the bundler group for development setup (e.g. ovirt).') %>
</div>
<% if @proxies.any? %>
<div class="tab-pane" id="smart_proxies">
<div class="tab-pane active" id="smart_proxies">
<% if @proxies.empty? %>
<p class="ca"><%= _("No smart proxies to show") %></p>
<% else %>
<% else %>
<table class="table table-striped">
<tr>
<th><%= _("Name") %></th>
<th><%= _("Features") %></th>
<th><%= _("Status") %></th>
</tr>
<% @proxies.each do |proxy| %>
<tr>
<td><%= link_to_if_authorized proxy.name, hash_for_edit_smart_proxy_path(:id => proxy.id) %></td>
<td><%=h proxy.features.to_sentence %></td>
<td><div class="proxy-status" data-url=<%= ping_smart_proxy_path(proxy) %>><%= _("Connecting..") %></div></td>
</tr>
<th><%= _("Name") %></th>
<th><%= _("Features") %></th>
<th><%= _("Status") %></th>
</tr>
<% @proxies.each do |proxy| %>
<tr>
<td><%= link_to_if_authorized proxy.name, hash_for_edit_smart_proxy_path(:id => proxy.id) %></td>
<td><%=h proxy.features.to_sentence %></td>
<td><div class="proxy-status" data-url=<%= ping_smart_proxy_path(proxy) %>><%= _("Connecting..") %></div></td>
</tr>
<% end %>
</table>
<% end %>
</table>
<% end %>
</div>
<% end %>
<% if @compute_resources.any? %>
<% if SETTINGS[:unattended] %>
<div class="tab-pane" id="available_providers">
<table class="table table-striped">
<tr>
<th><%= _("Provider") %></th>
<th><%= _("Status") %></th>
</tr>
<% ComputeResource::SUPPORTED_PROVIDERS.each do |provider| %>
<tr>
<td><%= provider %></td>
<% if ComputeResource::PROVIDERS.include?(provider) %>
<td><div class="badge badge-success"><%= _('Installed') %></div></td>
<% else %>
<td><div class="badge badge-inverse"><%= _('Not Installed') %></div></td>
<% end %>
</tr>
<% end %>
</table>
<%= _('To enable a provider, either install the OS package (e.g. foreman-libvirt) or enable the bundler group for development setup (e.g. ovirt).') %>
</div>
<div class="tab-pane" id="compute_resources">
<% if @compute_resources.empty? %>
<p class="ca"><%= _("No compute resource to show") %></p>
<% else %>
<% if @compute_resources.empty? %>
<p class="ca"><%= _("No compute resource to show") %></p>
<% else %>
<table class="table table-striped">
<tr>
<th><%= _("Name") %></th>
<th><%= _("Type") %></th>
<th><%= _("Status") %></th>
</tr>
<% @compute_resources.each do |compute| %>
<tr>
<td><%= link_to compute.name, compute %></td>
<td><%= compute.provider_friendly_name %></td>
<td><div class="compute-status" data-url=<%= ping_compute_resource_path(compute) %>><%= _("Connecting..") %></div></td>
</tr>
<% end %>
</table>
<% end %>
</div>
<% end %>
<div class="tab-pane" id="plugins">
<% if @plugins.empty? %>
<p class="ca"><%= _("No plugins found") %></p>
<% else %>
<table class="table table-striped">
<tr>
<th><%= _("Name") %></th>
<th><%= _("Type") %></th>
<th><%= _("Status") %></th>
</tr>
<% @compute_resources.each do |compute| %>
<tr>
<td><%= link_to compute.name, compute %></td>
<td><%= compute.provider_friendly_name %></td>
<td><div class="compute-status" data-url=<%= ping_compute_resource_path(compute) %>><%= _("Connecting..") %></div></td>
</tr>
<th><%= _("Name") %></th>
<th><%= _("Description") %></th>
<th><%= _("Author") %></th>
<th><%= _("Version") %></th>
</tr>
<% @plugins.each do |plugin| %>
<tr>
<td><%= plugin.url.blank? ? plugin.name : link_to(plugin.name, plugin.url, :rel=>'external')%></td>
<td><%= _(plugin.description) %></td>
<td><%= plugin.author_url.blank? ? plugin.author : link_to(plugin.author, plugin.author_url)%></td>
<td><%= plugin.version %></td>
</tr>
<% end %>
</table>
<% end %>
</table>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
<div class="span5">
<div class="stats-well">
<h4><%= _("Support") %></h4>
app/views/api/v2/plugins/index.json.rabl
collection @plugins
attributes :id, :name, :author, :description, :url, :version
app/views/home/_org_switcher.html.erb
<% if SETTINGS[:locations_enabled] || SETTINGS[:organizations_enabled] %>
<li class="dropdown org-switcher" >
<li class="dropdown org-switcher menu_tab_dropdown" >
<a href="#" class ="dropdown-toggle" data-toggle="dropdown" ><%= org_switcher_title %><span class="caret"></span></a>
<ul class="dropdown-menu">
app/views/home/_settings.html.erb
<li class="dropdown menu_tab_settings">
<% choices = setting_options %>
<%= link_to (_("More") + content_tag(:span,'', :class=>"caret")).html_safe, "#", :class => "dropdown-toggle", :'data-toggle'=>"dropdown" unless choices.empty?%>
<ul class="dropdown-menu">
<% choices.each do |item|%>
<% if item[0] == :divider %>
<%= content_tag(:li, "", :class=>"divider") %>
<% elsif item[0] == :group %>
<%= content_tag :li, :class=>"dropdown-submenu" do %>
<%= link_to(item[1],"#",:class=>"dropdown-toggle", :"data-toggle"=>"dropdown") %>
<%= content_tag :ul, :class=>"dropdown-menu" do%>
<% item[2].each do |sub_item|%>
<%= content_tag(:li, link_to(sub_item[0], {:controller => "/#{sub_item[1]}", :action => :index})) %>
<% end %>
<% end %>
<% end %>
<% else %>
<%= content_tag(:li, link_to(item[0], {:controller => "/#{item[1]}", :action => :index})) %>
<% end %>
<% end %>
</ul>
</li>
app/views/home/_submenu.html.erb
<li class="dropdown menu_tab_dropdown">
<%= link_to (menu_title + content_tag(:span, '', :class => "caret")).html_safe, "#", :class => "dropdown-toggle", :'data-toggle' => "dropdown" %>
<ul class="dropdown-menu">
<% authorized_menu_actions(menu_items).each do |item| %>
<% case item %>
<% when Menu::Item %>
<%= menu_item_tag item %>
<% when Menu::Divider %>
<%= content_tag(:li, "", :class => "divider") %>
<%= content_tag(:li, _(item.caption), :class => "nav-header") if item.caption %>
<% when Menu::Toggle %>
<%= content_tag :li, :class => "dropdown-submenu" do %>
<%= link_to(_(item.caption), "#", :class => "dropdown-toggle", :"data-toggle" => "dropdown") %>
<%= content_tag :ul, :class => "dropdown-menu" do %>
<% authorized_menu_actions(item.children).each do |sub_item| %>
<%= menu_item_tag sub_item %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
</ul>
</li>
app/views/home/_topbar.html.erb
<span class="icon-cog icon-white"></span>
</a>
<!-- menu -->
<div id='current_tab' data-controller='<%=controller_name.gsub(/_.*/,"s")%>' data-settings='<%= class_for_setting_page %>'> </div>
<%= content_tag :div,nil, :id=>'current_tab', :data=>{:controller=>controller_name, :action=>action_name} %>
<% if User.current %>
<% cache(TopbarSweeper.fragment_name) do %>
<div class="nav-collapse collapse nav1">
<ul class="nav" id="menu">
<%= render "home/org_switcher" %>
<%= menu 'dashboard', _('Dashboard') %>
<%= menu 'hosts', _('Hosts') %>
<%= menu 'reports', _('Reports'), hash_for_reports_path.merge(:search => 'eventful = true') %>
<%= menu 'facts' , _('Facts'), hash_for_fact_values_path %>
<%= menu 'audits', _('Audits'), hash_for_audits_path %>
<%= menu 'statistics', _('Statistics') %>
<%= menu 'trends', _('Trends'), hash_for_trends_path %>
</ul>
</div>
<div class="nav-collapse collapse nav2">
<ul class="nav pull-right" id="menu2">
<%= render "home/settings" %>
</ul>
</div>
<%= render "home/org_switcher" %>
<%= render_menu :top_menu %>
</ul>
</div>
<div class="nav-collapse collapse nav2">
<ul class="nav pull-right" id="menu2">
<%= render_menu :admin_menu %>
</ul>
</div>
<% end %>
<% end %>
app/views/home/_user_dropdown.html.erb
<ul class="nav pull-right ">
<li class="dropdown">
<li class="dropdown menu_tab_dropdown">
<%= user_header %>
<ul class="dropdown-menu pull-right">
<li><%= link_to(_("Sign Out"), logout_users_path) %></li>
<li><%= link_to(_("My account"), edit_user_path(User.current) )%></li>
<% authorized_menu_actions(Menu::Manager.items(:user_menu).children).map do |item| %>
<%= menu_item_tag item %>
<% end %>
</ul>
</li>
</ul>
config/initializers/foreman.rb
require 'foreman/access_permissions'
require 'foreman/default_data/loader'
require 'menu/loader'
require 'foreman/renderer'
require 'foreman/controller'
require 'net'
......
# We load the default settings for the roles if they are not already present
Foreman::DefaultData::Loader.load(false)
#load topbar
Menu::Loader.load
# clear our users topbar cache
begin
User.unscoped.pluck(:id).each do |id|
config/routes/api/v2.rb
end
end
get 'orchestration/(:id)/tasks', :to => 'tasks#index'
resources :plugins, :only => [:index]
end
match '*other', :to => 'v2/home#route_error', :constraints => ApiConstraints.new(:version => 2)
end
lib/tasks/plugins.rake
desc "List Installed plugins"
task :plugins => :environment do
puts 'Collecting plugin information'
Foreman::Plugin.all.map{ |p| puts p.to_s }
end
script/foreman-debug
add_files /etc/sysconfig/named /etc/default/bind
add_files /etc/{sysconfig,default}/libvirt*
add_files /etc/sysconfig/pgsql
add_cmd "foreman-rake plugins" "plugin_list"
qprintf "\n\n"
qprintf "%10s %s\n" "HOSTNAME:" "$(hostname -f 2>/dev/null)"
test/functional/api/v2/plugins_controller_test.rb
require 'test_helper'
class Api::V2::PluginsControllerTest < ActionController::TestCase
test "should get plugins " do
get :index
plugins = assigns(:plugins)
assert_response :success
end
end
test/integration/top_bar_test.rb
end
within("div.navbar-inner") do
assert page.has_link?("Dashboard", :href => "/dashboard")
assert page.has_link?("Hosts", :href => "/hosts")
assert page.has_link?("All hosts", :href => "/hosts")
assert page.has_link?("Reports", :href => "/reports?search=eventful+%3D+true")
assert page.has_link?("Facts", :href => "/fact_values")
assert page.has_link?("Audits", :href => "/audits")
......
test "Hosts link" do
visit root_path
within("div.navbar-inner") do
click_link("Hosts")
click_link("All hosts")
end
assert page.has_selector?('h1', :text => "Hosts")
end
test/unit/menu_item_test.rb
# Redmine - project management software
# Copyright (C) 2006-2009 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'test_helper'
module MenuItemTestHelper
# Helpers
def get_menu_item(menu_name, item_name)
Menu::Manager.items(menu_name).find {|item| item.name == item_name.to_sym}
end
end
class MenuItemTest < ActiveSupport::TestCase
include MenuItemTestHelper
Menu::Manager.map :test_menu do |menu|
menu.item(:parent)
menu.item(:child_menu, :parent => :parent)
menu.item(:child2_menu, :parent => :parent)
end
context "MenuItem#caption" do
should "be tested"
end
context "MenuItem#html_options" do
should "be tested"
end
def test_new_menu_item_with_all_required_parameters
assert Menu::Item.new(:test_good_menu, :url_hash => {:controller=>'test', :action=>'index'}, :after => :me)
end
def test_new_menu_item_should_require_a_proc_to_use_for_the_if_condition
assert_raises ArgumentError do
Menu::Item.new(:test_error, :if => ['not_a_proc'] )
end
assert Menu::Item.new(:test_good_if, :if => Proc.new{})
end
def test_new_menu_item_should_allow_a_hash_for_extra_html_options
assert_raises ArgumentError do
Menu::Item.new(:test_error, :html => ['not_a_hash'])
end
assert Menu::Item.new(:test_good_html, :html => { :onclick => 'doSomething' })
end
def test_new_menu_item_should_require_a_proc_to_use_the_children_option
assert_raises ArgumentError do
Menu::Item.new(:test_error, :children => ['not_a_proc'])
end
assert Menu::Item.new(:test_good_children, :children => Proc.new{} )
end
def test_new_should_not_allow_setting_the_parent_item_to_the_current_item
assert_raises ArgumentError do
Menu::Item.new(:test_error, :parent => :test_error )
end
end
def test_has_children
parent_item = get_menu_item(:test_menu, :parent)
assert parent_item.children.present?
assert_equal 2, parent_item.children.size
assert_equal get_menu_item(:test_menu, :child_menu), parent_item.children[0]
assert_equal get_menu_item(:test_menu, :child2_menu), parent_item.children[1]
end
end
test/unit/menu_manager_test.rb
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'test_helper'
class MenuManagerTest < ActiveSupport::TestCase
def test_map_should_yield_a_mapper
assert_difference 'Menu::Manager.items(:test_menu).size' do
Menu::Manager.map :test_menu do |mapper|
assert_kind_of Menu::Manager::Mapper, mapper
mapper.item :new_item
end
end
end
def test_items_should_return_menu_items
items = Menu::Manager.items(:test_menu)
assert_kind_of Menu::Node, items.first
end
end
test/unit/menu_mapper_test.rb
# Redmine - project management software
# Copyright (C) 2006-2013 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'test_helper'
class MenuMapperTest < ActiveSupport::TestCase
test "Mapper#initialize should define a root MenuNode if menu is not present in items" do
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {})
node = menu_mapper.menu_items
assert_not_nil node
assert_equal :root, node.name
end
test "Mapper#initialize should use existing MenuNode if present" do
node = "foo" # just an arbitrary reference
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {:test_menu => node})
assert_equal node, menu_mapper.menu_items
end
def test_push_onto_root
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {})
menu_mapper.item :test_overview, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.exists?(:test_overview)
end
def test_push_onto_parent
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {})
menu_mapper.item :test_overview, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_child, :url_hash => { :controller => 'hosts', :action => 'show'}, :parent => :test_overview
assert menu_mapper.exists?(:test_child)
assert_equal :test_child, menu_mapper.find(:test_child).name
end
def test_push_onto_grandparent
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {})
menu_mapper.item :test_overview, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_child, :url_hash => { :controller => 'hosts', :action => 'show'}, :parent => :test_overview
menu_mapper.item :test_grandchild, :url_hash => { :controller => 'hosts', :action => 'show'}, :parent => :test_child
assert menu_mapper.exists?(:test_grandchild)
grandchild = menu_mapper.find(:test_grandchild)
assert_equal :test_grandchild, grandchild.name
assert_equal :test_child, grandchild.parent.name
end
def test_push_first
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {})
menu_mapper.item :test_second, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_third, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_fourth, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_fifth, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_first, :url_hash => { :controller => 'hosts', :action => 'show'}, :first => true
root = menu_mapper.find(:root)
assert_equal 5, root.children.size
{0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
assert_not_nil root.children[position]
assert_equal name, root.children[position].name
end
end
def test_push_before
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {})
menu_mapper.item :test_first, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_second, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_fourth, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_fifth, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_third, :url_hash => { :controller => 'hosts', :action => 'show'}, :before => :test_fourth
root = menu_mapper.find(:root)
assert_equal 5, root.children.size
{0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
assert_not_nil root.children[position]
assert_equal name, root.children[position].name
end
end
def test_push_after
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {})
menu_mapper.item :test_first, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_second, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_third, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_fifth, :url_hash => { :controller => 'hosts', :action => 'show'}
menu_mapper.item :test_fourth, :url_hash => { :controller => 'hosts', :action => 'show'}, :after => :test_third
root = menu_mapper.find(:root)
assert_equal 5, root.children.size
{0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
assert_not_nil root.children[position]
assert_equal name, root.children[position].name
end
end
def test_push_last
menu_mapper = Menu::Manager::Mapper.new(:test_menu, {})
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff