diff options
86 files changed, 4539 insertions, 697 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..e0f7431c5e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "chef-server-slice/public/javascripts/jsoneditor"] + path = chef-server-slice/public/javascripts/jsoneditor + url = git://github.com/iterationlabs/jsoneditor.git +[submodule "chef-server-slice/public/web-app-theme"] + path = chef-server-slice/public/web-app-theme + url = git://github.com/pilu/web-app-theme.git diff --git a/README.rdoc b/README.rdoc index a7a4ea8dea..3cd1d8ffc2 100644 --- a/README.rdoc +++ b/README.rdoc @@ -4,9 +4,7 @@ == DESCRIPTION: -Chef is a systems management framework masquerading as a configuration management tool. - -I'm in ur netwerk, cookin up yer servers. :) +Chef is a systems integration framework masquerading as a configuration management tool. == REQUIREMENTS: @@ -19,7 +17,7 @@ chef: * stomp * ohai -chef-server and the chefserverslice (merb slice), same requires as chef above, plus: +chef-server and the chef-server-slice (merb slice), same requires as chef above, plus: * stompserver * ferret @@ -30,15 +28,6 @@ chef-server and the chefserverslice (merb slice), same requires as chef above, p * ruby-openid * syntax -Interim Note: - -Once the {chef,chef-server,chefserverslice}.gem set is installed, do the following: - - > cd /Library/Ruby/Gems/1.8/gems/chef-server<xxx> # or wherever your gems land - > sudo rake slices:chefserverslice:install - -This installs the chefserverslice into the chef-server merb application - External Servers: * stompserver (for easy stomp mq testing) @@ -46,7 +35,7 @@ External Servers: == INSTALL: -Install all of the above. To fire up a develpment environment, do the following: +Install all of the above. To fire up a development environment, do the following: * Start CouchDB with 'couchdb' * Start stompserver with 'stompserver' @@ -57,10 +46,13 @@ Install all of the above. To fire up a develpment environment, do the following * Start chef-server: chef-server -N -c 2 + OR + (in chef-server-slice) + slice -N -c 2 * Test run chef to begin node registration: - sudo ./bin/chef-client + chef-client * Validate the node registration: @@ -2,15 +2,22 @@ gems = %w[chef chef-server-slice chef-server] require 'rubygems' require 'cucumber/rake/task' +namespace :git do + desc "Initialise and update the Git submodules" + task :submodule_update do + sh "git submodule update --init" + end +end + desc "Build the chef gems" -task :gem do +task :gem => "git:submodule_update" do gems.each do |dir| Dir.chdir(dir) { sh "rake package" } end end desc "Install the chef gems" -task :install do +task :install => "git:submodule_update" do gems.each do |dir| Dir.chdir(dir) { sh "rake install" } end diff --git a/chef-server-slice/app/controllers/application.rb b/chef-server-slice/app/controllers/application.rb index a87ed92a5d..de03021917 100644 --- a/chef-server-slice/app/controllers/application.rb +++ b/chef-server-slice/app/controllers/application.rb @@ -107,7 +107,7 @@ class ChefServerSlice::Application < Merb::Controller case content_type when :html store_location - redirect url(:openid_consumer) + redirect slice_url(:openid_consumer), :message => { :error => "You don't have access to that, please login."} else raise Unauthorized, "You must authenticate first!" end diff --git a/chef-server-slice/app/controllers/nodes.rb b/chef-server-slice/app/controllers/nodes.rb index 52ee441121..03b343767a 100644 --- a/chef-server-slice/app/controllers/nodes.rb +++ b/chef-server-slice/app/controllers/nodes.rb @@ -38,8 +38,8 @@ class ChefServerSlice::Nodes < ChefServerSlice::Application rescue Net::HTTPServerException => e raise NotFound, "Cannot load node #{params[:id]}" end - if params[:ajax] == "true" - render JSON.pretty_generate(@node), :layout=>false + if request.xhr? + render JSON.pretty_generate(@node), :layout => false else display @node end @@ -57,7 +57,7 @@ class ChefServerSlice::Nodes < ChefServerSlice::Application end def update - if params[:ajax] + if request.xhr? @node = JSON.parse(params[:value]) else @node = params.has_key?("inflated_object") ? params["inflated_object"] : nil @@ -66,8 +66,8 @@ class ChefServerSlice::Nodes < ChefServerSlice::Application if @node @status = 202 @node.save - if params[:ajax] - partial("nodes/node", :node => @node) + if request.xhr? + partial :node, :node => @node else display @node end diff --git a/chef-server-slice/app/controllers/openid_consumer.rb b/chef-server-slice/app/controllers/openid_consumer.rb index 98abe8e74a..f2f232eae5 100644 --- a/chef-server-slice/app/controllers/openid_consumer.rb +++ b/chef-server-slice/app/controllers/openid_consumer.rb @@ -26,7 +26,11 @@ class ChefServerSlice::OpenidConsumer < ChefServerSlice::Application provides :html, :json def index - render + if request.xhr? + render :layout => false + else + render + end end def start diff --git a/chef-server-slice/app/controllers/openid_register.rb b/chef-server-slice/app/controllers/openid_register.rb index bc4ff618f8..d337790c1f 100644 --- a/chef-server-slice/app/controllers/openid_register.rb +++ b/chef-server-slice/app/controllers/openid_register.rb @@ -25,6 +25,8 @@ class ChefServerSlice::OpenidRegister < ChefServerSlice::Application provides :html, :json before :fix_up_node_id + before :login_required, :only => [ :update, :destroy, :validate, :admin ] + before :authorized_node, :only => [ :update, :destroy, :validate, :admin ] def index @headers['X-XRDS-Location'] = Chef::Config[:openid_url] + "/openid/server/server/xrds" diff --git a/chef-server-slice/app/controllers/search_entries.rb b/chef-server-slice/app/controllers/search_entries.rb index 371a024dc3..dfbc724ad2 100644 --- a/chef-server-slice/app/controllers/search_entries.rb +++ b/chef-server-slice/app/controllers/search_entries.rb @@ -26,7 +26,7 @@ class ChefServerSlice::SearchEntries < ChefServerSlice::Application def index @s = Chef::Search.new - @entries = @s.search(params[:search_id], "?*") + @entries = @s.search(params[:search_id]) display @entries end @@ -57,7 +57,7 @@ class ChefServerSlice::SearchEntries < ChefServerSlice::Application def destroy @s = Chef::Search.new - @entries = @s.search(params[:id], "?*") + @entries = @s.search(params[:id]) @entries.each do |entry| Chef::Queue.send_msg(:queue, :remove, entry) end diff --git a/chef-server-slice/app/helpers/cookbooks_helper.rb b/chef-server-slice/app/helpers/cookbooks_helper.rb index 49d5010824..76efc7c765 100644 --- a/chef-server-slice/app/helpers/cookbooks_helper.rb +++ b/chef-server-slice/app/helpers/cookbooks_helper.rb @@ -22,9 +22,9 @@ module Merb def syntax_highlight(code) converter = Syntax::Convertors::HTML.for_syntax "ruby" if File.exists?(code) - converter.convert(File.read(code)) + converter.convert(File.read(code), false) else - converter.convert(code) + converter.convert(code, false) end end end diff --git a/chef-server-slice/app/helpers/global_helpers.rb b/chef-server-slice/app/helpers/global_helpers.rb index 77e51fca8a..645343a6a7 100644 --- a/chef-server-slice/app/helpers/global_helpers.rb +++ b/chef-server-slice/app/helpers/global_helpers.rb @@ -33,6 +33,7 @@ module Merb def node_escape(node) node.gsub(/\./, '_') end + end end end diff --git a/chef-server-slice/app/helpers/nodes_helper.rb b/chef-server-slice/app/helpers/nodes_helper.rb index 258aa944bc..3409f44f09 100644 --- a/chef-server-slice/app/helpers/nodes_helper.rb +++ b/chef-server-slice/app/helpers/nodes_helper.rb @@ -1,5 +1,6 @@ # # Author:: Adam Jacob (<adam@opscode.com>) +# Author:: AJ Christensen (<aj@junglist.gen.nz>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 # @@ -22,7 +23,7 @@ module Merb def recipe_list(node) response = "" node.recipes.each do |recipe| - response << "<li>#{recipe}</li>" + response << "<li>#{recipe}</li>\n" end response end @@ -30,11 +31,35 @@ module Merb def attribute_list(node) response = "" node.each_attribute do |k,v| - response << "<li><b>#{k}</b>: #{v}</li>" + response << "<li><b>#{k}</b>: #{v}</li>\n" end response end + + # Recursively build a tree of lists. + def build_tree(node) + list = "<dl>" + list << "\n<!-- Beginning of Node Tree -->" + walk = lambda do |key,value| + case value + when Hash, Array + list << "\n<!-- Beginning of Enumerable obj -->" + list << "\n<dt>#{key}</dt>" + list << "<dd>" + list << "\t<dl>\n" + value.each(&walk) + list << "\t</dl>\n" + list << "</dd>" + list << "\n<!-- End of Enumerable obj -->" + + else + list << "\n<dt>#{key}</dt>" + list << "<dd>#{value}</dd>" + end + end + node.attribute.sort{ |a,b| a[0] <=> b[0] }.each(&walk) + list << "</dl>" + end end - end end diff --git a/chef-server-slice/app/helpers/search_helper.rb b/chef-server-slice/app/helpers/search_helper.rb index a3e5846918..d7de6e6d70 100644 --- a/chef-server-slice/app/helpers/search_helper.rb +++ b/chef-server-slice/app/helpers/search_helper.rb @@ -1,7 +1,38 @@ module Merb module ChefServerSlice module SearchHelper - + def output_path(attributes) + res = Hash.new + attributes.each do |path| + parts = path.split("/") + unless parts[0].nil? + parts.shift if parts[0].length == 0 + end + res[path] = ohai_walk(parts) + end + res + end + + def ohai_walk(path) + unless path[0] + @@ohai.to_json + else + ohai_walk_r(@@ohai, path) + end + end + + def ohai_walk_r(ohai, path) + hop = (ohai.is_a?(Array) ? path.shift.to_i : path.shift) + if ohai[hop] + if path[0] + ohai_walk_r(ohai[hop], path) + else + ohai[hop].to_json + end + else + nil + end + end end end end # Merb diff --git a/chef-server-slice/app/views/cookbooks/_attribute_file.html.haml b/chef-server-slice/app/views/cookbooks/_attribute_file.html.haml deleted file mode 100644 index eb91921bf0..0000000000 --- a/chef-server-slice/app/views/cookbooks/_attribute_file.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%h3= name -%pre= contents
\ No newline at end of file diff --git a/chef-server-slice/app/views/cookbooks/_syntax_highlight.html.haml b/chef-server-slice/app/views/cookbooks/_syntax_highlight.html.haml deleted file mode 100644 index 1f769d05f1..0000000000 --- a/chef-server-slice/app/views/cookbooks/_syntax_highlight.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%h3= name -%div.ruby= syntax_highlight(contents)
\ No newline at end of file diff --git a/chef-server-slice/app/views/cookbooks/attribute_files.html.haml b/chef-server-slice/app/views/cookbooks/attribute_files.html.haml deleted file mode 100644 index c0751560f4..0000000000 --- a/chef-server-slice/app/views/cookbooks/attribute_files.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -- throw_content(:header, "All Attributes Files") -- @attribute_files.each do |af_hash| - %h2= "#{af_hash[:cookbook]}" - = partial(:attribute_file, :name => af_hash[:name], :contents => af_hash[:contents])
\ No newline at end of file diff --git a/chef-server-slice/app/views/cookbooks/index.html.haml b/chef-server-slice/app/views/cookbooks/index.html.haml index 51874ac2fd..d8e9eabda6 100644 --- a/chef-server-slice/app/views/cookbooks/index.html.haml +++ b/chef-server-slice/app/views/cookbooks/index.html.haml @@ -1,7 +1,10 @@ -- throw_content(:header, "Cookbooks") -- @cl.each do |cookbook| - .index - %table - %tr - %td - %a{ :href => slice_url(:cookbook, { :id => cookbook.name }) }= cookbook.name +.block#block-tables + .content + %h2.title Cookbooks + .inner + %table.table + %tr + %th.first Cookbook Name + - @cl.sort{ |a,b| a.name.to_s <=> b.name.to_s }.each_with_index do |cookbook, index| + %tr{:class => "#{index % 2 == 1 ? 'odd' : 'even'}"} + %td= link_to cookbook.name, slice_url(:cookbook, { :id => cookbook.name })
\ No newline at end of file diff --git a/chef-server-slice/app/views/cookbooks/show.html.haml b/chef-server-slice/app/views/cookbooks/show.html.haml index c9249609c1..03b39d55ab 100644 --- a/chef-server-slice/app/views/cookbooks/show.html.haml +++ b/chef-server-slice/app/views/cookbooks/show.html.haml @@ -1,28 +1,40 @@ -- throw_content(:header, "Cookbook #{h @cookbook.name}") -.cookbook -- if @cookbook.lib_files.length > 0 - %h2 Library Files - - @cookbook.lib_files.each do |f| - = partial(:syntax_highlight, :name => File.basename(f), :contents => f) - -- if @cookbook.attribute_files.length > 0 - %h2 Attribute Files - - @cookbook.attribute_files.each do |f| - = partial(:syntax_highlight, :name => File.basename(f), :contents => f) - -- if @cookbook.definition_files.length > 0 - %h2 Definition Files - - @cookbook.definition_files.each do |f| - = partial(:syntax_highlight, :name => File.basename(f), :contents => f) - -- if @cookbook.recipe_files.length > 0 - %h2 Recipe Files - - @cookbook.recipe_files.each do |f| - = partial(:syntax_highlight, :name => File.basename(f), :contents => f) - -- if @cookbook.template_files.length > 0 - %h2 Template Files - - @cookbook.template_files.each do |f| - %h3= File.basename(f) - %div= syntax_highlight(f) - +.block#block-text + .content + %h2.title= "Cookbook #{h @cookbook.name}" + .inner + .accordion + - unless @cookbook.lib_files.empty? + %h2.head= link_to "Library Files", "#" + .files + - @cookbook.lib_files.each do |f| + .code + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.attribute_files.empty? + %h2.head= link_to "Attribute Files", "#" + .files + - @cookbook.attribute_files.each do |f| + .code + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.definition_files.empty? + %h2.head= link_to "Definition Files", "#" + .files + - @cookbook.definition_files.each do |f| + .code + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.recipe_files.empty? + %h2.head= link_to "Recipe Files", "#" + .files + - @cookbook.recipe_files.each do |f| + .code + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.template_files.empty? + %h2.head= link_to "Template Files", "#" + .files + - @cookbook.template_files.each do |f| + .code + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f)
\ No newline at end of file diff --git a/chef-server-slice/app/views/layout/chef_server_slice.html.haml b/chef-server-slice/app/views/layout/chef_server_slice.html.haml index e4e353926b..d06372abfd 100644 --- a/chef-server-slice/app/views/layout/chef_server_slice.html.haml +++ b/chef-server-slice/app/views/layout/chef_server_slice.html.haml @@ -2,31 +2,45 @@ !!! %html %head + %meta{ "http-equiv" => "content-type", :content => "text/html; charset=utf-8" } %title Chef Server - %meta{"http-equiv" => "content-type", :content => "text/html; charset=utf-8" } - %link{:rel => "stylesheet", :href => "/stylesheets/master-highlight.css", :type => "text/css", :media => "screen", :charset => "utf-8" } - = js_include_tag "jquery-1.3.1.min", "jquery.jeditable.mini", "jquery.livequery" + = css_include_tag "base", "themes/djime-cerulean/style", "chef", "/facebox/facebox.css" + = js_include_tag "jquery-1.3.1.min", "jquery.jeditable.mini", "jquery.livequery", "jquery.localscroll", "jquery.scrollTo" + = js_include_tag "/facebox/facebox.js" = js_include_tag "chef" + %body - .header - %a{:href => slice_url(:searches) } Search - | - %a{:href => slice_url(:nodes) } Nodes - | - %a{:href => slice_url(:status) } Status - | - %a{:href => slice_url(:cookbooks) } Cookbooks - | - %a{:href => slice_url(:registrations)} Registrations - - if session[:openid] - | - %a{:href => slice_url(:openid_consumer_logout)}= "Logout #{h session[:openid]}" - = "(#{session[:level].to_s})" - - else - | - %a{:href => slice_url(:openid_consumer)} Login - %h3= catch_content :header - - unless message.empty? - = message[:error] - = message[:notice] - = catch_content :for_layout + #container + #header + %h1= link_to "Chef Server", slice_url(:top) + #user-navigation + %ul + - if session[:openid] + %li= link_to "Logout #{h session[:openid]} (#{session[:level].to_s})", slice_url(:openid_consumer_logout), :method => "get", :confirm => "Are you sure you want to logout?" + - else + %li= link_to "Login", slice_url(:openid_consumer), :rel => "facebox" + .clear + #main-navigation + %ul + %li= link_to "Search", slice_url(:searches) + %li= link_to "Status", slice_url(:status) + %li= link_to "Nodes", slice_url(:nodes) + %li= link_to "Cookbooks", slice_url(:cookbooks) + %li= link_to "Registrations", slice_url(:registrations) + .clear + #wrapper + #main + - unless message.empty? + .block#block-messages + .content + %h2.title Error Messages + .inner + .flash + - message.each do |type, msg| + %div{:class => "message #{type}"} + %p= msg + = catch_content :for_layout + #footer + .block + %p Copyright © 2009 Opscode + .clear diff --git a/chef-server-slice/app/views/nodes/_node.html.haml b/chef-server-slice/app/views/nodes/_node.html.haml index c5c73fbf66..a63b37e735 100644 --- a/chef-server-slice/app/views/nodes/_node.html.haml +++ b/chef-server-slice/app/views/nodes/_node.html.haml @@ -1,16 +1,22 @@ -- if session[:level] == :admin - .edit_area#node{:to_update => node.name} - %h2 Recipes - %ol - = recipe_list(node) - %h2 Attributes - %ol - = attribute_list(node) -- else - #node - %h2 Recipes - %ol - = recipe_list(node) - %h2 Attributes - %ol - = attribute_list(node)
\ No newline at end of file +%div{ :id => 'node', :class => "#{session[:level] == :admin ? 'edit_area' : 'non_edit_area'}", :to_update => @node.name} + %table.table + %tr + %th.first Recipes + %th.last   + - if @node.recipes.empty? + %tr + %td{:colspan => 2} This node has no recipes applied, double click to add one. + - else + - @node.recipes.each_with_index do |recipe, index| + %tr{:class => "#{index % 2 == 1 ? 'odd' : 'even'}"} + %td{:colspan => 2}= recipe + %table.table + %tr + %th.first Attributes + %th.last   + - if @node.attribute.empty? + %tr + %td{:colspan => 2} This node has no attributes, double click to add one. + - else + %tr + %td{:colspan => 2}= build_tree(@node)
\ No newline at end of file diff --git a/chef-server-slice/app/views/nodes/compile.html.haml b/chef-server-slice/app/views/nodes/compile.html.haml deleted file mode 100644 index 5656447ea8..0000000000 --- a/chef-server-slice/app/views/nodes/compile.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -= partial(:node, :node => @output[:node]) -.resource_collection - %h1 Resource Collection - - @output[:collection].each do |resource| - = partial(:resource, :resource => resource) diff --git a/chef-server-slice/app/views/nodes/index.html.haml b/chef-server-slice/app/views/nodes/index.html.haml index dce11d0c71..3d260cb6f4 100644 --- a/chef-server-slice/app/views/nodes/index.html.haml +++ b/chef-server-slice/app/views/nodes/index.html.haml @@ -1,7 +1,17 @@ -- throw_content(:header, "Node List") -- @node_list.each do |node| - .node - %a{ :href => slice_url(:node, { :id => node.gsub(/\./, "_") }) } - = node - - if session[:level] == :admin - = delete_button slice_url(:node, { :id => node.gsub(/\./, "_") }), "Delete" +.block#block-tables + .content + %h2.title Node List + .inner + %table.table + %tr + %th.first{:colspan => 2} Node Name + %th Control + %th.last   + - if @node_list.empty? + %tr + %td{:colspan => 4}= "You appear to have no nodes - try connecting one, or validating an existing #{link_to('registration', slice_url(:registrations))}" + - else + - @node_list.each_with_index do |node, index| + %tr{:class => "#{index % 2 == 1 ? 'odd' : 'even'}"} + %td{:colspan => 2}= link_to node, slice_url(:node, { :id => node.gsub(/\./, "_" ) } ) + %td= link_to "Delete", slice_url(:node, { :id => node.gsub(/\./, "_") }), :method => "delete", :confirm => "Are you sure? There is no undo."
\ No newline at end of file diff --git a/chef-server-slice/app/views/nodes/show.html.haml b/chef-server-slice/app/views/nodes/show.html.haml index d355974c49..a028f989b7 100644 --- a/chef-server-slice/app/views/nodes/show.html.haml +++ b/chef-server-slice/app/views/nodes/show.html.haml @@ -1,3 +1,5 @@ -- throw_content(:header, "Node #{h @node.name}") -.node - = partial(:node, :node => @node)
\ No newline at end of file +.block#block-tables + .content + %h2.title= "Node #{h @node.name}" + .inner + = partial :node, :node => @node
\ No newline at end of file diff --git a/chef-server-slice/app/views/openid_consumer/index.html.haml b/chef-server-slice/app/views/openid_consumer/index.html.haml index 6ff300b8cc..7580a5bf07 100644 --- a/chef-server-slice/app/views/openid_consumer/index.html.haml +++ b/chef-server-slice/app/views/openid_consumer/index.html.haml @@ -1,25 +1,17 @@ -- throw_content(:header, "OpenID Relaying Party") --if session[:alert] - .alert= h session[:alert] --if session[:error] - .error= h session[:error] --if session[:success] - .success= h session[:success] -#verify-form - %form{ :method => "get", "accept-charset" => "UTF-8", :action => slice_url(:openid_consumer_login) } - Identifier: - %input.openid{ :type => "text", :name => "openid_identifier" }/ - %input{ :type => "submit", :value => "Verify"}/ - %br - %input#immediate{ :name => "immediate", :type => "checkbox" }/ - %label{:for => "immediate"} Use immediate mode - %br - %input#immediate{ :name => "use_sreg", :type => "checkbox" }/ - %label{:for => "use_sreg"} Request registration data - %br - %input#immediate{ :name => "use_pape", :type => "checkbox" }/ - %label{:for => "use_pape"} Request phishing-resistent auth policy - %br - %input#immediate{ :name => "force_post", :type => "checkbox" }/ - %label{:for => "force_post"} Force the transaction to POST - %br +.block#block-forms + .content + %h2.title Login + .inner + %form.form{ :method => "get", "accept-charset" => "UTF-8", :action => slice_url(:openid_consumer_start) } + .group + .text_field= text_field :openid_identifier + .group + .check_box= check_box :name => "immediate", :label => "Use immediate mode", :value => "0" + .group + .check_box= check_box :name => "use_sreg", :label => "Request registration data", :value => "0" + .group + .check_box= check_box :name => "use_pape", :label => "Request phishing-resistent auth policy", :value => "0" + .group + .check_box= check_box :name => "force_post", :label => "Force the transaction to POST", :value => "0" + .group.navform + .button= submit "Login →" diff --git a/chef-server-slice/app/views/openid_register/index.html.haml b/chef-server-slice/app/views/openid_register/index.html.haml index cb52e4c196..4a54ed7512 100644 --- a/chef-server-slice/app/views/openid_register/index.html.haml +++ b/chef-server-slice/app/views/openid_register/index.html.haml @@ -1,18 +1,19 @@ -- throw_content(:header, "Registered OpenID Nodes List") -%table -- @registered_nodes.each do |node| - %tr - %td - %a{ :href => slice_url(:registration, { :id => node.name }) } - = h node.name - %td - - if session[:level] == :admin - %form{ :method => "post", :action => slice_url(:validate_registration, { :id => node.name })} - - submit_name = node.validated ? "Invalidate" : "Validate" - %input{ :type => "submit", :name => submit_name, :value => submit_name } - %form{ :method => "post", :action => slice_url(:admin_registration, { :id => node.name })} - - submit_name = node.admin ? "Remove Admin Rights" : "Admin" - %input{ :type => "submit", :name => submit_name, :value => submit_name } - %form{ :method => "post", :action => slice_url(:registration, { :id => node.name })} - %input{ :type => "hidden", :name => "_method", :value => "delete" } - %input{ :type => "submit", :name => "Delete", :value => "Delete" } +.block#block-tables + .content + %h2.title Registrations + .inner + %table.table + %tr + %th.first Registration Name + %th   + %th Control + %th.last + - @registered_nodes.sort{ |a,b| a.name.to_s <=> b.name.to_s }.each_with_index do |node, index| + %tr{:class => "#{index % 2 == 1 ? 'odd' : 'even'}"} + %td{:colspan => 2}= link_to(node.name, slice_url(:registration, { :id => node.name })) + %td + = link_to((node.validated ? "Invalidate" : "Validate"), slice_url(:validate_registration, { :id => node.name}), :method => "post", :confirm => "Are you sure you want to toggle this registrations validation?") + | + = link_to((node.admin ? "Remove Admin Rights" : "Make Admin"), slice_url(:admin_registration, { :id => node.name}), :method => "post", :confirm => "Are you sure you want to toggle this registrations administrator privileges?") + | + = link_to("Delete", slice_url(:registration, { :id => node.name} ), :method => "delete", :confirm => "Are you sure you want to delete this registration")
\ No newline at end of file diff --git a/chef-server-slice/app/views/openid_register/show.html.haml b/chef-server-slice/app/views/openid_register/show.html.haml index 58f55a3579..63d84d8364 100644 --- a/chef-server-slice/app/views/openid_register/show.html.haml +++ b/chef-server-slice/app/views/openid_register/show.html.haml @@ -1,6 +1,7 @@ -- throw_content(:header, "Registered OpenID Node #{@registered_node.name}") -%ol - %li - %a{ :href => slice_url(:openid_node , { :id => @registered_node.name.gsub(/\./, "_") }) } OpenID URL - %li= "Validated: #{@registered_node.validated}" - %li= "Admin: #{@registered_node.admin}" +.block#block-text + .content + %h2.title= "Registration #{@registered_node.name}" + .inner + = link_to "OpenID URL", slice_url(:openid_node, { :id => @registered_node.name.gsub(/\./, "_")}) + %br= "Validated: #{@registered_node.validated}" + %br= "Admin: #{@registered_node.admin}"
\ No newline at end of file diff --git a/chef-server-slice/app/views/search/index.html.haml b/chef-server-slice/app/views/search/index.html.haml index 75e4572481..8f3b199592 100644 --- a/chef-server-slice/app/views/search/index.html.haml +++ b/chef-server-slice/app/views/search/index.html.haml @@ -1,9 +1,9 @@ -- throw_content(:header, "Search Indexes") -- @search_indexes.each do |index| - .index - %table - %tr - %td - %a{ :href => slice_url(:search, { :id => index }) }= index - %td - = partial(:search_form, :index_name => index) +.block#block-forms + .content + %h2.title Search Indexes + .inner + - @search_indexes.sort{ |a,b| a.to_s <=> b.to_s}.each do |index| + %form.form{ :method => "get", "accept-charset" => "UTF-8", :action => slice_url(:search, { :id => index})} + .group.label= text_field :name => "q", :label => "Query" + .group.label= text_field :name => "a", :label => "Attributes" + .group= submit "Search #{index}"
\ No newline at end of file diff --git a/chef-server-slice/app/views/search/show.html.haml b/chef-server-slice/app/views/search/show.html.haml index e464b458b7..f20f9fa2ea 100644 --- a/chef-server-slice/app/views/search/show.html.haml +++ b/chef-server-slice/app/views/search/show.html.haml @@ -1,13 +1,14 @@ -- throw_content(:header, "Search Results") -.search - = partial(:search_form, :index_name => params[:id]) -.query - %h2= "Search Query was #{params[:q] ? params[:q] : '*'}" -- @results.each do |result| - .search_result - %h3= "#{h result[:index_name]} (#{h result[:id]})" - %table - - result.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |k, v| - %tr.attr_group - %td.attr_name= k - %td.attr_value= v.kind_of?(Array) ? v.join(",") : v +.block#block-tables + .content + %h2.title Search Results + .inner + - @results.each do |result| + %table.table.search{:width => '400px'} + %tr + %th.first Attribute + %th.last Value + - @results.each do |result| + - result.sort { |a,b| a[0].to_s <=> b[0].to_s}.each_with_index do |h,index| + %tr{:class => "#{index % 2 == 1 ? 'odd' : 'even'}"} + %td= "#{h[0]} (#{h[1].class})" + %td= h[1]
\ No newline at end of file diff --git a/chef-server-slice/app/views/status/index.html.haml b/chef-server-slice/app/views/status/index.html.haml index eff064752c..dd1fce34cc 100644 --- a/chef-server-slice/app/views/status/index.html.haml +++ b/chef-server-slice/app/views/status/index.html.haml @@ -1,8 +1,22 @@ -- throw_content(:header, "Status") -- @node_list.each do |node| - .node - %h3= node - = get_info(node) - %br - %strong Recipes: - = recipe_list(node) +.block#block-tables + .content + %h2.title Status + .inner + %table.table + %tr + %th.first Node Name + %th   + %th State + %th.last   + - if @node_list.empty? + %tr + %td{:colspan => 4}= "You appear to have no nodes - try connecting one, or validating an existing #{link_to('registration', slice_url(:registrations))}" + - else + - @node_list.each_with_index do |node, index| + %tr{:class => "#{index % 2 == 1 ? 'odd' : 'even'}"} + %td{:colspan => 2}= node + %td + = get_info(node) + %br + %strong Recipes: + = recipe_list(node)
\ No newline at end of file diff --git a/chef-server-slice/config/init.rb b/chef-server-slice/config/init.rb index 588d47c81f..064df28b93 100644 --- a/chef-server-slice/config/init.rb +++ b/chef-server-slice/config/init.rb @@ -21,7 +21,7 @@ # code and views. # -merb_gems_version = "> 1.0" +merb_gems_version = " > 1.0" dependency "merb-haml", merb_gems_version dependency "merb-assets", merb_gems_version dependency "merb-helpers", merb_gems_version diff --git a/chef-server-slice/public/facebox/README.txt b/chef-server-slice/public/facebox/README.txt new file mode 100644 index 0000000000..d4fc2d5e81 --- /dev/null +++ b/chef-server-slice/public/facebox/README.txt @@ -0,0 +1,4 @@ +Please visit http://famspam.com/facebox/ or open index.html in your favorite browser. + +Need help? Join our Google Groups mailing list: + http://groups.google.com/group/facebox/ diff --git a/chef-server-slice/public/facebox/b.png b/chef-server-slice/public/facebox/b.png Binary files differnew file mode 100644 index 0000000000..f184e6269b --- /dev/null +++ b/chef-server-slice/public/facebox/b.png diff --git a/chef-server-slice/public/facebox/bl.png b/chef-server-slice/public/facebox/bl.png Binary files differnew file mode 100644 index 0000000000..f6271859d5 --- /dev/null +++ b/chef-server-slice/public/facebox/bl.png diff --git a/chef-server-slice/public/facebox/br.png b/chef-server-slice/public/facebox/br.png Binary files differnew file mode 100644 index 0000000000..31f204fc45 --- /dev/null +++ b/chef-server-slice/public/facebox/br.png diff --git a/chef-server-slice/public/facebox/closelabel.gif b/chef-server-slice/public/facebox/closelabel.gif Binary files differnew file mode 100755 index 0000000000..87b4f8bd69 --- /dev/null +++ b/chef-server-slice/public/facebox/closelabel.gif diff --git a/chef-server-slice/public/facebox/facebox.css b/chef-server-slice/public/facebox/facebox.css new file mode 100644 index 0000000000..97ebe3cab3 --- /dev/null +++ b/chef-server-slice/public/facebox/facebox.css @@ -0,0 +1,95 @@ +#facebox .b { + background:url(/facebox/b.png); +} + +#facebox .tl { + background:url(/facebox/tl.png); +} + +#facebox .tr { + background:url(/facebox/tr.png); +} + +#facebox .bl { + background:url(/facebox/bl.png); +} + +#facebox .br { + background:url(/facebox/br.png); +} + +#facebox { + position: absolute; + top: 0; + left: 0; + z-index: 100; + text-align: left; +} + +#facebox .popup { + position: relative; +} + +#facebox table { + border-collapse: collapse; +} + +#facebox td { + border-bottom: 0; + padding: 0; +} + +#facebox .body { + padding: 10px; + background: #fff; + width: 370px; +} + +#facebox .loading { + text-align: center; +} + +#facebox .image { + text-align: center; +} + +#facebox img { + border: 0; + margin: 0; +} + +#facebox .footer { + border-top: 1px solid #DDDDDD; + padding-top: 5px; + margin-top: 10px; + text-align: right; +} + +#facebox .tl, #facebox .tr, #facebox .bl, #facebox .br { + height: 10px; + width: 10px; + overflow: hidden; + padding: 0; +} + +#facebox_overlay { + position: fixed; + top: 0px; + left: 0px; + height:100%; + width:100%; +} + +.facebox_hide { + z-index:-100; +} + +.facebox_overlayBG { + background-color: #000; + z-index: 99; +} + +* html #facebox_overlay { /* ie6 hack */ + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); +} diff --git a/chef-server-slice/public/facebox/facebox.js b/chef-server-slice/public/facebox/facebox.js new file mode 100644 index 0000000000..cbbb450b10 --- /dev/null +++ b/chef-server-slice/public/facebox/facebox.js @@ -0,0 +1,319 @@ +/* + * Facebox (for jQuery) + * version: 1.2 (05/05/2008) + * @requires jQuery v1.2 or later + * + * Examples at http://famspam.com/facebox/ + * + * Licensed under the MIT: + * http://www.opensource.org/licenses/mit-license.php + * + * Copyright 2007, 2008 Chris Wanstrath [ chris@ozmm.org ] + * + * Usage: + * + * jQuery(document).ready(function() { + * jQuery('a[rel*=facebox]').facebox() + * }) + * + * <a href="#terms" rel="facebox">Terms</a> + * Loads the #terms div in the box + * + * <a href="terms.html" rel="facebox">Terms</a> + * Loads the terms.html page in the box + * + * <a href="terms.png" rel="facebox">Terms</a> + * Loads the terms.png image in the box + * + * + * You can also use it programmatically: + * + * jQuery.facebox('some html') + * + * The above will open a facebox with "some html" as the content. + * + * jQuery.facebox(function($) { + * $.get('blah.html', function(data) { $.facebox(data) }) + * }) + * + * The above will show a loading screen before the passed function is called, + * allowing for a better ajaxy experience. + * + * The facebox function can also display an ajax page or image: + * + * jQuery.facebox({ ajax: 'remote.html' }) + * jQuery.facebox({ image: 'dude.jpg' }) + * + * Want to close the facebox? Trigger the 'close.facebox' document event: + * + * jQuery(document).trigger('close.facebox') + * + * Facebox also has a bunch of other hooks: + * + * loading.facebox + * beforeReveal.facebox + * reveal.facebox (aliased as 'afterReveal.facebox') + * init.facebox + * + * Simply bind a function to any of these hooks: + * + * $(document).bind('reveal.facebox', function() { ...stuff to do after the facebox and contents are revealed... }) + * + */ +(function($) { + $.facebox = function(data, klass) { + $.facebox.loading() + + if (data.ajax) fillFaceboxFromAjax(data.ajax) + else if (data.image) fillFaceboxFromImage(data.image) + else if (data.div) fillFaceboxFromHref(data.div) + else if ($.isFunction(data)) data.call($) + else $.facebox.reveal(data, klass) + } + + /* + * Public, $.facebox methods + */ + + $.extend($.facebox, { + settings: { + opacity : 0, + overlay : true, + loadingImage : '/facebox/loading.gif', + closeImage : '/facebox/closelabel.gif', + imageTypes : [ 'png', 'jpg', 'jpeg', 'gif' ], + faceboxHtml : '\ + <div id="facebox" style="display:none;"> \ + <div class="popup"> \ + <table> \ + <tbody> \ + <tr> \ + <td class="tl"/><td class="b"/><td class="tr"/> \ + </tr> \ + <tr> \ + <td class="b"/> \ + <td class="body"> \ + <div class="content"> \ + </div> \ + <div class="footer"> \ + <a href="#" class="close"> \ + <img src="/facebox/closelabel.gif" title="close" class="close_image" /> \ + </a> \ + </div> \ + </td> \ + <td class="b"/> \ + </tr> \ + <tr> \ + <td class="bl"/><td class="b"/><td class="br"/> \ + </tr> \ + </tbody> \ + </table> \ + </div> \ + </div>' + }, + + loading: function() { + init() + if ($('#facebox .loading').length == 1) return true + showOverlay() + + $('#facebox .content').empty() + $('#facebox .body').children().hide().end(). + append('<div class="loading"><img src="'+$.facebox.settings.loadingImage+'"/></div>') + + $('#facebox').css({ + top: getPageScroll()[1] + (getPageHeight() / 10), + left: 385.5 + }).show() + + $(document).bind('keydown.facebox', function(e) { + if (e.keyCode == 27) $.facebox.close() + return true + }) + $(document).trigger('loading.facebox') + }, + + reveal: function(data, klass) { + $(document).trigger('beforeReveal.facebox') + if (klass) $('#facebox .content').addClass(klass) + $('#facebox .content').append(data) + $('#facebox .loading').remove() + $('#facebox .body').children().fadeIn('normal') + $('#facebox').css('left', $(window).width() / 2 - ($('#facebox table').width() / 2)) + $(document).trigger('reveal.facebox').trigger('afterReveal.facebox') + }, + + close: function() { + $(document).trigger('close.facebox') + return false + } + }) + + /* + * Public, $.fn methods + */ + + $.fn.facebox = function(settings) { + init(settings) + + function clickHandler() { + $.facebox.loading(true) + + // support for rel="facebox.inline_popup" syntax, to add a class + // also supports deprecated "facebox[.inline_popup]" syntax + var klass = this.rel.match(/facebox\[?\.(\w+)\]?/) + if (klass) klass = klass[1] + + fillFaceboxFromHref(this.href, klass) + return false + } + + return this.click(clickHandler) + } + + /* + * Private methods + */ + + // called one time to setup facebox on this page + function init(settings) { + if ($.facebox.settings.inited) return true + else $.facebox.settings.inited = true + + $(document).trigger('init.facebox') + makeCompatible() + + var imageTypes = $.facebox.settings.imageTypes.join('|') + $.facebox.settings.imageTypesRegexp = new RegExp('\.' + imageTypes + '$', 'i') + + if (settings) $.extend($.facebox.settings, settings) + $('body').append($.facebox.settings.faceboxHtml) + + var preload = [ new Image(), new Image() ] + preload[0].src = $.facebox.settings.closeImage + preload[1].src = $.facebox.settings.loadingImage + + $('#facebox').find('.b:first, .bl, .br, .tl, .tr').each(function() { + preload.push(new Image()) + preload.slice(-1).src = $(this).css('background-image').replace(/url\((.+)\)/, '$1') + }) + + $('#facebox .close').click($.facebox.close) + $('#facebox .close_image').attr('src', $.facebox.settings.closeImage) + } + + // getPageScroll() by quirksmode.com + function getPageScroll() { + var xScroll, yScroll; + if (self.pageYOffset) { + yScroll = self.pageYOffset; + xScroll = self.pageXOffset; + } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict + yScroll = document.documentElement.scrollTop; + xScroll = document.documentElement.scrollLeft; + } else if (document.body) {// all other Explorers + yScroll = document.body.scrollTop; + xScroll = document.body.scrollLeft; + } + return new Array(xScroll,yScroll) + } + + // Adapted from getPageSize() by quirksmode.com + function getPageHeight() { + var windowHeight + if (self.innerHeight) { // all except Explorer + windowHeight = self.innerHeight; + } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode + windowHeight = document.documentElement.clientHeight; + } else if (document.body) { // other Explorers + windowHeight = document.body.clientHeight; + } + return windowHeight + } + + // Backwards compatibility + function makeCompatible() { + var $s = $.facebox.settings + + $s.loadingImage = $s.loading_image || $s.loadingImage + $s.closeImage = $s.close_image || $s.closeImage + $s.imageTypes = $s.image_types || $s.imageTypes + $s.faceboxHtml = $s.facebox_html || $s.faceboxHtml + } + + // Figures out what you want to display and displays it + // formats are: + // div: #id + // image: blah.extension + // ajax: anything else + function fillFaceboxFromHref(href, klass) { + // div + if (href.match(/#/)) { + var url = window.location.href.split('#')[0] + var target = href.replace(url,'') + $.facebox.reveal($(target).clone().show(), klass) + + // image + } else if (href.match($.facebox.settings.imageTypesRegexp)) { + fillFaceboxFromImage(href, klass) + // ajax + } else { + fillFaceboxFromAjax(href, klass) + } + } + + function fillFaceboxFromImage(href, klass) { + var image = new Image() + image.onload = function() { + $.facebox.reveal('<div class="image"><img src="' + image.src + '" /></div>', klass) + } + image.src = href + } + + function fillFaceboxFromAjax(href, klass) { + $.get(href, function(data) { $.facebox.reveal(data, klass) }) + } + + function skipOverlay() { + return $.facebox.settings.overlay == false || $.facebox.settings.opacity === null + } + + function showOverlay() { + if (skipOverlay()) return + + if ($('facebox_overlay').length == 0) + $("body").append('<div id="facebox_overlay" class="facebox_hide"></div>') + + $('#facebox_overlay').hide().addClass("facebox_overlayBG") + .css('opacity', $.facebox.settings.opacity) + .click(function() { $(document).trigger('close.facebox') }) + .fadeIn(200) + return false + } + + function hideOverlay() { + if (skipOverlay()) return + + $('#facebox_overlay').fadeOut(200, function(){ + $("#facebox_overlay").removeClass("facebox_overlayBG") + $("#facebox_overlay").addClass("facebox_hide") + $("#facebox_overlay").remove() + }) + + return false + } + + /* + * Bindings + */ + + $(document).bind('close.facebox', function() { + $(document).unbind('keydown.facebox') + $('#facebox').fadeOut(function() { + $('#facebox .content').removeClass().addClass('content') + hideOverlay() + $('#facebox .loading').remove() + }) + }) + +})(jQuery); diff --git a/chef-server-slice/public/facebox/loading.gif b/chef-server-slice/public/facebox/loading.gif Binary files differnew file mode 100755 index 0000000000..f864d5fd38 --- /dev/null +++ b/chef-server-slice/public/facebox/loading.gif diff --git a/chef-server-slice/public/facebox/tl.png b/chef-server-slice/public/facebox/tl.png Binary files differnew file mode 100644 index 0000000000..d99c8f6c6e --- /dev/null +++ b/chef-server-slice/public/facebox/tl.png diff --git a/chef-server-slice/public/facebox/tr.png b/chef-server-slice/public/facebox/tr.png Binary files differnew file mode 100644 index 0000000000..e99b6ec831 --- /dev/null +++ b/chef-server-slice/public/facebox/tr.png diff --git a/chef-server-slice/public/images/avatar.png b/chef-server-slice/public/images/avatar.png Binary files differnew file mode 100644 index 0000000000..66488481ae --- /dev/null +++ b/chef-server-slice/public/images/avatar.png diff --git a/chef-server-slice/public/images/toggle-collapse.gif b/chef-server-slice/public/images/toggle-collapse.gif Binary files differnew file mode 100644 index 0000000000..f0979304ac --- /dev/null +++ b/chef-server-slice/public/images/toggle-collapse.gif diff --git a/chef-server-slice/public/images/toggle-expand.gif b/chef-server-slice/public/images/toggle-expand.gif Binary files differnew file mode 100644 index 0000000000..03fa8360dd --- /dev/null +++ b/chef-server-slice/public/images/toggle-expand.gif diff --git a/chef-server-slice/public/javascripts/chef.js b/chef-server-slice/public/javascripts/chef.js index bd82f1aeb7..0245bf79d2 100644 --- a/chef-server-slice/public/javascripts/chef.js +++ b/chef-server-slice/public/javascripts/chef.js @@ -1,5 +1,6 @@ // // Author:: Adam Jacob (<adam@opscode.com>) +// Author:: AJ Christensen (<aj@junglist.gen.nz>) // Copyright:: Copyright (c) 2008 Opscode, Inc. // License:: Apache License, Version 2.0 // @@ -16,21 +17,8 @@ // limitations under the License. // -$(document).ready(function() { - $(".edit_area").editable(location.href + ".json", { - type : 'textarea', - cancel : 'Cancel', - submit : 'Save', - indicator : "<img src='/images/indicator.gif'>", - target : location.href + "?ajax=true", - loadtype : "GET", - loadurl : location.href + ".json?ajax=true", - tooltip : 'Click to edit...', - method : "PUT" - }); -}); - -$(document).ready(function() { +$(document).ready(function(){ + // livequery hidden form for link_to ajax magic $('a[method]').livequery(function(){ var message = $(this).attr('confirm'); var method = $(this).attr('method'); @@ -53,4 +41,56 @@ $(document).ready(function() { } }); }); + + $("dd:has(dl)").livequery(function(){ + $(this).hide().prev("dt").addClass("collapsed"); + }); + $("dd:not(:has(dl))").livequery(function(){ + $(this).addClass("inline").prev().addClass("inline"); + }); + $("dt.collapsed").livequery(function(){ + $(this).click(function() { + $(this).toggleClass("collapsed").next().toggle(); + }); + }); + + // editable table for the node show view + $(".edit_area").editable(location.href + ".json", { + target : location.href, + method : "PUT", + submit : "Save", + cancel : "Cancel", + indicator : "Saving..", + loadurl : location.href, + tooltip : "Click to edit", + type : "textarea", + event : "dblclick", + height : 300 + }); + + + //alert("blah" + $('#json_tree_source').text()); + //var json = $('#json_tree_source').text(); + //$('#attribute_tree_view').append(TreeView($('#json_tree_source').text())); + + // accordion for the cookbooks show view + $('.accordion .head').click(function() { + $(this).next().toggle('slow'); + return false; + }).next().hide(); + + // global facebox callback + $('a[rel*=facebox]').facebox(); + + /* + JSONEditor.prototype.ADD_IMG = '/images/add.png'; + JSONEditor.prototype.DELETE_IMG = '/images/delete.png'; + var attrib_editor = new JSONEditor($("#attrib_json_edit"), 400, 300); + attrib_editor.doTruncation(true); + attrib_editor.showFunctionButtons(); + + var recipe_editor = new JSONEditor($("#recipe_json_edit"), 400, 300); + recipe_editor.doTruncation(true); + recipe_editor.showFunctionButtons(); + */ });
\ No newline at end of file diff --git a/chef-server-slice/public/javascripts/jquery.editinline.js b/chef-server-slice/public/javascripts/jquery.editinline.js new file mode 100644 index 0000000000..e97abe1d15 --- /dev/null +++ b/chef-server-slice/public/javascripts/jquery.editinline.js @@ -0,0 +1,108 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + + function startEditing(elem, options) { + var editable = $(elem); + var origHtml = editable.html(); + var origText = options.populate($.trim(editable.text())); + + if (!options.begin.apply(elem, [origText])) { + return; + } + + var input = options.createInput.apply(elem, [origText]) + .addClass("editinline").val(origText) + .dblclick(function() { return false; }) + .keydown(function(evt) { + switch (evt.keyCode) { + case 13: { // return + if (!input.is("textarea")) applyChange(evt.keyCode); + break; + } + case 27: { // escape + cancelChange(evt.keyCode); + break; + } + case 9: { // tab + if (!input.is("textarea")) { + applyChange(evt.keyCode); + return false; + } + } + } + }); + + function applyChange(keyCode) { + var newText = input.val(); + if (newText == origText) { + cancelChange(keyCode); + return true; + } + if ((!options.allowEmpty && !newText.length) || + !options.validate.apply(elem, [newText])) { + input.addClass("invalid"); + return false; + } + input.remove(); + tools.remove(); + options.accept.apply(elem, [newText, origText]); + editable.removeClass("editinline-container"); + options.end.apply(elem, [keyCode]); + return true; + } + + function cancelChange(keyCode) { + options.cancel.apply(elem, [origText]); + editable.html(origHtml).removeClass("editinline-container"); + options.end.apply(elem, [keyCode]); + } + + var tools = $("<span class='editinline-tools'></span>"); + $("<button type='button' class='apply'></button>") + .text(options.acceptLabel).click(applyChange).appendTo(tools); + $("<button type='button' class='cancel'></button>") + .text(options.cancelLabel).click(cancelChange).appendTo(tools) + + editable.html("").append(tools).append(input) + .addClass("editinline-container"); + options.prepareInput.apply(elem, [input[0]]); + input.each(function() { this.focus(); this.select(); }); + } + + $.fn.makeEditable = function(options) { + options = $.extend({ + allowEmpty: true, + acceptLabel: "", + cancelLabel: "", + toolTip: "Double click to edit", + + // callbacks + begin: function() { return true }, + accept: function(newValue, oldValue) {}, + cancel: function(oldValue) {}, + createInput: function(value) { return $("<input type='text'>") }, + prepareInput: function(input) {}, + end: function(keyCode) {}, + populate: function(value) { return value }, + validate: function() { return true } + }, options || {}); + + return this.each(function() { + $(this).attr("title", options.toolTip).dblclick(function() { + startEditing(this, options); + }); + }); + } + +})(jQuery); diff --git a/chef-server-slice/public/javascripts/jquery.localscroll.js b/chef-server-slice/public/javascripts/jquery.localscroll.js new file mode 100644 index 0000000000..596e1ba200 --- /dev/null +++ b/chef-server-slice/public/javascripts/jquery.localscroll.js @@ -0,0 +1,104 @@ +/**
+ * jQuery.LocalScroll
+ * Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Dual licensed under MIT and GPL.
+ * Date: 6/3/2008
+ *
+ * @projectDescription Animated scrolling navigation, using anchors.
+ * http://flesler.blogspot.com/2007/10/jquerylocalscroll-10.html
+ * @author Ariel Flesler
+ * @version 1.2.6
+ *
+ * @id jQuery.fn.localScroll
+ * @param {Object} settings Hash of settings, it is passed in to jQuery.ScrollTo, none is required.
+ * @return {jQuery} Returns the same jQuery object, for chaining.
+ *
+ * @example $('ul.links').localScroll();
+ *
+ * @example $('ul.links').localScroll({ filter:'.animated', duration:400, axis:'x' });
+ *
+ * @example $.localScroll({ target:'#pane', axis:'xy', queue:true, event:'mouseover' });
+ *
+ * Notes:
+ * - The plugin requires jQuery.ScrollTo.
+ * - The hash of settings, is passed to jQuery.ScrollTo, so the settings are valid for that plugin as well.
+ * - jQuery.localScroll can be used if the desired links, are all over the document, it accepts the same settings.
+ * - If the setting 'lazy' is set to true, then the binding will still work for later added anchors.
+ * - The setting 'speed' is deprecated, use 'duration' instead.
+ * - If onBefore returns false, the event is ignored.
+ **/
+;(function( $ ){
+ var URI = location.href.replace(/#.*/,'');//local url without hash
+
+ var $localScroll = $.localScroll = function( settings ){
+ $('body').localScroll( settings );
+ };
+
+ //Many of these defaults, belong to jQuery.ScrollTo, check it's demo for an example of each option.
+ //@see http://www.freewebs.com/flesler/jQuery.ScrollTo/
+ $localScroll.defaults = {//the defaults are public and can be overriden.
+ duration:1000, //how long to animate.
+ axis:'y',//which of top and left should be modified.
+ event:'click',//on which event to react.
+ stop:true//avoid queuing animations
+ /*
+ lock:false,//ignore events if already animating
+ lazy:false,//if true, links can be added later, and will still work.
+ target:null, //what to scroll (selector or element). Keep it null if want to scroll the whole window.
+ filter:null, //filter some anchors out of the matched elements.
+ hash: false//if true, the hash of the selected link, will appear on the address bar.
+ */
+ };
+
+ //if the URL contains a hash, it will scroll to the pointed element
+ $localScroll.hash = function( settings ){
+ settings = $.extend( {}, $localScroll.defaults, settings );
+ settings.hash = false;//can't be true
+ if( location.hash )
+ setTimeout(function(){ scroll( 0, location, settings ); }, 0 );//better wrapped with a setTimeout
+ };
+
+ $.fn.localScroll = function( settings ){
+ settings = $.extend( {}, $localScroll.defaults, settings );
+
+ return ( settings.persistent || settings.lazy )
+ ? this.bind( settings.event, function( e ){//use event delegation, more links can be added later.
+ var a = $([e.target, e.target.parentNode]).filter(filter)[0];//if a valid link was clicked.
+ a && scroll( e, a, settings );//do scroll.
+ })
+ : this.find('a,area')//bind concretely, to each matching link
+ .filter( filter ).bind( settings.event, function(e){
+ scroll( e, this, settings );
+ }).end()
+ .end();
+
+ function filter(){//is this a link that points to an anchor and passes a possible filter ? href is checked to avoid a bug in FF.
+ return !!this.href && !!this.hash && this.href.replace(this.hash,'') == URI && (!settings.filter || $(this).is( settings.filter ));
+ };
+ };
+
+ function scroll( e, link, settings ){
+ var id = link.hash.slice(1),
+ elem = document.getElementById(id) || document.getElementsByName(id)[0];
+ if ( elem ){
+ e && e.preventDefault();
+ var $target = $( settings.target || $.scrollTo.window() );//if none specified, then the window.
+
+ if( settings.lock && $target.is(':animated') ||
+ settings.onBefore && settings.onBefore.call(link, e, elem, $target) === false ) return;
+
+ if( settings.stop )
+ $target.queue('fx',[]).stop();//remove all its animations
+ $target
+ .scrollTo( elem, settings )//do scroll
+ .trigger('notify.serialScroll',[elem]);//notify serialScroll about this change
+ if( settings.hash )
+ $target.queue(function(){
+ location = link.hash;
+ // make sure this function is released
+ $(this).dequeue();
+ });
+ }
+ };
+
+})( jQuery );
\ No newline at end of file diff --git a/chef-server-slice/public/javascripts/jquery.scrollTo.js b/chef-server-slice/public/javascripts/jquery.scrollTo.js new file mode 100644 index 0000000000..688d58e55d --- /dev/null +++ b/chef-server-slice/public/javascripts/jquery.scrollTo.js @@ -0,0 +1,150 @@ +/**
+ * jQuery.ScrollTo
+ * Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Dual licensed under MIT and GPL.
+ * Date: 2/19/2008
+ *
+ * @projectDescription Easy element scrolling using jQuery.
+ * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
+ * Tested with jQuery 1.2.1. On FF 2.0.0.11, IE 6, Opera 9.22 and Safari 3 beta. on Windows.
+ *
+ * @author Ariel Flesler
+ * @version 1.3.3
+ *
+ * @id jQuery.scrollTo
+ * @id jQuery.fn.scrollTo
+ * @param {String, Number, DOMElement, jQuery, Object} target Where to scroll the matched elements.
+ * The different options for target are:
+ * - A number position (will be applied to all axes).
+ * - A string position ('44', '100px', '+=90', etc ) will be applied to all axes
+ * - A jQuery/DOM element ( logically, child of the element to scroll )
+ * - A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc )
+ * - A hash { top:x, left:y }, x and y can be any kind of number/string like above.
+ * @param {Number} duration The OVERALL length of the animation, this argument can be the settings object instead.
+ * @param {Object} settings Hash of settings, optional.
+ * @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'.
+ * @option {Number} duration The OVERALL length of the animation.
+ * @option {String} easing The easing method for the animation.
+ * @option {Boolean} margin If true, the margin of the target element will be deducted from the final position.
+ * @option {Object, Number} offset Add/deduct from the end position. One number for both axes or { top:x, left:y }.
+ * @option {Object, Number} over Add/deduct the height/width multiplied by 'over', can be { top:x, left:y } when using both axes.
+ * @option {Boolean} queue If true, and both axis are given, the 2nd axis will only be animated after the first one ends.
+ * @option {Function} onAfter Function to be called after the scrolling ends.
+ * @option {Function} onAfterFirst If queuing is activated, this function will be called after the first scrolling ends.
+ * @return {jQuery} Returns the same jQuery object, for chaining.
+ *
+ * @example $('div').scrollTo( 340 );
+ *
+ * @example $('div').scrollTo( '+=340px', { axis:'y' } );
+ *
+ * @example $('div').scrollTo( 'p.paragraph:eq(2)', 500, { easing:'swing', queue:true, axis:'xy' } );
+ *
+ * @example var second_child = document.getElementById('container').firstChild.nextSibling;
+ * $('#container').scrollTo( second_child, { duration:500, axis:'x', onAfter:function(){
+ * alert('scrolled!!');
+ * }});
+ *
+ * @example $('div').scrollTo( { top: 300, left:'+=200' }, { offset:-20 } );
+ *
+ * Notes:
+ * - jQuery.scrollTo will make the whole window scroll, it accepts the same arguments as jQuery.fn.scrollTo.
+ * - If you are interested in animated anchor navigation, check http://jquery.com/plugins/project/LocalScroll.
+ * - The options margin, offset and over are ignored, if the target is not a jQuery object or a DOM element.
+ * - The option 'queue' won't be taken into account, if only 1 axis is given.
+ */
+;(function( $ ){
+
+ var $scrollTo = $.scrollTo = function( target, duration, settings ){
+ $scrollTo.window().scrollTo( target, duration, settings );
+ };
+
+ $scrollTo.defaults = {
+ axis:'y',
+ duration:1
+ };
+
+ //returns the element that needs to be animated to scroll the window
+ $scrollTo.window = function(){
+ return $( $.browser.safari ? 'body' : 'html' );
+ };
+
+ $.fn.scrollTo = function( target, duration, settings ){
+ if( typeof duration == 'object' ){
+ settings = duration;
+ duration = 0;
+ }
+ settings = $.extend( {}, $scrollTo.defaults, settings );
+ duration = duration || settings.speed || settings.duration;//speed is still recognized for backwards compatibility
+ settings.queue = settings.queue && settings.axis.length > 1;//make sure the settings are given right
+ if( settings.queue )
+ duration /= 2;//let's keep the overall speed, the same.
+ settings.offset = both( settings.offset );
+ settings.over = both( settings.over );
+
+ return this.each(function(){
+ var elem = this, $elem = $(elem),
+ t = target, toff, attr = {},
+ win = $elem.is('html,body');
+ switch( typeof t ){
+ case 'number'://will pass the regex
+ case 'string':
+ if( /^([+-]=)?\d+(px)?$/.test(t) ){
+ t = both( t );
+ break;//we are done
+ }
+ t = $(t,this);// relative selector, no break!
+ case 'object':
+ if( t.is || t.style )//DOM/jQuery
+ toff = (t = $(t)).offset();//get the real position of the target
+ }
+ $.each( settings.axis.split(''), function( i, axis ){
+ var Pos = axis == 'x' ? 'Left' : 'Top',
+ pos = Pos.toLowerCase(),
+ key = 'scroll' + Pos,
+ act = elem[key],
+ Dim = axis == 'x' ? 'Width' : 'Height',
+ dim = Dim.toLowerCase();
+
+ if( toff ){//jQuery/DOM
+ attr[key] = toff[pos] + ( win ? 0 : act - $elem.offset()[pos] );
+
+ if( settings.margin ){//if it's a dom element, reduce the margin
+ attr[key] -= parseInt(t.css('margin'+Pos)) || 0;
+ attr[key] -= parseInt(t.css('border'+Pos+'Width')) || 0;
+ }
+
+ attr[key] += settings.offset[pos] || 0;//add/deduct the offset
+
+ if( settings.over[pos] )//scroll to a fraction of its width/height
+ attr[key] += t[dim]() * settings.over[pos];
+ }else
+ attr[key] = t[pos];//remove the unnecesary 'px'
+
+ if( /^\d+$/.test(attr[key]) )//number or 'number'
+ attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max(Dim) );//check the limits
+
+ if( !i && settings.queue ){//queueing each axis is required
+ if( act != attr[key] )//don't waste time animating, if there's no need.
+ animate( settings.onAfterFirst );//intermediate animation
+ delete attr[key];//don't animate this axis again in the next iteration.
+ }
+ });
+ animate( settings.onAfter );
+
+ function animate( callback ){
+ $elem.animate( attr, duration, settings.easing, callback && function(){
+ callback.call(this, target);
+ });
+ };
+ function max( Dim ){
+ var el = win ? $.browser.opera ? document.body : document.documentElement : elem;
+ return el['scroll'+Dim] - el['client'+Dim];
+ };
+ });
+ };
+
+ function both( val ){
+ return typeof val == 'object' ? val : { top:val, left:val };
+ };
+
+})( jQuery );
\ No newline at end of file diff --git a/chef-server-slice/public/javascripts/master.js b/chef-server-slice/public/javascripts/master.js deleted file mode 100644 index e69de29bb2..0000000000 --- a/chef-server-slice/public/javascripts/master.js +++ /dev/null diff --git a/chef-server-slice/public/stylesheets/base.css b/chef-server-slice/public/stylesheets/base.css new file mode 100644 index 0000000000..5775ac3a16 --- /dev/null +++ b/chef-server-slice/public/stylesheets/base.css @@ -0,0 +1,336 @@ +* {margin:0;padding:0} +.clear { clear: both; height: 0; } + +h1 { margin: 15px 0; font-size: 22px; font-weight: normal; } +h2 { font-size: 22px; margin: 15px 0; font-weight: normal;} +h3 { font-size: 18px; margin: 10px 0; font-weight: normal;} +h4 { font-size: 16px; margin: 10px 0; font-weight: normal;} +hr {height: 1px; border: 0; } +p { margin: 15px 0;} +a img { border: none; } + +body { + font-size: 12px; + font-family: sans-serif; +} + +#container { + min-width: 960px; +} + +#header, #wrapper { + padding: 0 20px; +} + +#header { + position: relative; + padding-top: 1px; +} + +#header h1 { + margin: 0; + padding: 10px 0; + font-size: 30px; +} + +#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited { + text-decoration: none; +} + +#main { + width: 70%; + float: left; +} + +.actions-bar { + padding: 10px 1px; +} + +.actions-bar .actions { + float: left; +} + + +.actions-bar .pagination { + float: right; + padding: 1px 0; +} + +#sidebar { + width: 25%; + float: right; +} + +#sidebar h3 { + padding: 10px 15px; + margin: 0; + font-size: 13px; +} + +#sidebar .block { + margin-bottom: 20px; + padding-bottom: 10px; +} + +#sidebar .block .content { + padding: 0 15px; +} + +#sidebar ul.navigation li a:link, #sidebar ul.navigation li a:visited { + display: block; + padding: 10px 15px; +} + +#sidebar .block .sidebar-block, #sidebar .notice { + padding:10px; +} + +#wrapper { + padding-top: 20px; +} + +#main .block { + margin-bottom: 20px; + padding-top: 1px; +} + +#main .block .content .inner { + padding: 0 15px 15px; +} + +#main .main p.first { + margin-top: 0; +} + +#user-navigation { + position: absolute; + top: 0px; + right: 20px; +} + +#main-navigation { + width: 100%; +} + +#user-navigation ul, #main-navigation ul, .secondary-navigation ul, #sidebar ul.navigation { + margin: 0; + padding: 0; + list-style-type: none; +} + +#user-navigation ul li, #main-navigation ul li, .secondary-navigation ul li { + float: left; +} + +#main-navigation ul li { + margin-right: 5px; +} + +#user-navigation ul li { + padding: 5px 10px; +} + +#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active, +#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active { + text-decoration: none; +} + +#main-navigation ul li a { + font-size: 15px; + display: block; + padding: 8px 15px; +} + +.secondary-navigation { + font-size: 13px; + border-bottom-width: 10px; + border-bottom-style: solid; +} + +.secondary-navigation ul li a { + display: block; + padding: 10px 15px; +} + +#footer { + padding-bottom: 20px; +} + +/* pagination */ + +.pagination a, .pagination span { + padding: 2px 5px; + margin-right: 5px; + display: block; + float: left; + border-style: solid; + border-width: 1px; +} + +.pagination span.current { + font-weight: bold; +} + +.pagination a { + text-decoration: none; +} + +/* tables */ +.table { + width: 100%; + border-collapse: collapse; + margin-bottom: 15px; +} + +.table th { + padding: 10px; + font-weight: bold; + text-align: left; +} + +.table th.first { + width: 30px; +} + +.table th.last { + width: 200px; +} + +.table .checkbox { + margin-left: 10px; +} + +.table td { + padding: 10px; +} + +.table td.last { + text-align: right; +} + +/* forms */ + +input.checkbox { + margin: 0; + padding: 0; +} + +.form .group { + margin-bottom: 15px; +} + +.form div.left { + width: 20%; + float: left; +} + +.form div.right { + width: 75%; + float: right; +} + +.form .columns .column { + width: 48%; +} + +.form .columns .left { + float: left; +} + +.form .columns .right { + float: right; +} + +.form label.label, .form input.text_field, .form textarea.text_area { + font-size: 1.2em; + padding: 1px 0; + margin: 0; +} + +.form label.right { + text-align: right; +} + +.form input.checkbox, .form input.radio { + margin-right: 5px; +} + +.form label.checkbox, .form label.radio { + line-height: 1.5em; +} + +.form label.label { + display: block; + padding-bottom: 2px; + font-weight: bold; +} + +.form div.fieldWithErrors label.label { + display: inline; +} + +.form .fieldWithErrors .error { + color: red; +} + +.form input.text_field, .form textarea.text_area { + width: 100%; + border-width: 1px; + border-style: solid; +} + +/* lists */ + +ul.list { + margin: 0; + padding: 0; + list-style-type: none; +} + +ul.list li { + clear: left; + padding-bottom: 5px; +} + +ul.list li .left { + float: left; +} + +ul.list li .left .avatar { + width: 50px; + height: 50px; +} + +ul.list li .item { + margin-left: 80px; +} + +ul.list li .item .avatar { + float: left; + margin: 0 5px 5px 0; + width: 30px; + height: 30px; +} + +/* box */ + +#box { + width: 500px; + margin: 50px auto; +} + +#box .block { + margin-bottom: 20px; +} + +#box .block h2 { + padding: 10px 15px; + margin: 0; +} + +#box .block .content { + padding: 10px 20px; +} + + diff --git a/chef-server-slice/public/stylesheets/chef.css b/chef-server-slice/public/stylesheets/chef.css new file mode 100644 index 0000000000..1efc8a84c1 --- /dev/null +++ b/chef-server-slice/public/stylesheets/chef.css @@ -0,0 +1,50 @@ +.ruby .normal {} +.ruby .comment { color: #005; font-style: italic; } +.ruby .keyword { color: #A00; font-weight: bold; } +.ruby .method { color: #077; } +.ruby .class { color: #074; } +.ruby .module { color: #050; } +.ruby .punct { color: #447; font-weight: bold; } +.ruby .symbol { color: #099; } +.ruby .string { color: #944; background: #FFE; } +.ruby .char { color: #F07; } +.ruby .ident { color: #004; } +.ruby .constant { color: #07F; } +.ruby .regex { color: #B66; background: #FEF; } +.ruby .number { color: #F99; } +.ruby .attribute { color: #7BB; } +.ruby .global { color: #7FB; } +.ruby .expr { color: #227; } +.ruby .escape { color: #277; } + +.files { + padding-left: 20px; +} + +.code { + overflow: auto; + font-size: 0.8em; +} + +.search td { + /*overflow: auto;*/ + font-size: 0.8em; +} + +dl dt { font-weight: bold; } +.content td dl { margin: 0; padding: 0; } +.content td dt { + background: transparent url(/images/toggle-collapse.gif) 0 3px no-repeat; + clear: left; color: #333; cursor: pointer; line-height: 1em; + margin-left: -12px; padding-left: 14px; +} +.content td dd { margin: 0; + padding: 0 0 0 1em; +} +.content td dt.collapsed { + background-image: url(/images/toggle-expand.gif); +} +.content td dt.inline { background-image: none; cursor: default; + float: left; margin-left: 0; padding-left: 2px; padding-right: .5em; + padding-top: 2px; +}
\ No newline at end of file diff --git a/chef-server-slice/public/stylesheets/master-highlight.css b/chef-server-slice/public/stylesheets/master-highlight.css deleted file mode 100644 index ccbda31932..0000000000 --- a/chef-server-slice/public/stylesheets/master-highlight.css +++ /dev/null @@ -1,193 +0,0 @@ -body { - font-family: Verdana, Arial, sans-serif; - font-size: 11px; - background-color: #fff; - margin: ; -} -html { - height: 100%; - margin-bottom: 1px; -} -#container { - width: 80%; - text-align: left; - background-color: #fff; - margin-right: auto; - margin-left: auto; -} -#header-container { - width: 100%; - padding-top: 15px; -} -#header-container h1, #header-container h2 { - margin-left: 6px; - margin-bottom: 6px; -} -.spacer { - width: 100%; - height: 15px; -} -hr { - border: 0px; - color: #ccc; - background-color: #cdcdcd; - height: 1px; - width: 100%; - text-align: left; -} -h1 { - font-size: 28px; - color: #c55; - background-color: #fff; - font-family: Arial, Verdana, sans-serif; - font-weight: 300; -} -h2 { - font-size: 15px; - color: #999; - font-family: Arial, Verdana, sans-serif; - font-weight: 300; - background-color: #fff; -} -h3 { - color: #4d9b12; - font-size: 15px; - text-align: left; - font-weight: 300; - padding: 5px; - margin-top: 5px; -} - -#left-container { - float: left; - width: 250px; - background-color: #FFFFFF; - color: black; -} - -#left-container h3 { - color: #c55; -} - -#main-container { - margin: 5px 5px 5px 260px; - padding: 15px; - border-left: 1px solid silver; - min-height: 400px; -} -p { - color: #000; - background-color: #fff; - line-height: 20px; - padding: 5px; -} -a { - color: #4d9b12; - background-color: #fff; - text-decoration: none; -} -a:hover { - color: #4d9b12; - background-color: #fff; - text-decoration: underline; -} -#footer-container { - clear: both; - font-size: 12px; - font-family: Verdana, Arial, sans-serif; -} -.right { - float: right; - font-size: 100%; - margin-top: 5px; - color: #999; - background-color: #fff; -} -.left { - float: left; - font-size: 100%; - margin-top: 5px; - color: #999; - background-color: #fff; -} -#main-container ul { - margin-left: 3.0em; - margin-right: 3.0em; -} -#main-container ol { - margin-left: 3.0em; - margin-right: 3.0em; -} - -div.resource_collection { - border: 1px solid #999; - float: left; - clear: both; - margin: 10px; - padding: 5px; -} - -div.resource { - border-top: 1px solid #999; - float: left; - clear: both; -} - -div.attr_group { - float: left; - clear: both; -} - -td.attr_name { - font-weight: bold; - margin-right: 10px; - border-right: 1px solid #999; -} - -td.attr_value { - padding-left: 5px; - border-top: 1px solid #999; -} - -div.node { - float: left; - clear: both; - margin: 10px; - padding: 5px; - border: 1px solid #999; - width: 95%; -} -li { - overflow-x: auto; /* Use horizontal scroller if needed; for Firefox 2, not needed in Firefox 3 */ - white-space: pre-wrap; /* css-3 */ - white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ - /* width: 99%; */ - word-wrap: break-word; /* Internet Explorer 5.5+ */ -} - -textarea { - font-size: 10px; - font-family: monospace; - border: 1px solid #999; -} - -.ruby .normal {} -.ruby .comment { color: #005; font-style: italic; } -.ruby .keyword { color: #A00; font-weight: bold; } -.ruby .method { color: #077; } -.ruby .class { color: #074; } -.ruby .module { color: #050; } -.ruby .punct { color: #447; font-weight: bold; } -.ruby .symbol { color: #099; } -.ruby .string { color: #944; background: #FFE; } -.ruby .char { color: #F07; } -.ruby .ident { color: #004; } -.ruby .constant { color: #07F; } -.ruby .regex { color: #B66; background: #FEF; } -.ruby .number { color: #F99; } -.ruby .attribute { color: #7BB; } -.ruby .global { color: #7FB; } -.ruby .expr { color: #227; } -.ruby .escape { color: #277; } diff --git a/chef-server-slice/public/stylesheets/themes/bec-green/style.css b/chef-server-slice/public/stylesheets/themes/bec-green/style.css new file mode 100644 index 0000000000..225b6d4e35 --- /dev/null +++ b/chef-server-slice/public/stylesheets/themes/bec-green/style.css @@ -0,0 +1,290 @@ +a:link, a:visited, a:hover, a:active { color: #33f; } +h1, h2, h3 {color:#444} + +body { + color: #222; + background: #e5e5e5; + font-family: "Bitstream Vera Sans", verdana, sans-serif; +} + +hr { + background: #f0f0ee; +} + +p { + font-size: 14px; + line-height: 20px; +} + +input.checkbox { + vertical-align:middle; +} + +#header h1 { + font-size: 28px; + padding: 5px 0; + margin: 5px 0; +} + +.hightlight { + background-color: #ffc; +} +.small { + font-size: 11px; +} +.gray { + color: #999; +} +#header { + background: #48625B; +} + +#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited { + color: #FFF; +} + +#main { + background: #e5e5e5; + width: 69%; +} + +#main .block { + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + padding: 0; + margin-bottom:20px; + padding-bottom: 20px; + background: #fff; +} + +#main .block h2.title { + margin: 0 0 20px 0; + background-color: #E9FAE6; + padding: 5px 5px 5px 15px; + font-size:18px; +} + +.main_container { + padding:10px; +} + +/* #sidebar .block { background: #FFF; padding-bottom:0px; } */ + +#sidebar .notice { + background-color: #ffc; + padding: 0 10px; + border-bottom:1px solid #ddd; + border-right:1px solid #ddd; + border-top:1px solid #fff; + border-left:1px solid #fff; +} +#sidebar .notice h2 { + font-size:16px; + margin: 5px 0; + border-bottom:1px solid #aaa; +} +#sidebar .notice p { + font-size:12px; +} + +#sidebar .block { + padding-bottom: 0; +} + +#sidebar .block .content { + padding: 0 10px; +} + + +#sidebar h3 { + background: #c7d8d8; + border-bottom:1px solid #999; + padding: 5px 10px; +} + +#sidebar ul li a:link, #sidebar ul li a:visited { + font-size:14px; +} + +#sidebar ul li a:hover, #sidebar ul li a:active { + background: #E9FAE6; + color: #444; + font-size:14px; + text-decoration:underline; +} +#sidebar ul.navigation li.last a { + border-bottom: none; +} + +#sidebar ul.navigation li a:link,#sidebar ul.navigation li a:visited { + padding: 5px 10px; + color:#444; + text-decoration: none; +} +#sidebar ul.navigation li a:hover { + text-decoration:underline; +} +#sidebar .block .sidebar-block h4 { + border-bottom: 1px solid #bbb; +} +#main-navigation ul li { + background: #30423E; +} + +#main-navigation ul li:hover { + background: #23302D; +} + +#main-navigation ul li.active { + background: #e5e5e5; +} + +#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active, +#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active { + text-decoration: none; + color: #FFF; +} + +#main-navigation ul li a { + font-size: 14px; + padding: 4px 10px; +} + +#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active { + color: #333; +} +#user-navigation ul li a:hover { + text-decoration: underline; +} +.secondary-navigation { + background: #48625B; + border-bottom-color: #30423e; + border-bottom: 5px solid #30423e; +} + +.secondary-navigation ul li.active { + background-color: #30423e; +} + +.secondary-navigation ul li:hover { + background-color: #23302d; +} + +/* pagination */ + +.pagination span.current { + background: #30423e; + color: #FFF; + border: 1px solid #30423e; + -moz-border-radius:5px; +} + +.pagination a { + color: #364B69; + border: 1px solid #ddd; + -moz-border-radius:5px; +} + +.pagination a:hover { + color: #444; + background: #E9FAE6; +} + +/* tables */ + +.table th { + background: #48625B; + color: #FFF; + font-weight:normal; + padding:3px; +} + +.table th a.toggle { + display: block; + width: 12px; + height: 12px; + background: transparent url('images/tick.png') center no-repeat; + text-indent: -9999px; + -moz-outline: none; +} + +.table th.first { + width: 30px; + text-align: center; +} + +.table td { + border-bottom: 1px solid #F0F0EE; +} + +/* forms */ + +.form input.text, .form textarea.textarea { + border: 1px solid #ddd; + padding: 5px; + width: 95%; +} + +.form .navform { + padding:10px; + background-color: #E9FAE6; + font-size:14px; + border-bottom:1px solid #ddd; + border-right:1px solid #ddd; + border-top:1px solid #eee; + border-left:1px solid #eee; +} +.form .navform input { + font-size:14px; +} + +/* flash-messages */ +.flash .message { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-align:center; + margin:0 auto 5px; + width:80%; +} +.flash .message p { + margin:8px; +} +.flash .error { + border: 1px solid #fbb; + background-color: #fdd; +} +.flash .warning { + border: 1px solid #fffaaa; + background-color: #ffffcc; +} +.flash .notice { + border: 1px solid #ddf; + background-color: #eef; +} + +/* lists */ + +ul.list li { + border-bottom-color: #F0F0EE; +} + +ul.list li .item .avatar { + border-color: #F0F0EE; + margin: 3px 10px 0 0; +} + +ul.list li .left { + padding: 5px 5px; +} + +/* box */ + +#box .block { + background: #FFF; +} + +#box .block h2 { + background: #48625B; + color: #FFF; +} diff --git a/chef-server-slice/public/stylesheets/themes/bec/style.css b/chef-server-slice/public/stylesheets/themes/bec/style.css new file mode 100644 index 0000000000..c94474866a --- /dev/null +++ b/chef-server-slice/public/stylesheets/themes/bec/style.css @@ -0,0 +1,301 @@ +a:link, a:visited, a:hover, a:active { color: #33f; } +h1, h2, h3 {color:#444} + +body { + color: #333; + background: #e5e5e5; + font-family: Verdana, Arial, "Bitstream Vera Sans", sans-serif; +} + +hr { + background: #f0f0ee; +} + +p { + font-size: 12px; + line-height: 20px; +} + +input.checkbox { + vertical-align:middle; +} + +#header h1 { + font-size: 28px; + padding: 5px 0; + margin: 5px 0; +} + +.hightlight { + background-color: #ffc; +} +.small { + font-size: 11px; +} +.gray { + color: #999; +} +#header { + background: #006666; +} + +#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited { + color: #FFF; +} + +#main { + background: #e5e5e5; + width: 73%; +} + +#main .block { + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + padding: 0; + margin-bottom:20px; + padding-bottom: 20px; + background: #fff; +} + +#main .block h2.title { + margin: 0 0 20px 0; + background-color: #E6FAFA; + padding: 5px 5px 5px 15px; + font-size:18px; +} + +.main_container { + padding:10px; +} + +#footer .block p { + color:#aaa; + font-size:11px; +} + +/* #sidebar .block { background: #FFF; padding-bottom:0px; } */ + +#sidebar .notice { + background-color: #ffc; + padding: 0 10px; + border-bottom:1px solid #ddd; + border-right:1px solid #ddd; + border-top:1px solid #fff; + border-left:1px solid #fff; +} +#sidebar .notice h2 { + font-size:16px; + margin: 5px 0; + border-bottom:1px solid #aaa; +} +#sidebar .notice p { + font-size:12px; +} + +#sidebar .block { + padding-bottom: 0; +} + +#sidebar .block .content { + padding: 0 10px; +} + + +#sidebar h3 { + background: #c7d8d8; + border-bottom:1px solid #999; + padding: 5px 10px; +} + +#sidebar ul li a:link, #sidebar ul li a:visited { + font-size:12px; +} + +#sidebar ul li a:hover, #sidebar ul li a:active { + background: #e1efef; + color: #444; + font-size:12px; + text-decoration:underline; +} +#sidebar ul.navigation li.last a { + border-bottom: none; +} + +#sidebar ul.navigation li a:link,#sidebar ul.navigation li a:visited { + padding: 5px 10px; + color:#444; + text-decoration: none; +} +#sidebar ul.navigation li a:hover { + text-decoration:underline; +} +#sidebar .block .sidebar-block h4 { + border-bottom: 1px solid #bbb; +} +#main-navigation ul li { + background: #008c8c; +} + +#main-navigation ul li:hover { + background: #00b2b2; +} + +#main-navigation ul li.active { + background: #f0f0ee; +} + +#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active, +#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active { + text-decoration: none; + color: #FFF; +} + +#main-navigation ul li a { + font-size: 12px; + padding: 4px 10px; +} + +#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active { + color: #364b69; +} +#user-navigation ul li a:hover { + text-decoration: underline; +} +.secondary-navigation { + background: #006666; + border-bottom-color: #008c8c; + border-bottom: 5px solid #008c8c; +} + +.secondary-navigation ul li.active { + background-color: #008c8c; +} + +.secondary-navigation ul li:hover { + background-color: #00b2b2; +} + +/* pagination */ + +.pagination span.current { + background: #008c8c; + color: #FFF; + border: 1px solid #008c8c; + -moz-border-radius:5px; +} + +.pagination a { + color: #364B69; + border: 1px solid #ddd; + -moz-border-radius:5px; + font-size:11px; +} + +.pagination a:hover { + color: #444; + background: #E6FAFA; +} + +/* tables */ + +.table th { + background: #006666; + color: #FFF; + font-weight:normal; + padding:3px; +} + +.table th a.toggle { + display: block; + width: 12px; + height: 12px; + background: transparent url('images/tick.png') center no-repeat; + text-indent: -9999px; + -moz-outline: none; +} + +.table th.first { + width: 30px; + text-align: center; +} + +.table td { + border-bottom: 1px solid #F0F0EE; +} + +/* forms */ + +.form input.text, .form textarea.textarea { + border: 1px solid #ddd; + padding: 5px; + width:99%; +} + +.form .navform { + padding:10px; + background-color: #f1f8f8; + font-size:14px; + border-bottom:1px solid #ddd; + border-right:1px solid #ddd; + border-top:1px solid #eee; + border-left:1px solid #eee; +} +.form .navform input { + font-size:14px; +} + +.description { + color:#aaa; + font-family:Georgia, serif; +} + +/* flash-messages */ +.flash .message { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-align:center; + margin:0 auto 5px; + width:80%; +} +.flash .message p { + margin:8px; +} +.flash .error { + border: 1px solid #fbb; + background-color: #fdd; +} +.flash .warning { + border: 1px solid #fffaaa; + background-color: #ffffcc; +} +.flash .notice { + border: 1px solid #ddf; + background-color: #eef; +} + +/* lists */ + +ul.list li { + border-bottom-color: #F0F0EE; +} + +ul.list li .item .avatar { + border-color: #F0F0EE; + margin: 3px 10px 0 0; +} + +ul.list li .left { + padding: 5px 5px; +} + +/* box */ + +#box .block { + background: #FFF; +} + +#box .block h2 { + background: #006666; + color: #FFF; +} diff --git a/chef-server-slice/public/stylesheets/themes/blue/style.css b/chef-server-slice/public/stylesheets/themes/blue/style.css new file mode 100644 index 0000000000..cce8f4bdf0 --- /dev/null +++ b/chef-server-slice/public/stylesheets/themes/blue/style.css @@ -0,0 +1,280 @@ +.small { font-size:12px; } +.gray { color:#999999; } +.hightlight { background-color:#FFFFCC; } + +a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #2F427A; } +a { -moz-outline: none; } + +body { + color: #222; + background: #f0f0ee; + font-family: helvetica, arial, sans-serif; + font-size: 14px; +} + +hr { + background: #f0f0ee; + color: #f0f0ee; +} + +#header { + background: #2F427A; +} + +#header h1 { + padding: 20px 0; +} + +#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited { + color: #FFF; +} + +#user-navigation { + top: auto; + bottom: 5px; + right: 25px; +} + +#main .block .content { + background: #FFF; + padding-top: 1px; +} + +#main .block .content h2 { + margin-left: 15px; +} + +#sidebar .block { + background: #FFF; +} + +#sidebar .notice { + background: #FFFFCC; +} + +#sidebar h3 { + background: #2F427A; + color: #FFF; + border-bottom: 10px solid #262626; + font-size: 15px; +} + +#main-navigation ul li { + padding-left: 15px; +} + +#main-navigation ul li a { + padding: 8px 0; +} + +#main-navigation ul li.active { + padding: 0; + margin-left: 15px; +} + +#main-navigation ul li.active { + margin-left: 15px; +} + +#main-navigation ul li.active a { + padding: 8px 15px; +} + +#sidebar ul li a:link, #sidebar ul li a:visited { + background: #FFF; + border-bottom: 1px solid #F0F0EE; + text-decoration: none; +} + +#sidebar ul li a:hover, #sidebar ul li a:active { + background: #316291; + color: #FFF; +} + +#main-navigation { + background: #262626; +} + +#main-navigation ul li { + background: #262626; + margin-right: 0; +} + +#main-navigation ul li.active { + background: #f0f0ee; +} + +#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active, +#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active { + text-decoration: none; + color: #FFF; +} + +.secondary-navigation li a:hover { + background: #5C637A; +} + +#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active { + color: #262626; +} + +.secondary-navigation { + background: #2F427A; + border-bottom-color: #262626; + font-size: 15px; +} + +.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover { + background-color: #262626; +} + +#footer .block { + color: #FFF; + background: #262626; +} + +#footer .block p { + margin: 0; + padding: 10px; +} + +/* pagination */ + +.pagination span, .pagination a { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + padding-top: 4px; +} + +.pagination span.current { + background: #262626; + color: #FFF; + border-color: #262626; +} + +.pagination a { + color: #262626; + border-color: #262626; +} + +.pagination a:hover { + color: #FFF; + background: #262626; +} + +/* tables */ + +.table th { + background: #262626; + color: #FFF; +} + +.table td { + border-bottom:1px solid #F0F0EE; +} + +/* forms */ + +.form input.text_field, .form textarea.text_area { + width: 100%; + border: 1px solid #262626; +} + +.form input.button { + background: #EEE; + color: #262626; + padding: 2px 5px; + border: 1px solid #262626; + cursor: pointer; +} + +.form .description { + font-style: italic; + color: #8C8C8C; + font-size: .9em; +} + +/* flash-messages */ +.flash .message { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-align:center; + margin: 0 auto 15px; + +} + +.flash .message p { + margin:8px; +} +.flash .error { + border: 1px solid #fbb; + background-color: #fdd; +} +.flash .warning { + border: 1px solid #fffaaa; + background-color: #ffffcc; +} +.flash .notice { + border: 1px solid #1FDF00; + background-color: #BBFFB6; +} + +/* lists */ + +ul.list li { + border-bottom-color: #F0F0EE; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +ul.list li .item .avatar { + border-color: #F0F0EE; + border-width: 1px; + border-style: solid; + padding: 2px; +} + +/* box */ + +#box .block { + background: #FFF; +} + +#box .block h2 { + background: #2F427A; + color: #FFF; +} + + +/* rounded borders */ + +#main, #main-navigation, #main-navigation li, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li, +#footer .block, .form input.button, #box .block, #box .block h2 { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +.table th.last { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation ul li.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block { + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; +}
\ No newline at end of file diff --git a/chef-server-slice/public/stylesheets/themes/default/style.css b/chef-server-slice/public/stylesheets/themes/default/style.css new file mode 100644 index 0000000000..e7a5ee1271 --- /dev/null +++ b/chef-server-slice/public/stylesheets/themes/default/style.css @@ -0,0 +1,267 @@ +.small { font-size:11px; } +.gray { color:#999999; } +.hightlight { background-color:#FFFFCC; } + +a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #7A1818; } +a { -moz-outline: none; } + +body { + color: #222; + background: #f0f0ee; + font-family: helvetica, arial, sans-serif; +} + +hr { + background: #f0f0ee; + color: #f0f0ee; +} + +#header { + background: #7A1818; +} + +#header h1 { + padding: 20px 0; +} + +#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited { + color: #FFF; +} + +#user-navigation { + top: auto; + bottom: 5px; + right: 25px; +} + +#main .block .content { + background: #FFF; + padding-top: 1px; +} + +#main .block .content h2 { + margin-left: 15px; +} + +#sidebar .block { + background: #FFF; +} + +#sidebar h3 { + background: #7A1818; + color: #FFF; + border-bottom: 10px solid #262626; +} + +#main-navigation ul li { + padding-left: 15px; +} + +#main-navigation ul li a { + padding: 8px 0; +} + +#main-navigation ul li.active { + padding: 0; + margin-left: 15px; +} + +#main-navigation ul li.active { + margin-left: 15px; +} + +#main-navigation ul li.active a { + padding: 8px 15px; +} + +#sidebar ul li a:link, #sidebar ul li a:visited { + background: #FFF; + border-bottom: 1px solid #F0F0EE; + text-decoration: none; +} + +#sidebar ul li a:hover, #sidebar ul li a:active { + background: #470E0E; + color: #FFF; +} + +#main-navigation { + background: #262626; +} + +#main-navigation ul li { + background: #262626; + margin-right: 0; +} + +#main-navigation ul li.active { + background: #f0f0ee; +} + +#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active, +#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active { + text-decoration: none; + color: #FFF; +} + +.secondary-navigation li a:hover { + background: #470E0E; +} + +#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active { + color: #262626; +} + +.secondary-navigation { + background: #7A1818; + border-bottom-color: #262626; +} + +.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover { + background-color: #262626; +} + +#footer .block { + color: #FFF; + background: #262626; +} + +#footer .block p { + margin: 0; + padding: 10px; +} + +/* pagination */ + +.pagination span.current { + background: #262626; + color: #FFF; + border-color: #262626; +} + +.pagination a { + color: #262626; + border-color: #262626; +} + +.pagination a:hover { + color: #FFF; + background: #262626; +} + +/* tables */ + +.table th { + background: #262626; + color: #FFF; +} + +.table td { + border-bottom:1px solid #F0F0EE; +} + +/* forms */ + +.form input.text_field, .form textarea.text_area { + width: 100%; + border: 1px solid #262626; +} + +.form input.button { + background: #EEE; + color: #262626; + padding: 2px 5px; + border: 1px solid #262626; + cursor: pointer; +} + +.form .description { + font-style: italic; + color: #8C8C8C; + font-size: .9em; +} + +/* flash-messages */ +.flash .message { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-align:center; + margin: 0 auto 15px; + +} + +.flash .message p { + margin:8px; +} +.flash .error { + border: 1px solid #fbb; + background-color: #fdd; +} +.flash .warning { + border: 1px solid #fffaaa; + background-color: #ffffcc; +} +.flash .notice { + border: 1px solid #1FDF00; + background-color: #BBFFB6; +} + +/* lists */ + +ul.list li { + border-bottom-color: #F0F0EE; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +ul.list li .item .avatar { + border-color: #F0F0EE; + border-width: 1px; + border-style: solid; + padding: 2px; +} + +/* box */ + +#box .block { + background: #FFF; +} + +#box .block h2 { + background: #7A1818; + color: #FFF; +} + + +/* rounded borders */ + +#main, #main-navigation, #main-navigation li, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li, +#footer .block, .form input.button, #box .block, #box .block h2 { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +.table th.last { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation ul li.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block { + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; +}
\ No newline at end of file diff --git a/chef-server-slice/public/stylesheets/themes/djime-cerulean/style.css b/chef-server-slice/public/stylesheets/themes/djime-cerulean/style.css new file mode 100644 index 0000000000..9b050c6785 --- /dev/null +++ b/chef-server-slice/public/stylesheets/themes/djime-cerulean/style.css @@ -0,0 +1,298 @@ +/** + * Cerulean web-app-theme made for Djime: http://github.com/mikl/djime/ + * + * Please note that we're using CSSEdit's @group comment syntax. + * + * Colour sheme: + * Cerulean: #007BA7 + * Bright blue: #01B8DE + * Near-white: #F7F7F8 + * Silver grey: #C2C8D1 + * Dark blue: #001C26 + * + * http://www.colourlovers.com/palette/646252/Cerulean_touch + */ + +/* @group General styles */ + +.small { font-size:11px; } +.gray { color:#999; } +.hightlight { background-color:#ffc; } + +a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #007BA7; } + +body { + color: #222; + background: #C2C8D1; + font-family: "Helvetica Neue",Helvetica,Arial,"Bitstream Vera Sans",sans-serif; +} + +hr { + background: #EEF0F0; + color: #EEF0F0; +} + +/* @end */ + +/* @group Header */ + +#header { + background: #007BA7; +} + +#header h1 { + padding: 20px 0; +} + +#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited { + color: #F7F7F8; +} + +/* @end */ + +#user-navigation { + top: auto; + bottom: 5px; + right: 25px; +} + +#main .block .content { + background: #F7F7F8; + padding-top: 1px; +} + +#main .block .content h2 { + margin-left: 15px; +} + + + +/* @group Main navigation */ + +#main-navigation ul li { + padding-left: 0; +} + +#main-navigation ul li a { + padding: 8px 0; +} + +#main-navigation ul li a { + padding: 8px 15px; +} + +#main-navigation { + background-color: #005573; +} + +#main-navigation ul li a:hover { + background-color: #001C26; +} + +#main-navigation ul li.active a { + background-color: #C2C8D1; + background: -webkit-gradient(linear, left top, left bottom, from(#C2C8D1), to(#C2C8D1), color-stop(0.5, #F7F7F8), color-stop(0.5, #F7F7F8)); + +} + +/* @end */ + +/* @group Secondary navigation */ + +.secondary-navigation li a:hover { + background: #005573; +} + +.secondary-navigation { + background: #007BA7; + border-bottom-width: 7px; + border-bottom-color: #005573; +} + +.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover { + background-color: #005573; +} + +/* @end */ + +/* @group Sidebar */ + +#sidebar .block { + background: #F7F7F8; +} + +#sidebar h3 { + background: #007BA7; + color: #F7F7F8; + border-bottom: 7px solid #005573; +} + +#sidebar ul li a:link, #sidebar ul li a:visited { + background: #F7F7F8; + border-bottom: 1px solid #EEF0F0; + text-decoration: none; +} + +#sidebar ul li a:hover, #sidebar ul li a:active { + background: #005573; + color: #F7F7F8; +} + +/* @end */ + +#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active, +#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active { + text-decoration: none; + color: #F7F7F8; +} + +#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active { + color: #001C26; +} + +#footer .block { + color: #F7F7F8; + background: #005573; +} + +#footer .block p { + margin: 0; + padding: 10px; +} + +/* pagination */ + +.pagination span.current { + background: #005573; + color: #F7F7F8; + border-color: #005573; +} + +.pagination a, +.pagination span { + color: #001C26; + border-color: #005573; +} + +.pagination a:hover { + color: #F7F7F8; + background: #005573; +} + +/* tables */ + +.table th { + background: #C2C8D1; + color: #001C26; +} + +.table td { + border-bottom:1px solid #EEF0F0; +} + +/* forms */ + +.form input.text_field, .form textarea.text_area { + width: 100%; + border: 1px solid #001C26; +} + +.form input.button { + background: #EEE; + color: #001C26; + padding: 2px 5px; + border: 1px solid #001C26; + cursor: pointer; +} + +.form .description { + color: #8C8C8C; + font-size: .9em; +} + +/* @group Flash messages */ + +.flash .message { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-align:center; + margin: 0 auto 15px; +} + +.flash .message p { + margin:8px; +} +.flash .error { + border: 1px solid #fbb; + background-color: #fdd; +} +.flash .warning { + border: 1px solid #e0d300; + background-color: #ffffcc; +} +.flash .notice { + border: 1px solid #8ec4df; + background-color: #dffaff; +} + +/* @end */ + +/* lists */ + +ul.list li { + border-bottom-color: #EEF0F0; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +ul.list li .item .avatar { + border-color: #EEF0F0; + border-width: 1px; + border-style: solid; + padding: 2px; +} + +/* box */ + +#box .block { + background: #F7F7F8; +} + +#box .block h2 { + background: #005573; + color: #F7F7F8; +} + + +/* rounded borders */ + +#main, #main-navigation, #main-navigation li, #main-navigation li a, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li, +#footer .block, .form input.button, #box .block, #box .block h2 { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +.table th.last { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation ul li.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block { + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; +}
\ No newline at end of file diff --git a/chef-server-slice/public/stylesheets/themes/kathleene/style.css b/chef-server-slice/public/stylesheets/themes/kathleene/style.css new file mode 100644 index 0000000000..e68a545431 --- /dev/null +++ b/chef-server-slice/public/stylesheets/themes/kathleene/style.css @@ -0,0 +1,272 @@ +.small { font-size:11px; } +.gray { color:#999999; } +.hightlight { background-color:#FFFFCC; } + +a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #AF0000; } +a { -moz-outline: none; } + +body { + color: #222; + background: #f0f0ee; + font-family: helvetica, arial, sans-serif; +} + +hr { + background: #f0f0ee; + color: #f0f0ee; +} + +#header { + background: #AF0000; +} + +#header h1 { + padding: 20px 0; +} + +#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited { + color: #FFF; +} + +#user-navigation { + top: auto; + bottom: 5px; + right: 25px; +} + +#main .block .content { + background: #FFF; + padding-top: 1px; +} + +#main .block .content h2 { + margin-left: 15px; +} + +#sidebar .block { + background: #FFF; +} + +#sidebar h3 { + background: #AF0000; + color: #FFF; + border-bottom: 5px solid #2a0000; +} + +#main-navigation ul li { + padding-left: 15px; +} + +#main-navigation ul li a { + padding: 8px 0; +} + +#main-navigation ul li.active { + padding: 0; + margin-left: 15px; +} + +#main-navigation ul li.active { + margin-left: 15px; +} + +#main-navigation ul li.active a { + padding: 8px 15px; +} + +#sidebar ul li a:link, #sidebar ul li a:visited { + background: #FFF; + border-bottom: 1px solid #F0F0EE; + text-decoration: none; +} + +#sidebar ul li a:hover, #sidebar ul li a:active { + background: #470E0E; + color: #FFF; +} + +#main-navigation { + background: #2a0000; +} + +#main-navigation ul li { + background: #2a0000; + margin-right: 0; +} + +#main-navigation ul li.active { + background: #f0f0ee; +} + +#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active, +#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active { + text-decoration: none; + color: #FFF; +} + +.secondary-navigation li a:hover { + background: #470E0E; +} + +#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active { + color: #2a0000; +} + +.secondary-navigation { + background: #AF0000; + border-bottom-color: #2a0000; +} + +.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover { + background-color: #2a0000; +} + +#footer .block { + color: #FFF; + background: #2a0000; +} + +#footer .block p { + margin: 0; + padding: 10px; +} + +/* pagination */ + +.pagination span.current { + background: #2a0000; + color: #FFF; + border-color: #2a0000; +} + +.pagination a { + color: #2a0000; + border-color: #2a0000; +} + +.pagination a:hover { + color: #FFF; + background: #2a0000; +} + +/* tables */ + +.table th { + background: #100000; + border-bottom: 3px solid #700000; + color: #FFF; +} + +.table td { + border-bottom:1px solid #F0F0EE; +} + +/* forms */ + +.form input.text_field, .form textarea.text_area { + width: 100%; + border: 1px solid #2a0000; +} + +.form input.button { + background: #EEE; + color: #2a0000; + padding: 2px 5px; + border: 1px solid #2a0000; + cursor: pointer; +} + +.form .description { + font-style: italic; + color: #8C8C8C; + font-size: .9em; +} + +/* flash-messages */ +.flash .message { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-align:center; + margin: 0 auto 15px; + +} + +.flash .message p { + margin:8px; +} +.flash .error { + border: 1px solid #fbb; + background-color: #fdd; +} +.flash .warning { + border: 1px solid #fffaaa; + background-color: #ffffcc; +} +.flash .notice { + border: 1px solid #1FDF00; + background-color: #BBFFB6; +} + +/* lists */ + +ul.list li { + border-bottom-color: #F0F0EE; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +ul.list li .item .avatar { + border-color: #F0F0EE; + border-width: 1px; + border-style: solid; + padding: 2px; +} + +/* box */ + +#box .block { + background: #FFF; +} + +#box .block h2 { + background: #AF0000; + color: #FFF; +} + + +/* rounded borders */ + +#main, #main-navigation, #main-navigation li, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li, +#footer .block, .form input.button, #box .block, #box .block h2 { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +.table th.last { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation ul li.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block { + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; +} + +.secondary-navigation { + border-bottom-width: 5px; +} diff --git a/chef-server-slice/public/stylesheets/themes/orange/style.css b/chef-server-slice/public/stylesheets/themes/orange/style.css new file mode 100644 index 0000000000..90e7d8de58 --- /dev/null +++ b/chef-server-slice/public/stylesheets/themes/orange/style.css @@ -0,0 +1,263 @@ +.small { font-size:11px; } +.gray { color:#999999; } +.hightlight { background-color:#FFFFCC; } + +a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #ff7900; } +a { -moz-outline: none; } + +body { + color: #222; + background: #f0f0ee; + font-family: helvetica, arial, sans-serif; +} + +hr { + background: #f0f0ee; + color: #f0f0ee; +} + +#header { + background: #ff7900; +} + +#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited { + color: #FFF; +} + +#user-navigation { + top: auto; + bottom: 5px; + right: 25px; +} + +#main .block .content { + background: #FFF; + padding-top: 1px; +} + +#main .block .content h2 { + margin-left: 15px; +} + +#sidebar .block { + background: #FFF; +} + +#sidebar h3 { + background: #ff7900; + color: #FFF; + border-bottom: 10px solid #262626; +} + +#main-navigation ul li { + padding-left: 15px; +} + +#main-navigation ul li a { + padding: 8px 0; +} + +#main-navigation ul li.active { + padding: 0; + margin-left: 15px; +} + +#main-navigation ul li.active { + margin-left: 15px; +} + +#main-navigation ul li.active a { + padding: 8px 15px; +} + +#sidebar ul li a:link, #sidebar ul li a:visited { + background: #FFF; + border-bottom: 1px solid #F0F0EE; + text-decoration: none; +} + +#sidebar ul li a:hover, #sidebar ul li a:active { + background: #863800; + color: #FFF; +} + +#main-navigation { + background: #262626; +} + +#main-navigation ul li { + background: #262626; + margin-right: 0; +} + +#main-navigation ul li.active { + background: #f0f0ee; +} + +#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active, +#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active { + text-decoration: none; + color: #FFF; +} + +.secondary-navigation li a:hover { + background: #863800; +} + +#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active { + color: #262626; +} + +.secondary-navigation { + background: #ff7900; + border-bottom-color: #262626; +} + +.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover { + background-color: #262626; +} + +#footer .block { + color: #FFF; + background: #262626; + width: 70%; +} + +#footer .block p { + margin: 0; + padding: 10px; +} + +/* pagination */ + +.pagination span.current { + background: #262626; + color: #FFF; + border-color: #262626; +} + +.pagination a { + color: #262626; + border-color: #262626; +} + +.pagination a:hover { + color: #FFF; + background: #262626; +} + +/* tables */ + +.table th { + background: #262626; + color: #FFF; +} + +.table td { + border-bottom:1px solid #F0F0EE; +} + +/* forms */ + +.form input.text, .form textarea { + width: 100%; + border: 1px solid #262626; +} + +.form input.button { + background: #EEE; + color: #262626; + padding: 2px 5px; + border: 1px solid #262626; + cursor: pointer; +} + +.form .description { + font-style: italic; + color: #8C8C8C; + font-size: .9em; +} + +/* flash-messages */ +.flash .message { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-align:center; + margin:0 auto 5px; + +} +.flash .message p { + margin:8px; +} +.flash .error { + border: 1px solid #fbb; + background-color: #fdd; +} +.flash .warning { + border: 1px solid #fffaaa; + background-color: #ffffcc; +} +.flash .notice { + border: 1px solid #1FDF00; + background-color: #BBFFB6; +} + +/* lists */ + +ul.list li { + border-bottom-color: #F0F0EE; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +ul.list li .item .avatar { + border-color: #F0F0EE; + border-width: 1px; + border-style: solid; + padding: 2px; +} + +/* box */ + +#box .block { + background: #FFF; +} + +#box .block h2 { + background: #ff7900; + color: #FFF; +} + + +/* rounded borders */ + +#main, #main-navigation, #main-navigation li, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li, +#footer .block, .form input.button, #box .block, #box .block h2 { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +.table th.last { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation ul li.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block { + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; +}
\ No newline at end of file diff --git a/chef-server-slice/public/stylesheets/themes/reidb-greenish/style.css b/chef-server-slice/public/stylesheets/themes/reidb-greenish/style.css new file mode 100644 index 0000000000..23e5245eb4 --- /dev/null +++ b/chef-server-slice/public/stylesheets/themes/reidb-greenish/style.css @@ -0,0 +1,301 @@ +.small { font-size:11px; } +.gray { color:#a2b0b6; } +.hightlight { background-color:#d6e7c7; } + +a:link, a:visited, a:hover, a:active { color: #81B953; } +h1, h2, h3 { color: #3B5526; } +a { -moz-outline: none; } + +body { + color: #222; + background: #e4ebe4; + font-family: helvetica, arial, sans-serif; +} + +hr { + background: #f0f0ee; + color: #f0f0ee; +} + +#header { + background: #c9deb7; + +} + +#header h1{ + padding: 20px 0; + font-weight: bold; + +} + +#header h1 a:link, #header h1 a:active, +#header h1 a:hover, #header h1 a:visited { + color: #3B5526; +} + +#main .block .content { + background: #FFF; + padding-top: 1px; +} + +#main .block .content h2 { + margin-left: 15px; +} + +#main .content { border: 1px solid #81B953;} + +#sidebar .block { + background: #FFF; + border: none; +} + +#sidebar h3 { + padding: 8px 12px; + background: #3B5526; + color: #FFF; + font-weight: bold; + border-bottom: 5px solid #81B953; +} + + +#sidebar ul li a:link, +#sidebar ul li a:visited { + background: #FFF; + border-bottom: 1px solid #F0F0EE; + text-decoration: none; +} + +#sidebar ul li a:hover, +#sidebar ul li a:active { + background: #D3E8C1; + color: #FFF; +} + +#main-navigation { + background: #44721e; + margin-right: 20px; + padding: 7px 7px 0 7px; +} + +#main-navigation ul li { + background: #91B96F; + margin-right: 7px; +} + +#main-navigation ul li a { + padding: 10px 10px 5px 10px; +} + +#main-navigation ul li.active { + background: #e4ebe4; + border: 1px solid #91B96F; + border-bottom: none; + font-weight: bold; +} + +#main-navigation ul li a:hover, +#main-navigation ul li a:link, +#main-navigation ul li a:visited, +#main-navigation ul li a:active, +.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, +.secondary-navigation ul li a:hover, .secondary-navigation ul li a:active { + text-decoration: none; + color: #FFF; +} + +#user-navigation ul li a:link, +#user-navigation ul li a:visited, +#user-navigation ul li a:active { + color: #3B5526; +} + +#user-navigation ul li a:hover { color: #fff; } + +#main-navigation ul li.active a:link, +#main-navigation ul li.active a:visited, +#main-navigation ul li.active a:hover, +#main-navigation ul li.active a:active { + color: #262626; +} + +.secondary-navigation { + background: #3B5526; + border-bottom: 5px solid #81b953; +} + +.secondary-navigation ul li a { +display:block; +padding: 8px 12px; +} + +.secondary-navigation ul li.active {background: #81b953; font-weight: bold;} +.secondary-navigation ul li.active a:hover { + background-color: #81B953; +} + +.secondary-navigation li a:hover { + background: #81B953; +} + +#footer .block { + color: #FFF; + background: #3B5526; +} + +#footer .block p { + margin: 0; + padding: 5px; +} + +/* pagination */ + +.pagination span.current { + background: #262626; + color: #FFF; + border-color: #262626; +} + +.pagination a { + color: #262626; + border-color: #262626; +} + +.pagination a:hover { + color: #FFF; + background: #262626; +} + +/* tables */ + + +.table th { + background: #253618; + color: #FFF; +} + +.table tr th { padding: 5px; } + +.table td { + border-bottom:1px solid #F0F0EE; +} +.table tr.odd {background: #ebfadf;} +.table tr.even {background: #d3e8c1;} + + +/* forms */ + +.form input.text_field, .form textarea.text_area { + width: 100%; + border: 1px solid #262626; +} + +.form input.button { + background: #EEE; + color: #262626; + padding: 2px 5px; + border: 1px solid #262626; + cursor: pointer; +} + +.form .description { + font-style: italic; + color: #8C8C8C; + font-size: .9em; +} + +/* flash-messages */ +.flash .message { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + text-align:center; + margin: 0 auto 15px; + +} + +.flash .message p { + margin:8px; +} +.flash .error { + border: 1px solid #fbb; + background-color: #fdd; +} +.flash .warning { + border: 1px solid #fffaaa; + background-color: #ffffcc; +} +.flash .notice { + border: 1px solid #1FDF00; + background-color: #BBFFB6; +} + +/* lists */ + +ul.list li { + border-bottom-color: #F0F0EE; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +ul.list li .item .avatar { + border-color: #F0F0EE; + border-width: 1px; + border-style: solid; + padding: 2px; +} + +/* box */ + +#box .block { + background: #FFF; +} + +#box .block h2 { + color: #fff; + background: #3B5526; + border-bottom: 5px solid #81b953; +} + +#box .block .content { border: 1px solid #81b953; border} + +/* login */ + +#block-login { } +#block-login h2 { background: #3B5526;border-bottom: 5px solid #81b953;} + +/* rounded borders */ + +#main, #main-navigation, #main-navigation li, +.secondary-navigation, #main .block, #sidebar .block, +#sidebar h3, ul.list li, #footer .block, +.form input.button, #box .block, #box .block h2 { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation li.first a, +.secondary-navigation ul li.first, +.table th.first, .table th.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +.table th.last { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} + +.secondary-navigation ul li.first { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; +} + +#sidebar, #sidebar .block, #main .block, +#sidebar ul.navigation, ul.list li, +#footer .block, .form input.button, #box .block { + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; +}
\ No newline at end of file diff --git a/chef-server/bin/chef-indexer b/chef-server/bin/chef-indexer index f36e09a8c3..74eb5efca5 100755 --- a/chef-server/bin/chef-indexer +++ b/chef-server/bin/chef-indexer @@ -31,7 +31,7 @@ config = { :config_file => "/etc/chef/server.rb", } opts = OptionParser.new do |opts| - opts.banner = "Usage: #{$0} [-d DIR|-r FILE] (options)" + opts.banner = "Usage: #{$0} (options)" opts.on("-c CONFIG", "--config CONFIG", "The Chef Config file to use") do |c| config[:config_file] = c end @@ -64,6 +64,8 @@ end Chef::Config.from_file(config[:config_file]) Chef::Config.configure { |c| c.merge!(config) } +Chef::Daemon.change_privilege + if Chef::Config[:daemonize] unless Chef::Config[:log_location].is_a? IO Chef::Log.init(Chef::Config[:log_location]) diff --git a/chef-server/config/init.rb b/chef-server/config/init.rb index 1d091484e2..aedc9b6753 100644 --- a/chef-server/config/init.rb +++ b/chef-server/config/init.rb @@ -23,4 +23,5 @@ end Merb::BootLoader.after_app_loads do # This will get executed after your app's classes have been loaded. + OpenID::Util.logger = Merb.logger end diff --git a/chef-server/contrib/el/chef-indexer.config b/chef-server/contrib/el/chef-indexer.config new file mode 100644 index 0000000000..1be9c9f0d2 --- /dev/null +++ b/chef-server/contrib/el/chef-indexer.config @@ -0,0 +1,10 @@ +# +# Chef Server Config File +# + +log_level :info +search_index_path "/var/lib/chef/search_index" + +Chef::Log::Formatter.show_time = false + +pid_file "/var/run/chef/chef-indexer.pid" diff --git a/chef-server/contrib/el/chef-indexer.init b/chef-server/contrib/el/chef-indexer.init new file mode 100644 index 0000000000..94b5b6ecfd --- /dev/null +++ b/chef-server/contrib/el/chef-indexer.init @@ -0,0 +1,76 @@ +#!/bin/bash +# Startup script for chef-indexer +# +# chkconfig: - 75 25 +# description: Server component of the Chef systems integration framework. +# processname: chef-indexer +# +# config: /etc/sysconfig/chef-indexer +# pidfile: /var/run/chef/chef-indexer.pid + +# Source function library +. /etc/init.d/functions + +[ -f /etc/sysconfig/chef-indexer ] && . /etc/sysconfig/chef-indexer + +prog="chef-indexer" +PIDFILE=/var/run/chef/chef-indexer.pid +LOCKFILE=/var/lock/subsys/$prog +CONFIG=/etc/chef/indexer.rb +USER="chef" +GROUP="chef" +LOGFILE=/var/log/chef/chef-indexer.log +OPTIONS= + +start() { + echo -n "Starting $prog:" + daemon chef-indexer -d -c "$CONFIG" -u "$USER" -g "$GROUP" -L "$LOGFILE" "$OPTIONS" ">/dev/null" + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch ${LOCKFILE} + return $RETVAL +} + +stop() { + echo -n "Stopping $prog: " + if [ -f $PIDFILE ]; then + killproc chef-indexer + RETVAL=$? + if [ $RETVAL -ne 0 ]; then + failure; + fi; + else + RETVAL=1 + failure; + fi + rm -f $LOCKFILE + echo + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + condrestart) + if [ -f $LOCKFILE ]; then + stop + start + fi + ;; + status) + status chef-indexer + ;; + *) + echo "Usage: $0 {start|stop|restart|condrestart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/chef-server/contrib/el/chef-server.config b/chef-server/contrib/el/chef-server.config new file mode 100644 index 0000000000..6d2a7794e0 --- /dev/null +++ b/chef-server/contrib/el/chef-server.config @@ -0,0 +1,22 @@ +# +# Chef Server Config File +# + +log_level :info +ssl_verify_mode :verify_none +registration_url "http://cserver:4000" +openid_url "http://cserver:4001" +template_url "http://cserver:4000" +remotefile_url "http://cserver:4000" +search_url "http://cserver:4000" +cookbook_path [ "/var/lib/chef/site-cookbooks", "/var/lib/chef/cookbooks" ] + +merb_root "/var/lib/chef/merb" +node_path "/etc/chef/node" +file_store_path "/var/lib/chef/store" +search_index_path "/var/lib/chef/search_index" +openid_store_path "/var/lib/chef/openid/db" +openid_cstore_path "/var/lib/chef/openid/cstore" +file_cache_path "/var/lib/chef/cache" + +Chef::Log::Formatter.show_time = false diff --git a/chef-server/contrib/el/chef-server.init b/chef-server/contrib/el/chef-server.init new file mode 100644 index 0000000000..5e5e9231d2 --- /dev/null +++ b/chef-server/contrib/el/chef-server.init @@ -0,0 +1,77 @@ +#!/bin/bash +# Startup script for chef-server +# +# chkconfig: - 65 35 +# description: Server component of the Chef systems integration framework. +# processname: chef-server +# +# config: /etc/sysconfig/chef-server +# pidfile: /var/run/chef/chef-server.pid + +# Source function library +. /etc/init.d/functions + +[ -f /etc/sysconfig/chef-server ] && . /etc/sysconfig/chef-server + +prog="chef-server" +PIDFILE=/var/run/chef/chef-server.pid +LOCKFILE=/var/lock/subsys/$prog +CONFIG=/etc/chef/server.rb +USER="chef" +GROUP="chef" +CLUSTER_NODES=2 +LOGFILE=/var/log/chef/chef-server-merb.log +OPTIONS= + +start() { + echo -n "Starting $prog:" + daemon chef-server -d -c "$CLUSTER_NODES" -C "$CONFIG" -u "$USER" -G "$GROUP" -L "$LOGFILE" -P "$PIDFILE" "$OPTIONS" ">/dev/null" + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch ${LOCKFILE} + return $RETVAL +} + +stop() { + echo -n "Stopping $prog: " + if [ -f $PIDFILE ]; then + killproc chef-server + RETVAL=$? + if [ $RETVAL -ne 0 ]; then + failure; + fi; + else + RETVAL=1 + failure; + fi + rm -f $LOCKFILE + echo + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + condrestart) + if [ -f $LOCKFILE ]; then + stop + start + fi + ;; + status) + status chef-server + ;; + *) + echo "Usage: $0 {start|stop|restart|condrestart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/chef-server/lib/chef/search.rb b/chef-server/lib/chef/search.rb index a26669f76c..3ecd89cbdd 100644 --- a/chef-server/lib/chef/search.rb +++ b/chef-server/lib/chef/search.rb @@ -27,7 +27,7 @@ class Chef @index = Ferret::Index::Index.new(:path => Chef::Config[:search_index_path]) end - def search(type, query, attributes, &block) + def search(type, query="*", attributes=[], &block) search_query = build_search_query(type, query) start_time = Time.now results = [] diff --git a/chef/bin/chef-client b/chef/bin/chef-client index df06e068fd..1c1b38bd19 100755 --- a/chef/bin/chef-client +++ b/chef/bin/chef-client @@ -79,6 +79,8 @@ end Chef::Config.from_file(config[:config_file]) Chef::Config.configure { |c| c.merge!(config) } +Chef::Daemon.change_privilege + if Chef::Config[:daemonize] unless Chef::Config[:log_location].is_a? IO Chef::Log.init(Chef::Config[:log_location]) diff --git a/chef/bin/chef-solo b/chef/bin/chef-solo index 5543faf9c3..ecc2c57f8a 100755 --- a/chef/bin/chef-solo +++ b/chef/bin/chef-solo @@ -34,7 +34,7 @@ config = { Chef::Config[:solo] = true opts = OptionParser.new do |opts| - opts.banner = "Usage: #{$0} [-d DIR|-r FILE] (options)" + opts.banner = "Usage: #{$0} (options)" opts.on("-c CONFIG", "--config CONFIG", "The Chef Config file to use") do |c| config[:config_file] = c end diff --git a/chef/contrib/el/chef-client.config b/chef/contrib/el/chef-client.config new file mode 100644 index 0000000000..5be5f35114 --- /dev/null +++ b/chef/contrib/el/chef-client.config @@ -0,0 +1,16 @@ +# +# Chef Client Config File +# + +log_level :info +ssl_verify_mode :verify_none +registration_url "http://127.0.0.1:4000" +openid_url "http://127.0.0.1:4001" +template_url "http://127.0.0.1:4000" +remotefile_url "http://127.0.0.1:4000" +search_url "http://127.0.0.1:4000" + +pid_file "/var/run/chef/chef-client.pid" + +#interval 1800 +#splay 0 diff --git a/chef/contrib/el/chef-client.init b/chef/contrib/el/chef-client.init new file mode 100644 index 0000000000..332e9606d9 --- /dev/null +++ b/chef/contrib/el/chef-client.init @@ -0,0 +1,74 @@ +#!/bin/bash +# Startup script for chef-client +# +# chkconfig: - 98 02 +# description: Client component of the Chef systems integration framework. +# processname: chef-client +# +# config: /etc/sysconfig/chef-client +# pidfile: /var/run/chef/chef-client.pid + +# Source function library +. /etc/init.d/functions + +[ -f /etc/sysconfig/chef-client ] && . /etc/sysconfig/chef-client + +prog="chef-client" +pidfile=${PIDFILE-/var/run/chef/chef-client.pid} +lockfile=${LOCKFILE-/var/lock/subsys/$prog} +config=${CONFIG-/etc/chef/client.rb} +logfile=${LOGFILE-/var/log/chef/chef-client.log} +OPTIONS= + +start() { + echo -n "Starting $prog:" + daemon chef-client -d -c "$config" -L "$logfile" "$OPTIONS" ">/dev/null" + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch ${lockfile} + return $RETVAL +} + +stop() { + echo -n "Stopping $prog: " + if [ -f $pidfile ]; then + killproc chef-client + RETVAL=$? + if [ $RETVAL -ne 0 ]; then + failure; + fi; + else + RETVAL=1 + failure; + fi + rm -f $lockfile + echo + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + condrestart) + if [ -f $lockfile ]; then + stop + start + fi + ;; + status) + status chef-client + ;; + *) + echo "Usage: $0 {start|stop|restart|condrestart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb index 9c33ccff53..f9fda13cf0 100644 --- a/chef/lib/chef/config.rb +++ b/chef/lib/chef/config.rb @@ -41,8 +41,6 @@ class Chef :interval => nil, :splay => nil, :solo => false, - :user => nil, - :group => nil, :json_attribs => nil, :cookbook_path => [ "/var/chef/site-cookbooks", "/var/chef/cookbooks" ], :validation_token => nil, @@ -62,8 +60,8 @@ class Chef :template_url => "http://localhost:4000", :remotefile_url => "http://localhost:4000", :search_url => "http://localhost:4000", - :couchdb_database => "chef", :couchdb_version => nil, + :couchdb_database => "chef", :openid_store_couchdb => false, :openid_cstore_couchdb => false, :openid_store_path => "/var/chef/openid/db", diff --git a/chef/lib/chef/couchdb.rb b/chef/lib/chef/couchdb.rb index 2c488b2ec2..a374205021 100644 --- a/chef/lib/chef/couchdb.rb +++ b/chef/lib/chef/couchdb.rb @@ -25,11 +25,10 @@ require 'json' class Chef class CouchDB include Chef::Mixin::ParamsValidate - + def initialize(url=nil) url ||= Chef::Config[:couchdb_url] @rest = Chef::REST.new(url) - Chef::Config[:couchdb_version] ||= @rest.run_request(:GET, URI.parse(@rest.url + "/"), false, 10, false)["version"].gsub(/-.+/,"").to_f end def create_db @@ -145,13 +144,8 @@ class Chef end end - private - - def safe_name(name) - name.gsub(/\./, "_") - end - def view_uri(design, view) + Chef::Config[:couchdb_version] ||= @rest.run_request(:GET, URI.parse(@rest.url + "/"), false, 10, false)["version"].gsub(/-.+/,"").to_f case Chef::Config[:couchdb_version] when 0.9 "#{Chef::Config[:couchdb_database]}/_design/#{design}/_view/#{view}" @@ -159,6 +153,12 @@ class Chef "#{Chef::Config[:couchdb_database]}/_view/#{design}/#{view}" end end - + + private + + def safe_name(name) + name.gsub(/\./, "_") + end + end end diff --git a/chef/lib/chef/daemon.rb b/chef/lib/chef/daemon.rb index 7ab66ab710..2b6e6db3ea 100644 --- a/chef/lib/chef/daemon.rb +++ b/chef/lib/chef/daemon.rb @@ -40,12 +40,12 @@ class Chef exit if fork Process.setsid exit if fork - change_privilege Chef::Log.info("Forked, in #{Process.pid}. Priveleges: #{Process.euid} #{Process.egid}") File.umask 0000 $stdin.reopen("/dev/null") $stdout.reopen("/dev/null", "a") $stdout.reopen($stdout) + save_pid_file at_exit { remove_pid_file } rescue NotImplementedError => e Chef.fatal!("There is no fork: #{e.message}") @@ -167,4 +167,4 @@ class Chef end end end -end
\ No newline at end of file +end diff --git a/chef/lib/chef/mixin/command.rb b/chef/lib/chef/mixin/command.rb index d787059548..f0c76b09e9 100644 --- a/chef/lib/chef/mixin/command.rb +++ b/chef/lib/chef/mixin/command.rb @@ -113,35 +113,11 @@ class Chef end exec_processing_block = lambda do |pid, stdin, stdout, stderr| - stdout.sync = true - stderr.sync = true - Chef::Log.debug("---- Begin output of #{args[:command]} ----") - - stdout_finished = false - stderr_finished = false - - while !stdout_finished || !stderr_finished - ready = IO.select([stdout, stderr], nil, nil, 1.0) - if ready && ready.first.include?(stdout) - line = stdout.gets - if line - command_output << "STDOUT: #{line.strip}\n" - Chef::Log.debug("STDOUT: #{line.strip}") - else - stdout_finished = true - end - end - if ready && ready.first.include?(stderr) - line = stderr.gets - if line - command_output << "STDERR: #{line.strip}\n" - Chef::Log.debug("STDERR: #{line.strip}") - else - stderr_finished = true - end - end - end + Chef::Log.debug("STDOUT: #{stdout.string.chomp!}") + Chef::Log.debug("STDERR: #{stderr.string.chomp!}") + command_output << "STDOUT: #{stdout.string.chomp!}" + command_output << "STDERR: #{stderr.string.chomp!}" Chef::Log.debug("---- End output of #{args[:command]} ----") end @@ -154,10 +130,6 @@ class Chef status = nil - # I don't understand what this :waitlast argument is doing, but setting it to true is causing the block in popen4 - # not to wait until the command is finished to execute, kind of the opposite of what I would guess from the name - args[:waitlast] ||= true - Dir.chdir(args[:cwd]) do if args[:timeout] begin @@ -200,8 +172,13 @@ class Chef # # Thanks Ara! def popen4(cmd, args={}, &b) - + # Waitlast - this is magic. + # + # Do we wait for the child process to die before we yield + # to the block, or after? That is the magic of waitlast. + # + # By default, we are waiting before we yield the block. args[:waitlast] ||= false args[:user] ||= nil @@ -298,11 +275,58 @@ class Chef # wants to do must be done - it's dead. If it isn't, # it's because something totally skanky is happening, # and we don't care. + o = StringIO.new + e = StringIO.new + pi[0].close - pi[1].fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) - pi[2].fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) - results = Process.waitpid2(cid).last - b[cid, *pi] + + stdout = pi[1] + stderr = pi[2] + + stdout.sync = true + stderr.sync = true + + stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) + stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) + + + stdout_finished = false + stderr_finished = false + + results = nil + + while !stdout_finished || !stderr_finished + begin + ready = IO.select([stdout, stderr], nil, nil, 1.0) + rescue Errno::EAGAIN + results = Process.waitpid2(cid, Process::WNOHANG) + if results + stdout_finished = true + stderr_finished = true + end + end + + if ready && ready.first.include?(stdout) + line = results ? stdout.gets(nil) : stdout.gets + if line + o.write(line) + else + stdout_finished = true + end + end + if ready && ready.first.include?(stderr) + line = results ? stderr.gets(nil) : stderr.gets + if line + e.write(line) + else + stderr_finished = true + end + end + end + results = Process.waitpid2(cid).last unless results + o.rewind + e.rewind + b[cid, pi[0], o, e] results end ensure diff --git a/chef/lib/chef/provider/cron.rb b/chef/lib/chef/provider/cron.rb index 88fd75e2d3..9e633053ce 100644 --- a/chef/lib/chef/provider/cron.rb +++ b/chef/lib/chef/provider/cron.rb @@ -64,7 +64,12 @@ class Chef status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr| stdout.each_line do |line| if cron_found - crontab << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n" + cronline = "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n" + if (line == cronline) + Chef::Log.debug("Skipping existing cron entry '#{@new_resource.name}'") + return + end + crontab << cronline cron_found = false next end @@ -76,6 +81,7 @@ class Chef end end + status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| crontab.each { |line| stdin.puts "#{line}" } stdin.close diff --git a/chef/lib/chef/provider/package/yum-dump-json.py b/chef/lib/chef/provider/package/yum-dump-json.py new file mode 100644 index 0000000000..115265e5aa --- /dev/null +++ b/chef/lib/chef/provider/package/yum-dump-json.py @@ -0,0 +1,67 @@ +# +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Copyright:: Copyright (c) 2009 Matthew Kent +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# yum-dump-json.py +# Inspired by yumhelper.py by David Lutterkort +# +# Produce a list of installed and available packages using yum and dump the +# result as json to stdout. +# +# This invokes yum just as the command line would which makes it subject to +# all the caching related configuration paramaters in yum.conf. +# +# Can be run as non root, but that won't update the cache. + +import os +import sys +import yum +import json + +y = yum.YumBase() +# Only want json in output +y.doConfigSetup(debuglevel=0, errorlevel=0) + +# yum assumes it can update the cache directory. Disable this for non root +# users. +y.conf.cache = os.geteuid() != 0 + +y.doTsSetup() +y.doRpmDBSetup() + +db = y.doPackageLists('all') + +y.closeRpmDB() + +combined = {} + +for pkg in db.installed: + combined[pkg.name] = {} + combined[pkg.name]["installed"] = { "epoch": pkg.epoch, + "version": pkg.version, + "release": pkg.release, + "arch": pkg.arch } +for pkg in db.available: + if not combined.has_key(pkg.name): + combined[pkg.name] = {} + combined[pkg.name]["available"] = { "epoch": pkg.epoch, + "version": pkg.version, + "release": pkg.release, + "arch": pkg.arch } +print json.write( combined ) + +sys.exit(0) diff --git a/chef/lib/chef/provider/package/yum.rb b/chef/lib/chef/provider/package/yum.rb index 7e4a6e96ba..5eab817f0e 100644 --- a/chef/lib/chef/provider/package/yum.rb +++ b/chef/lib/chef/provider/package/yum.rb @@ -19,63 +19,114 @@ require 'chef/provider/package' require 'chef/mixin/command' require 'chef/resource/package' +require 'singleton' class Chef class Provider class Package class Yum < Chef::Provider::Package + class YumCache + include Chef::Mixin::Command + include Singleton + + def initialize + @created_at = Time.now + load_data + end + + def stale? + interval = Chef::Config[:interval].to_f + + # run once mode + if interval == 0 + return false + elsif (Time.now - @created_at) > interval + return true + end + + false + end + + def refresh + if @data.empty? + reload + elsif stale? + reload + end + end + + def load_data + parsed = String.new + helper = ::File.join(::File.dirname(__FILE__), 'yum-dump-json.py') + status = popen4("python #{helper}", :waitlast => true) do |pid, stdin, stdout, stderr| + stdout.each do |line| + parsed << line + end + end + + unless status.exitstatus == 0 + raise Chef::Exceptions::Package, "yum failed - #{status.inspect}!" + end + + @data = JSON.parse(parsed) + end + alias :reload :load_data + + def version(package_name, type) + if (x = @data[package_name]) + if (y = x[type]) + return "#{y["version"]}-#{y["release"]}" + end + end + + nil + end + + def installed_version(package_name) + version(package_name, "installed") + end + + def candidate_version(package_name) + version(package_name, "available") + end + + def flush + @data.clear + end + end + + def initialize(node, new_resource) + @yum = YumCache.instance + super(node, new_resource) + end + def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) Chef::Log.debug("Checking yum info for #{@new_resource.package_name}") - status = popen4("yum info -q -y #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| - package_type = nil - installed_version = nil - candidate_version = nil - stdout.each do |line| - case line - when /^Installed Packages$/ - package_type = :installed - when /^Available Packages$/ - package_type = :available - when /^Version\s*: (.+)$/ - if package_type == :installed - installed_version = $1 - elsif package_type == :available - candidate_version = $1 - end - when /^Release: (.+)$/ - if package_type == :installed - installed_version += "-#{$1}" - Chef::Log.debug("Installed release is #{installed_version}") - elsif package_type == :available - candidate_version += "-#{$1}" - Chef::Log.debug("Candidate version is #{candidate_version}") - end - end - end - - @current_resource.version(installed_version) - if candidate_version - @candidate_version = candidate_version - else - @candidate_version = installed_version - end - end + + @yum.refresh - unless status.exitstatus == 0 - raise Chef::Exceptions::Package, "yum failed - #{status.inspect}!" + installed_version = @yum.installed_version(@new_resource.package_name) + @candidate_version = @yum.candidate_version(@new_resource.package_name) + + @current_resource.version(installed_version) + if candidate_version + @candidate_version = candidate_version + else + @candidate_version = installed_version end @current_resource end - + def install_package(name, version) run_command( :command => "yum -q -y install #{name}-#{version}" ) + @yum.flush end def upgrade_package(name, version) @@ -84,6 +135,7 @@ class Chef run_command( :command => "yum -q -y update #{name}-#{version}" ) + @yum.flush else install_package(name, version) end @@ -93,6 +145,7 @@ class Chef run_command( :command => "yum -q -y remove #{name}-#{version}" ) + @yum.flush end def purge_package(name, version) diff --git a/chef/spec/unit/couchdb_spec.rb b/chef/spec/unit/couchdb_spec.rb index fe7a80235f..62ba85e33d 100644 --- a/chef/spec/unit/couchdb_spec.rb +++ b/chef/spec/unit/couchdb_spec.rb @@ -31,15 +31,11 @@ describe Chef::CouchDB, "new" do Chef::REST.should_receive(:new).with("http://monkey") Chef::CouchDB.new end - + it "should create a new Chef::REST object from a provided url" do Chef::REST.should_receive(:new).with("http://monkeypants") Chef::CouchDB.new("http://monkeypants") end - - it "should parse the CouchDB version number" do - - end end describe Chef::CouchDB, "create_db" do @@ -252,23 +248,27 @@ describe Chef::CouchDB, "has_key?" do end end -describe Chef::CouchDB, "safe_name" do +describe Chef::CouchDB, "view_uri" do before do - @couchdb = mock("Chef::CouchDB", :null_object => true) - Chef::CouchDB.stub!(:new).and_return(@couchdb) + @mock_rest = mock("Chef::REST", :null_object => true, :url => "http://monkeypants") + Chef::REST.stub!(:new).and_return(@mock_rest) + @couchdb = Chef::CouchDB.new("http://localhost") end - it "should convert the name to a safe name" do - @couchdb.should_receive(:safe_name).with("asdf.lol.com").and_return("asdf_lol_com") - @couchdb.safe_name("asdf.lol.com") - end -end - -describe Chef::CouchDB, "view_uri" do - before do - @couchdb = mock("Chef::CouchDB", :null_object => true) - Chef::CouchDB.stub!(:new).and_return(@couchdb) - Chef::Config.stub!(:[]).with(:couchdb_database).and_return("chef") + describe "when the couchdb version is unknown" do + it "should set the couchdb version appropriately" do + ov = Chef::Config[:couchdb_version] + Chef::Config[:couchdb_version] = nil + @mock_rest.should_receive(:run_request).with( + :GET, + URI.parse("http://monkeypants/"), + false, + 10, + false + ).and_return({ "version" => "0.9" }) + @couchdb.view_uri("nodes", "all") + Chef::Config[:couchdb_version] = ov + end end describe "on couchdb 0.8" do @@ -292,4 +292,4 @@ describe Chef::CouchDB, "view_uri" do @couchdb.view_uri("nodes", "all") end end -end
\ No newline at end of file +end diff --git a/chef/spec/unit/mixin/command_spec.rb b/chef/spec/unit/mixin/command_spec.rb deleted file mode 100644 index d35712bab5..0000000000 --- a/chef/spec/unit/mixin/command_spec.rb +++ /dev/null @@ -1,85 +0,0 @@ -# -# Author:: Matthew Landauer (<matthew@openaustralia.org>) -# Copyright:: Copyright (c) 2008 Matthew Landauer -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) - -describe Chef::Mixin::Command do - before :each do - Chef::Log.init - end - - # Reset the logger for other tests - after :each do - Chef::Log.init - end - - it "should log the command's standard output at debug log level" do - command = "ruby -e 'puts 5'" - Chef::Log.should_receive(:debug).with("Executing #{command}").ordered - Chef::Log.should_receive(:debug).with("---- Begin output of #{command} ----").ordered - Chef::Log.should_receive(:debug).with("STDOUT: 5").ordered - Chef::Log.should_receive(:debug).with("---- End output of #{command} ----").ordered - Chef::Log.should_receive(:debug).with("Ran #{command} returned 0").ordered - Chef::Mixin::Command.run_command(:command => command) - end - - it "should log the command's standard error at debug log level" do - command = "ruby -e 'STDERR.puts 5'" - Chef::Log.should_receive(:debug).with("Executing #{command}").ordered - Chef::Log.should_receive(:debug).with("---- Begin output of #{command} ----").ordered - Chef::Log.should_receive(:debug).with("STDERR: 5").ordered - Chef::Log.should_receive(:debug).with("---- End output of #{command} ----").ordered - Chef::Log.should_receive(:debug).with("Ran #{command} returned 0").ordered - Chef::Mixin::Command.run_command(:command => command) - end - - it "should log the command's standard out and error at the same time" do - command = "ruby -e 'STDERR.puts 1; puts 2; STDERR.puts 3; puts 4'" - Chef::Log.should_receive(:debug).with("Executing #{command}").ordered - Chef::Log.should_receive(:debug).with("---- Begin output of #{command} ----").ordered - Chef::Log.should_receive(:debug).with("STDERR: 1").ordered - Chef::Log.should_receive(:debug).with("STDOUT: 2").ordered - Chef::Log.should_receive(:debug).with("STDERR: 3").ordered - Chef::Log.should_receive(:debug).with("STDOUT: 4").ordered - Chef::Log.should_receive(:debug).with("---- End output of #{command} ----").ordered - Chef::Log.should_receive(:debug).with("Ran #{command} returned 0").ordered - Chef::Mixin::Command.run_command(:command => command) - end - - it "should throw an exception if the command returns a bad exit value" do - command = "ruby -e 'puts 1; exit 1'" - Chef::Log.level :debug - # Stub out Chef::Log.debug to avoid messages going to console - Chef::Log.stub!(:debug) - lambda {Chef::Mixin::Command.run_command(:command => command)}.should raise_error(Chef::Exceptions::Exec, "#{command} returned 1, expected 0") - end - - it "should include the command output in the exception if the log level is not at debug" do - command = "ruby -e 'puts 1; exit 1'" - Chef::Log.level :info - lambda {Chef::Mixin::Command.run_command(:command => command)}.should raise_error(Chef::Exceptions::Exec, "#{command} returned 1, expected 0\n---- Begin output of #{command} ----\nSTDOUT: 1\n---- End output of #{command} ----\n") - end - - it "should log the output as the command is executing" do - command = "ruby -e 'STDOUT.sync = true; puts 1; sleep 2; puts 2'" - Chef::Log.should_receive(:debug).with("Executing #{command}").ordered - Chef::Log.should_receive(:debug).with("---- Begin output of #{command} ----").ordered - Chef::Log.should_receive(:debug).with("STDOUT: 1").ordered - lambda {Chef::Mixin::Command.run_command(:command => command, :timeout => 1)}.should raise_error(Timeout::Error) - end -end diff --git a/chef/spec/unit/mixin/template_spec.rb b/chef/spec/unit/mixin/template_spec.rb index 86e0b59232..05fee6820a 100644 --- a/chef/spec/unit/mixin/template_spec.rb +++ b/chef/spec/unit/mixin/template_spec.rb @@ -31,7 +31,8 @@ describe Chef::Mixin::Template, "render_template" do end it "should return a file" do - @template.render_template("abcdef", {}).should be_kind_of(File) + f = @template.render_template("abcdef", {}) + @template.render_template("abcdef", {}).should be_kind_of(Tempfile) end describe "when an exception is raised in the template" do diff --git a/chef/spec/unit/provider/cron_spec.rb b/chef/spec/unit/provider/cron_spec.rb index d09d14b93c..eda942e14b 100644 --- a/chef/spec/unit/provider/cron_spec.rb +++ b/chef/spec/unit/provider/cron_spec.rb @@ -128,7 +128,7 @@ describe Chef::Provider::Cron, "action_create" do @provider.action_create end - it "should update the cron entry if it exists" do + it "should update the cron entry if it exists and has changed" do @status = mock("Status", :exitstatus => 0) @stdin = mock("STDIN", :null_object => true) @stdout = mock("STDOUT", :null_object => true) @@ -142,7 +142,36 @@ describe Chef::Provider::Cron, "action_create" do @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_receive(:info).with("Updated cron '#{@new_resource.name}'") @provider.action_create + end + + it "should not update the cron entry if it exists and has not changed" do + resource = mock("Chef::Resource::Cron", + :null_object => true, + :name => "foo", + :minute => "30", + :hour => "*", + :day => "*", + :month => "*", + :weekday => "*", + :command => "/bin/true" + ) + provider = Chef::Provider::Cron.new(@node, resource) + + @status = mock("Status", :exitstatus => 0) + @stdin = mock("STDIN", :null_object => true) + @stdout = mock("STDOUT", :null_object => true) + @stderr = mock("STDERR", :null_object => true) + @pid = mock("PID", :null_object => true) + @stdout.stub!(:each_line).and_yield("# Chef Name: bar"). + and_yield("* 10 * * * /bin/false"). + and_yield("# Chef Name: foo\n"). + and_yield("30 * * * * /bin/true\n") + provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + Chef::Log.should_not_receive(:info).with("Updated cron '#{@new_resource.name}'") + Chef::Log.should_receive(:debug).with("Skipping existing cron entry '#{@new_resource.name}'") + provider.cron_exists = true + provider.action_create end end diff --git a/chef/spec/unit/provider/package/yum_spec.rb b/chef/spec/unit/provider/package/yum_spec.rb index 6a554c812f..2e8ddb9971 100644 --- a/chef/spec/unit/provider/package/yum_spec.rb +++ b/chef/spec/unit/provider/package/yum_spec.rb @@ -36,55 +36,16 @@ describe Chef::Provider::Package::Yum, "load_current_resource" do :updated => nil ) @status = mock("Status", :exitstatus => 0) + @yum_cache = mock( + 'Chef::Provider::Yum::YumCache', + :refresh => true, + :flush => true, + :installed_version => "1.2.4-11.18.el5", + :candidate_version => "1.2.4-11.18.el5_2.3" + ) + Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache) @provider = Chef::Provider::Package::Yum.new(@node, @new_resource) Chef::Resource::Package.stub!(:new).and_return(@current_resource) - @provider.stub!(:popen4).and_return(@status) - @stdin = mock("STDIN", :null_object => true) - @stdout = mock("STDOUT", :null_object => true) - @stdout.stub!(:each).and_yield("Installed Packages"). - and_yield("Name : cups"). - and_yield("Arch : i386"). - and_yield("Epoch : 1"). - and_yield("Version: 1.2.4"). - and_yield("Release: 11.18.el5"). - and_yield("Size : 7.8 M"). - and_yield("Repo : installed"). - and_yield("Summary: Common Unix Printing System"). - and_yield("Description:"). - and_yield("The Common UNIX Printing System provides a portable printing layer for"). - and_yield("UNIX® operating systems. It has been developed by Easy Software Products"). - and_yield("to promote a standard printing solution for all UNIX vendors and users."). - and_yield("CUPS provides the System V and Berkeley command-line interfaces."). - and_yield(""). - and_yield("Available Packages"). - and_yield("Name : cups"). - and_yield("Arch : i386"). - and_yield("Epoch : 1"). - and_yield("Version: 1.2.4"). - and_yield("Release: 11.18.el5_2.3"). - and_yield("Size : 2.7 M"). - and_yield("Repo : updates"). - and_yield("Summary: Common Unix Printing System"). - and_yield("Description:"). - and_yield("The Common UNIX Printing System provides a portable printing layer for"). - and_yield("UNIX® operating systems. It has been developed by Easy Software Products"). - and_yield("to promote a standard printing solution for all UNIX vendors and users."). - and_yield("CUPS provides the System V and Berkeley command-line interfaces.") - @stdout_available = mock("STDOUT AVAILABLE", :null_object => true) - @stdout_available.stub!(:each).and_yield("Available Packages"). - and_yield("Name : cups"). - and_yield("Arch : i386"). - and_yield("Epoch : 1"). - and_yield("Version: 1.2.4"). - and_yield("Release: 11.18.el5_2.3"). - and_yield("Size : 2.7 M"). - and_yield("Repo : updates"). - and_yield("Summary: Common Unix Printing System"). - and_yield("Description:"). - and_yield("The Common UNIX Printing System provides a portable printing layer for"). - and_yield("UNIX® operating systems. It has been developed by Easy Software Products"). - and_yield("to promote a standard printing solution for all UNIX vendors and users."). - and_yield("CUPS provides the System V and Berkeley command-line interfaces.") @stderr = mock("STDERR", :null_object => true) @pid = mock("PID", :null_object => true) end @@ -99,45 +60,22 @@ describe Chef::Provider::Package::Yum, "load_current_resource" do @provider.load_current_resource end - it "should run yum info with the package name" do - @provider.should_receive(:popen4).with("yum info -q -y #{@new_resource.package_name}").and_return(@status) - @provider.load_current_resource - end - - it "should read stdout on yum info" do - @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) - @stdout.should_receive(:each).and_return(true) - @provider.load_current_resource - end - it "should set the installed version to nil on the current resource if no installed package" do - @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout_available, @stderr).and_return(@status) + @yum_cache.stub!(:installed_version).and_return(nil) @current_resource.should_receive(:version).with(nil).and_return(true) @provider.load_current_resource end - it "should set the installed version if yum info has one" do - @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + it "should set the installed version if yum has one" do @current_resource.should_receive(:version).with("1.2.4-11.18.el5").and_return(true) @provider.load_current_resource end it "should set the candidate version if yum info has one" do - @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource @provider.candidate_version.should eql("1.2.4-11.18.el5_2.3") end - it "should raise an exception if yum info fails" do - @status.should_receive(:exitstatus).and_return(1) - lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package) - end - - it "should not raise an exception if yum info succeeds" do - @status.should_receive(:exitstatus).and_return(0) - lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Package) - end - it "should return the current resouce" do @provider.load_current_resource.should eql(@current_resource) end @@ -154,6 +92,14 @@ describe Chef::Provider::Package::Yum, "install_package" do :package_name => "emacs", :updated => nil ) + @yum_cache = mock( + 'Chef::Provider::Yum::YumCache', + :refresh => true, + :flush => true, + :installed_version => "1.2.4-11.18.el5", + :candidate_version => "1.2.4-11.18.el5_2.3" + ) + Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache) @provider = Chef::Provider::Package::Yum.new(@node, @new_resource) end @@ -169,13 +115,21 @@ describe Chef::Provider::Package::Yum, "upgrade_package" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Package", + @new_resource = mock("Chef::Resource::Package", :null_object => true, :name => "emacs", :version => nil, :package_name => "emacs", :updated => nil ) + @yum_cache = mock( + 'Chef::Provider::Yum::YumCache', + :refresh => true, + :flush => true, + :installed_version => "1.2.4-11.18.el5", + :candidate_version => "1.2.4-11.18.el5_2.3" + ) + Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache) @current_resource = mock("Chef::Resource::Package", :null_object => true, :name => "emacs", @@ -214,6 +168,14 @@ describe Chef::Provider::Package::Yum, "remove_package" do :package_name => "emacs", :updated => nil ) + @yum_cache = mock( + 'Chef::Provider::Yum::YumCache', + :refresh => true, + :flush => true, + :installed_version => "1.2.4-11.18.el5", + :candidate_version => "1.2.4-11.18.el5_2.3" + ) + Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache) @provider = Chef::Provider::Package::Yum.new(@node, @new_resource) end @@ -235,6 +197,14 @@ describe Chef::Provider::Package::Yum, "purge_package" do :package_name => "emacs", :updated => nil ) + @yum_cache = mock( + 'Chef::Provider::Yum::YumCache', + :refresh => true, + :flush => true, + :installed_version => "1.2.4-11.18.el5", + :candidate_version => "1.2.4-11.18.el5_2.3" + ) + Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache) @provider = Chef::Provider::Package::Yum.new(@node, @new_resource) end diff --git a/features/data/cookbooks/execute_commands/recipes/4k.rb b/features/data/cookbooks/execute_commands/recipes/4k.rb new file mode 100644 index 0000000000..bc810bd979 --- /dev/null +++ b/features/data/cookbooks/execute_commands/recipes/4k.rb @@ -0,0 +1,25 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +execute "create-4k-file" do + command "dd if=/dev/random of=#{node[:tmpdir]}/execute-4k.txt bs=1024 count=5" +end + +execute "read-4k-file" do + command "cat #{node[:tmpdir]}/execute-4k.txt" +end diff --git a/features/data/cookbooks/execute_commands/recipes/debug.rb b/features/data/cookbooks/execute_commands/recipes/debug.rb new file mode 100644 index 0000000000..5b76380508 --- /dev/null +++ b/features/data/cookbooks/execute_commands/recipes/debug.rb @@ -0,0 +1,22 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +execute "debug-log" do + command "ruby -e 'puts \"whats up\"; STDERR.puts \"doc!\"'" +end + diff --git a/features/data/cookbooks/execute_commands/recipes/default.rb b/features/data/cookbooks/execute_commands/recipes/default.rb new file mode 100644 index 0000000000..6a6f98361f --- /dev/null +++ b/features/data/cookbooks/execute_commands/recipes/default.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: execute_commands +# Recipe:: default +# +# Copyright 2009, Opscode +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +execute "create-mastodon" do + command "echo wewt > #{node[:tmpdir]}/mastodon_rocks" +end diff --git a/features/execute_commands.feature b/features/execute_commands.feature new file mode 100644 index 0000000000..2034cec79e --- /dev/null +++ b/features/execute_commands.feature @@ -0,0 +1,30 @@ +Feature: Execute Commands + In order to utilize the plethora of useful command line utilities + As a Developer + I want to execute commands from within chef + + Scenario: Execute a command + Given a validated node + And it includes the recipe 'execute_commands' + When I run the chef-client + Then the run should exit '0' + And a file named 'mastodon_rocks' should exist + + Scenario: Execute a command with more than 4k of output + Given a validated node + And it includes the recipe 'execute_commands::4k' + When I run the chef-client + Then the run should exit '0' + And a file named 'execute-4k.txt' should exist + + Scenario: Execute a command at the debug log level + Given a validated node + And it includes the recipe 'execute_commands::debug' + When I run the chef-client at log level 'debug' + Then the run should exit '0' + And 'stdout' should have 'DEBUG: Executing ruby -e .puts "whats up"; STDERR.puts "doc!".' + And 'stdout' should have 'DEBUG: ---- Begin output of ruby -e .puts "whats up"; STDERR.puts "doc!". ----' + And 'stdout' should have 'DEBUG: STDOUT: whats up' + And 'stdout' should have 'DEBUG: STDERR: doc!' + And 'stdout' should have 'DEBUG: ---- End output of ruby -e .puts "whats up"; STDERR.puts "doc!". ----' + And 'stdout' should have 'DEBUG: Ran ruby -e .puts "whats up"; STDERR.puts "doc!". returned 0' diff --git a/features/steps/run_client.rb b/features/steps/run_client.rb index 4afee9aa1d..ba7f01326a 100644 --- a/features/steps/run_client.rb +++ b/features/steps/run_client.rb @@ -20,16 +20,20 @@ # When ### When /^I run the chef\-client$/ do - log_level = ENV["LOG_LEVEL"] ? ENV["LOG_LEVEL"] : "error" + @log_level ||= ENV["LOG_LEVEL"] ? ENV["LOG_LEVEL"] : "error" status = Chef::Mixin::Command.popen4( - "chef-client -l #{log_level} -c #{File.expand_path(File.join(File.dirname(__FILE__), '..', 'data', 'config', 'client.rb'))}", :waitlast => true) do |p, i, o, e| - i.close + "chef-client -l #{@log_level} -c #{File.expand_path(File.join(File.dirname(__FILE__), '..', 'data', 'config', 'client.rb'))}") do |p, i, o, e| @stdout = o.gets(nil) @stderr = e.gets(nil) end @status = status end +When /^I run the chef\-client at log level '(.+)'$/ do |log_level| + @log_level = log_level + When "I run the chef-client" +end + ### # Then ### @@ -46,10 +50,10 @@ end def print_output puts "--- run stdout:" puts @stdout - puts "--- run stderr" + puts "--- run stderr:" puts @stderr end -Then /^stdout should have '(.+)'$/ do |to_match| - @stdout.should match(/#{to_match}/m) +Then /^'(.+)' should have '(.+)'$/ do |which, to_match| + self.instance_variable_get("@#{which}".to_sym).should match(/#{to_match}/m) end |